mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-20 01:46:15 +00:00
Merge pull request #102 from WanderingPix/meeting-abilities
Meeting abilities
This commit is contained in:
19 files changed
+1155
-5
No files matched your search
@@ -9,5 +9,6 @@ public static class ExampleAssets
|
||||
|
||||
// Credit to EpicHorrors for the teleport button asset.
|
||||
public static LoadableResourceAsset TeleportButton { get; } = new("MiraAPI.Example.Resources.TeleportButton.png");
|
||||
public static LoadableResourceAsset SharpshootButton { get; } = new("MiraAPI.Example.Resources.SharpshootButton.png");
|
||||
public static LoadableResourceAsset Banner { get; } = new("MiraAPI.Example.Resources.FortniteBanner.jpeg");
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using MiraAPI.MeetingAbilities;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.MeetingAbilities;
|
||||
|
||||
public class ShootButton : TargetedMeetingButton
|
||||
{
|
||||
public override string Name => "Shoot";
|
||||
|
||||
public override int MaxUses => 3;
|
||||
|
||||
public override float InitialCooldown => 5;
|
||||
|
||||
public override float Cooldown => 3;
|
||||
|
||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.SharpshootButton;
|
||||
|
||||
public override Color OutlineColor => Color.red;
|
||||
|
||||
public override bool Enabled(RoleBehaviour r)
|
||||
{
|
||||
return r.IsImpostor;
|
||||
}
|
||||
|
||||
protected override void OnClick(PlayerVoteArea playerVoteArea)
|
||||
{
|
||||
playerVoteArea.SetDisabled();
|
||||
var player = playerVoteArea.GetPlayer();
|
||||
PlayerControl.LocalPlayer.RpcCustomMurder(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using MiraAPI.MeetingAbilities;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.MeetingAbilities;
|
||||
|
||||
public class ShotgunButton : MultiTargetMeetingButton
|
||||
{
|
||||
public override string Name => "Shotgun";
|
||||
|
||||
public override int MaxUses => 3;
|
||||
|
||||
public override float InitialCooldown => 5;
|
||||
|
||||
public override float Cooldown => 3;
|
||||
|
||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.SharpshootButton;
|
||||
|
||||
public override Color OutlineColor => Color.red;
|
||||
|
||||
public override bool Enabled(RoleBehaviour r)
|
||||
{
|
||||
return r.IsImpostor;
|
||||
}
|
||||
|
||||
protected override void OnClick(PlayerVoteArea playerVoteArea)
|
||||
{
|
||||
}
|
||||
|
||||
public override LoadableAsset<Sprite> SpriteActive => ExampleAssets.CallMeetingButton;
|
||||
|
||||
public override int TargetsCount => 4;
|
||||
|
||||
protected override void OnSelect(PlayerVoteArea playerVoteArea, bool toggle)
|
||||
{
|
||||
HudManager.Instance.PlayerCam.ShakeScreen(0.5f, 2);
|
||||
}
|
||||
|
||||
public override void OnFinish()
|
||||
{
|
||||
foreach (var playerVoteArea in Targets.Keys)
|
||||
{
|
||||
playerVoteArea.SetDisabled();
|
||||
var player = playerVoteArea.GetPlayer();
|
||||
PlayerControl.LocalPlayer.RpcCustomMurder(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
@@ -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.
|
||||
@@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.MeetingAbilities;
|
||||
|
||||
/// <summary>
|
||||
/// A component for handling targeted meeting buttons.
|
||||
/// </summary>
|
||||
[RegisterInIl2Cpp]
|
||||
public class MeetingAbilityBehaviour : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets whether the component has been initialized.
|
||||
/// </summary>
|
||||
private bool _init;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="TargetedMeetingButton"/> attached to this component.
|
||||
/// </summary>
|
||||
private TargetedMeetingButton _button = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="PassiveButton"/> of this component.
|
||||
/// </summary>
|
||||
public PassiveButton Button;
|
||||
|
||||
/// <summary>
|
||||
/// The parent <see cref="PlayerVoteArea"/>.
|
||||
/// </summary>
|
||||
public PlayerVoteArea VoteArea;
|
||||
|
||||
/// <summary>
|
||||
/// The button's <see cref="SpriteRenderer"/> component.
|
||||
/// </summary>
|
||||
public SpriteRenderer Renderer;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="TextMeshPro"/> label used for showing the ability's cooldown.
|
||||
/// </summary>
|
||||
public TextMeshPro CooldownText;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="TextMeshPro"/> label used for showing the ability's remaining uses.
|
||||
/// </summary>
|
||||
public TextMeshPro UsesText;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the various properties of this component.
|
||||
/// </summary>
|
||||
/// <param name="button">The <see cref="TargetedMeetingButton"/> to initialize this component for.</param>
|
||||
/// <param name="voteArea">The parent <see cref="PlayerVoteArea"/>.</param>
|
||||
public void Initialize(TargetedMeetingButton button, PlayerVoteArea voteArea)
|
||||
{
|
||||
if (_init) return;
|
||||
VoteArea = voteArea;
|
||||
_button = button;
|
||||
Renderer = GetComponent<SpriteRenderer>();
|
||||
SetFillUp(1, 1);
|
||||
if (!TryGetComponent(out Button))
|
||||
{
|
||||
Error($"Could not initialize MeetingButtonBehaviour for {Button.GetType().Name}, Destroying...");
|
||||
Destroy(this);
|
||||
}
|
||||
_init = true;
|
||||
CooldownText = Instantiate(HudManager.Instance.KillButton.cooldownTimerText, transform);
|
||||
CooldownText.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
CooldownText.transform.localPosition = new Vector3(0, 0, -10);
|
||||
CooldownText.m_maxFontSize = 3f;
|
||||
CooldownText.color = new Color(1, 1, 1, 0.7f);
|
||||
UsesText = Instantiate(CooldownText, transform);
|
||||
UsesText.transform.localPosition = new Vector3(0.25f, 0.25f, -10);
|
||||
UsesText.transform.localScale = Vector3.one * 0.7f;
|
||||
|
||||
CooldownText.gameObject.SetActive(true);
|
||||
UsesText.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (!_init) return;
|
||||
Button.enabled = true;
|
||||
Renderer.enabled = true;
|
||||
UsesText.enabled = true;
|
||||
CooldownText.enabled = true;
|
||||
if (_button.Enabled(PlayerControl.LocalPlayer.Data.Role) && _button.IsTargetValid(VoteArea)) return;
|
||||
Button.enabled = false;
|
||||
Renderer.enabled = false;
|
||||
UsesText.enabled = false;
|
||||
CooldownText.enabled = false;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_init) return;
|
||||
if (_button.Timer < 0.01f)
|
||||
{
|
||||
Renderer.material.SetFloat("_Desat", 0.0f);
|
||||
CooldownText.text = string.Empty;
|
||||
UsesText.text = _button.LimitedUses ? _button.UsesLeft.ToString(CultureInfo.InvariantCulture) : string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
Renderer.material.SetFloat("_Desat", 0.5f);
|
||||
CooldownText.text = _button.Timer.ToString(_button.CooldownTimerFormatString, NumberFormatInfo.InvariantInfo);
|
||||
UsesText.text = string.Empty;
|
||||
}
|
||||
SetFillUp(_button.Timer, _button.InitialCooldown);
|
||||
}
|
||||
|
||||
public void SetFillUp(float timer, float maxTimer)
|
||||
{
|
||||
float percentCool = Mathf.Clamp(timer / maxTimer, 0.001f, 1f);
|
||||
if (percentCool <= 0) percentCool = 1f;
|
||||
Renderer.material.SetFloat("_Percent", percentCool);
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace MiraAPI.MeetingAbilities;
|
||||
|
||||
public enum MeetingButtonUsesMode
|
||||
{
|
||||
PerGame,
|
||||
PerMeeting
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace MiraAPI.MeetingAbilities;
|
||||
|
||||
/// <summary>
|
||||
/// Abstract class for creating multi target meeting abilities
|
||||
/// </summary>
|
||||
public abstract class MultiTargetMeetingButton : TargetedMeetingButton
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="LoadableAsset{Sprite}"/> used for when the button is on.
|
||||
/// </summary>
|
||||
public abstract LoadableAsset<Sprite> SpriteActive { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the required targets count.
|
||||
/// </summary>
|
||||
public abstract int TargetsCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets all selected targets.
|
||||
/// </summary>
|
||||
public Dictionary<PlayerVoteArea, MeetingAbilityBehaviour> Targets { get; set; } = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ClickHandler(MeetingAbilityBehaviour button, PlayerVoteArea playerVoteArea)
|
||||
{
|
||||
if (UsesLeft <= 0 && LimitedUses) return;
|
||||
if (Timer > 0) return;
|
||||
var toggle = !Targets.Remove(playerVoteArea);
|
||||
if (toggle) Targets.Add(playerVoteArea, button);
|
||||
button.Renderer.sprite = toggle ? SpriteActive.LoadAsset() : Sprite.LoadAsset();
|
||||
if (Targets.Count >= TargetsCount)
|
||||
{
|
||||
playerVoteArea.Cancel();
|
||||
HandleFinish();
|
||||
return;
|
||||
}
|
||||
OnSelect(playerVoteArea, toggle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the finish logic, increasing timer, and decreasing uses.
|
||||
/// </summary>
|
||||
public virtual void HandleFinish()
|
||||
{
|
||||
Targets = Targets.Take(TargetsCount).ToDictionary(pair => pair.Key, pair => pair.Value);
|
||||
foreach (var button in Targets.Values)
|
||||
{
|
||||
button.Renderer.sprite = Sprite.LoadAsset();
|
||||
}
|
||||
Timer = Cooldown;
|
||||
UsesLeft -= 1;
|
||||
OnFinish();
|
||||
Targets = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback method for when a player is selected.
|
||||
/// </summary>
|
||||
/// <param name="playerVoteArea">The target <see cref="PlayerVoteArea"/>.</param>
|
||||
/// <param name="toggle">Whether the button was toggled on/off.</param>
|
||||
protected abstract void OnSelect(PlayerVoteArea playerVoteArea, bool toggle);
|
||||
|
||||
/// <summary>
|
||||
/// Callback method for the button click.
|
||||
/// USE <see cref="OnSelect"/> INSTEAD!!!.
|
||||
/// </summary>
|
||||
/// <param name="playerVoteArea">The target <see cref="PlayerVoteArea"/>.</param>
|
||||
protected override void OnClick(PlayerVoteArea playerVoteArea)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback method for when enough targets are chosen.
|
||||
/// </summary>
|
||||
public abstract void OnFinish();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override MeetingAbilityBehaviour CreateButton(PlayerVoteArea playerVoteArea)
|
||||
{
|
||||
Targets = new();
|
||||
var button = base.CreateButton(playerVoteArea);
|
||||
button.Button.OnClick = new Button.ButtonClickedEvent();
|
||||
button.Button.OnClick.AddListener(new System.Action(() => { ClickHandler(button, playerVoteArea); }));
|
||||
return button;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
using System.Collections.Generic;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace MiraAPI.MeetingAbilities;
|
||||
|
||||
/// <summary>
|
||||
/// Abstract class for creating targeted meeting abilities.
|
||||
/// </summary>
|
||||
public abstract class TargetedMeetingButton
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the <see cref="TargetedMeetingButton"/>.
|
||||
/// </summary>
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Max Uses of the button.
|
||||
/// Set it to 0 for Infinite uses.
|
||||
/// </summary>
|
||||
public abstract int MaxUses { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of uses left for this button.
|
||||
/// </summary>
|
||||
public int UsesLeft { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the <see cref="TargetedMeetingButton"/> has limited uses.
|
||||
/// </summary>
|
||||
public bool LimitedUses => MaxUses != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cooldown time for this button upon meeting start.
|
||||
/// Defaults to <see cref="Cooldown"/> unless overridden.
|
||||
/// </summary>
|
||||
public virtual float InitialCooldown => Cooldown;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cooldown time for this button after being used.
|
||||
/// </summary>
|
||||
public abstract float Cooldown { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current cooldown timer for this button.
|
||||
/// </summary>
|
||||
public float Timer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cooldown time for this button.
|
||||
/// </summary>
|
||||
public virtual string CooldownTimerFormatString => "0";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for this button.
|
||||
/// </summary>
|
||||
public abstract LoadableAsset<Sprite> Sprite { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the button's outline color.
|
||||
/// </summary>
|
||||
public abstract Color OutlineColor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="MeetingButtonUsesMode"/> for this button.
|
||||
/// </summary>
|
||||
public virtual MeetingButtonUsesMode UsesMode { get; } = MeetingButtonUsesMode.PerGame;
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the button is enabled.
|
||||
/// </summary>
|
||||
/// <param name="r">The local player's <see cref="RoleBehaviour"/>.</param>
|
||||
/// <returns>Whether the button is enabled or not.</returns>
|
||||
public abstract bool Enabled(RoleBehaviour r);
|
||||
|
||||
/// <summary>
|
||||
/// Callback method for when the button is clicked.
|
||||
/// </summary>
|
||||
/// <param name="playerVoteArea">The target <see cref="PlayerVoteArea"/>.</param>
|
||||
protected abstract void OnClick(PlayerVoteArea playerVoteArea);
|
||||
|
||||
/// <summary>
|
||||
/// Handles the button creation.
|
||||
/// </summary>
|
||||
/// <param name="playerVoteArea">The target <see cref="PlayerVoteArea"/>.</param>
|
||||
/// <returns>The <see cref="MeetingAbilityBehaviour"/> instance.</returns>
|
||||
public virtual MeetingAbilityBehaviour CreateButton(PlayerVoteArea playerVoteArea)
|
||||
{
|
||||
if (MeetingHud.Instance == null) return null!;
|
||||
|
||||
var btn = Object.Instantiate(MeetingHud.Instance.PlayerButtonPrefab.ConfirmButton).GetComponent<PassiveButton>();
|
||||
btn.gameObject.name = Name + "Button";
|
||||
|
||||
var spriteRenderer = btn.GetComponent<SpriteRenderer>();
|
||||
spriteRenderer.sprite = Sprite.LoadAsset();
|
||||
spriteRenderer.material = new Material(HudManager.Instance.KillButton.graphic.material);
|
||||
spriteRenderer.SetCooldownNormalizedUvs();
|
||||
btn.gameObject.SetActive(true);
|
||||
|
||||
btn.transform.GetChild(0).GetComponent<SpriteRenderer>().color = OutlineColor;
|
||||
|
||||
btn.OnClick = new Button.ButtonClickedEvent();
|
||||
var abilityBehaviour = btn.gameObject.AddComponent<MeetingAbilityBehaviour>();
|
||||
abilityBehaviour.Initialize(this, playerVoteArea);
|
||||
btn.OnClick.AddListener(new System.Action(() => ClickHandler(abilityBehaviour, playerVoteArea)));
|
||||
Timer = InitialCooldown;
|
||||
|
||||
return abilityBehaviour;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called every <see cref="MeetingHud.Update"/>.
|
||||
/// Handles the timer logic.
|
||||
/// </summary>
|
||||
public virtual void UpdateHandler()
|
||||
{
|
||||
Timer -= Time.deltaTime;
|
||||
Timer = Mathf.Clamp(Timer, 0, int.MaxValue);
|
||||
FixedUpdate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback method for handling logic every frame.
|
||||
/// </summary>
|
||||
public virtual void FixedUpdate()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the button is clicked
|
||||
/// Handles increasing cooldown and decreasing uses remaining.
|
||||
/// </summary>
|
||||
/// <param name="button">The <see cref="MeetingAbilityBehaviour"/> instance.</param>
|
||||
/// <param name="playerVoteArea">The target <see cref="PlayerVoteArea"/>.</param>
|
||||
public virtual void ClickHandler(MeetingAbilityBehaviour button, PlayerVoteArea playerVoteArea)
|
||||
{
|
||||
if (UsesLeft <= 0 && LimitedUses) return;
|
||||
if (Timer > 0) return;
|
||||
Timer = Cooldown;
|
||||
UsesLeft--;
|
||||
playerVoteArea.Cancel();
|
||||
OnClick(playerVoteArea);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the target is valid.
|
||||
/// </summary>
|
||||
/// <param name="playerVoteArea">The target <see cref="PlayerVoteArea"/>.</param>
|
||||
/// <returns>Whether the target is valid or not.</returns>
|
||||
public virtual bool IsTargetValid(PlayerVoteArea playerVoteArea)
|
||||
{
|
||||
return playerVoteArea != null && playerVoteArea.PlayerId.Value != PlayerControl.LocalPlayer.Data.PlayerId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace MiraAPI.MeetingAbilities;
|
||||
|
||||
/// <summary>
|
||||
/// A utility class to get the instance of a <see cref="TargetedMeetingButton"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the button you are trying to access.</typeparam>
|
||||
public static class TargetedMeetingButtonSingleton<T> where T : TargetedMeetingButton
|
||||
{
|
||||
private static T? _instance;
|
||||
|
||||
/// <summary>
|
||||
/// 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 ??= MeetingButtonManager.TargetedButtons.OfType<T>().Single();
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
using MiraAPI.MeetingAbilities;
|
||||
using MiraAPI.Utilities;
|
||||
|
||||
namespace MiraAPI.Patches.Events;
|
||||
@@ -18,6 +20,7 @@ public static class FreeplayRoundStartPatch
|
||||
{
|
||||
if (!__result)
|
||||
{
|
||||
MeetingButtonManager.OnGameStart();
|
||||
MiraEventManager.InvokeEvent(new RoundStartEvent(true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime.InteropTypes;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
using MiraAPI.MeetingAbilities;
|
||||
using MiraAPI.GameModes;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
@@ -126,7 +128,9 @@ public static class IntroCutscenePatches
|
||||
{
|
||||
introCutscene = __instance.Cast<IntroCutscene>();
|
||||
}
|
||||
Info("IntroCutscene ended");
|
||||
|
||||
MeetingButtonManager.OnGameStart();
|
||||
MiraEventManager.InvokeEvent(new IntroEndEvent(introCutscene));
|
||||
|
||||
var @event = new BeforeRoundStartEvent(true);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
@@ -6,9 +8,12 @@ using InnerNet;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Meeting;
|
||||
using MiraAPI.Events.Vanilla.Meeting.Voting;
|
||||
using MiraAPI.MeetingAbilities;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Components;
|
||||
using MiraAPI.Voting;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches.Voting;
|
||||
|
||||
@@ -34,10 +39,46 @@ internal static class MeetingHudPatches
|
||||
return true;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(PlayerVoteArea), nameof(PlayerVoteArea.Select))]
|
||||
public static bool VoteAreaSelectPatch(PlayerVoteArea __instance)
|
||||
{
|
||||
if (PlayerControl.LocalPlayer.Data.IsDead || __instance.AmDead || !(bool) (UnityEngine.Object) __instance.Parent)
|
||||
return false;
|
||||
JudgeRole? judgeRole = PlayerControl.LocalPlayer.Data.Role.TryCast<JudgeRole>();
|
||||
bool flag = judgeRole && PlayerControl.LocalPlayer.PlayerId != __instance.PlayerId;
|
||||
__instance.JudgeOverruleButton?.gameObject.SetActive(flag);
|
||||
if (__instance.VoteComplete || !__instance.Parent.Select((byte) __instance.PlayerId))
|
||||
return false;
|
||||
__instance.Buttons.SetActive(true);
|
||||
|
||||
float startPos = __instance.AnimateButtonsFromLeft ? 0.2f : 1.95f;
|
||||
|
||||
Il2CppSystem.Collections.Generic.List<UiElement> selectableElements = new Il2CppSystem.Collections.Generic.List<UiElement>();
|
||||
foreach (var btn in __instance.Buttons.GetComponentsInChildren<PassiveButton>())
|
||||
{
|
||||
selectableElements.Add(btn);
|
||||
}
|
||||
|
||||
for (int i = 0; i < selectableElements.Count; i++)
|
||||
{
|
||||
var button = selectableElements[i];
|
||||
float endPos = 1.3f - 0.65f * i;
|
||||
float duration = 0.25f + 0.1f * i;
|
||||
__instance.StartCoroutine(Effects.All(Effects.Lerp(duration, (Action<float>) (t =>
|
||||
button.transform.localPosition = Vector2.Lerp(Vector2.right * startPos, Vector2.right * endPos, Effects.ExpOut(t))))));
|
||||
}
|
||||
|
||||
ControllerManager.Instance.OpenOverlayMenu(__instance.name, __instance.CancelButton, selectableElements[1], selectableElements);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(MeetingHud.Start))]
|
||||
public static void MeetingHudStartPatch(MeetingHud __instance)
|
||||
{
|
||||
MeetingButtonManager.OnMeetingStart(__instance);
|
||||
foreach (var plr in PlayerControl.AllPlayerControls)
|
||||
{
|
||||
var voteData = plr.GetVoteData();
|
||||
@@ -78,6 +119,16 @@ internal static class MeetingHudPatches
|
||||
[HarmonyPatch(nameof(MeetingHud.Update))]
|
||||
public static void ForceSkipPatch(MeetingHud __instance)
|
||||
{
|
||||
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;
|
||||
@@ -106,7 +157,7 @@ internal static class MeetingHudPatches
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(MeetingHud.Select))]
|
||||
public static bool SelectPatch(MeetingHud __instance, int suspectStateIdx)
|
||||
public static bool MeetingHudSelectPatch(MeetingHud __instance, int suspectStateIdx)
|
||||
{
|
||||
var voteData = PlayerControl.LocalPlayer.GetVoteData();
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using BepInEx.Configuration;
|
||||
using MiraAPI.GameModes;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.MeetingAbilities;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Presets;
|
||||
|
||||
@@ -58,6 +59,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>
|
||||
@@ -71,6 +82,8 @@ public class MiraPluginInfo
|
||||
Options = [..InternalOptions];
|
||||
Roles = new ReadOnlyDictionary<ushort, RoleBehaviour>(InternalRoles);
|
||||
Buttons = [..InternalButtons];
|
||||
MeetingButtons = [..InternalMeetingButtons];
|
||||
TargetedMeetingButtons = [..InternalTargetedMeetingButtons];
|
||||
}
|
||||
|
||||
internal List<OptionPreset> InternalPresets { get; } = [];
|
||||
@@ -86,6 +99,10 @@ public class MiraPluginInfo
|
||||
|
||||
internal List<CustomActionButton> InternalButtons { 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.
|
||||
/// </summary>
|
||||
|
||||
@@ -18,6 +18,7 @@ using MiraAPI.Hud;
|
||||
using MiraAPI.Keybinds;
|
||||
using MiraAPI.LocalSettings;
|
||||
using MiraAPI.LocalSettings.Attributes;
|
||||
using MiraAPI.MeetingAbilities;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Patches.Options;
|
||||
using MiraAPI.Presets;
|
||||
@@ -223,6 +224,17 @@ public sealed class MiraPluginManager
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (RegisterMeetingAbility(type, info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterTargetedMeetingAbility(type, info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterGameModeAttribute(type, info))
|
||||
{
|
||||
continue;
|
||||
@@ -583,4 +595,29 @@ public sealed class MiraPluginManager
|
||||
Error($"Failed to register keybind class {type.Name}: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool RegisterMeetingAbility(Type type, MiraPluginInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
return MeetingButtonManager.RegisterMeetingButton(type, info);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Error($"Failed to register meeting ability {type.Name}: {e}");
|
||||
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,87 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Utilities.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Utility component for arranging objects in a centered layout, similar to <see cref="GridArrange"/>.
|
||||
/// </summary>
|
||||
/// <param name="ptr"></param>
|
||||
[RegisterInIl2Cpp]
|
||||
public class CenteredGridArrange(IntPtr ptr) : MonoBehaviour(ptr)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the cell size, which is used for spacing.
|
||||
/// </summary>
|
||||
public Vector2 CellSize;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum amount of columns.
|
||||
/// </summary>
|
||||
public int MaxColumns = 6;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user