mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-19 15:51:48 +00:00
WIP Targeted meeting abilities API
This commit is contained in:
1 parent
f96f75cea1
commit
e381de033e
11 files changed
+376
-3
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,28 @@
|
||||
using MiraAPI.MeetingAbilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.MeetingAbilities;
|
||||
|
||||
public class ShootButton : TargetedMeetingButton
|
||||
{
|
||||
public override string Name => "Shooooooot";
|
||||
|
||||
public override int MaxUses => 0;
|
||||
|
||||
public override float Cooldown => 10;
|
||||
|
||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.SharpshootButton;
|
||||
|
||||
public override MeetingButtonUsesMode ButtonUsesMode => MeetingButtonUsesMode.PerGame;
|
||||
|
||||
public override bool Enabled(RoleBehaviour r)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnClick(PlayerVoteArea playerVoteArea)
|
||||
{
|
||||
playerVoteArea.SetDisabled();
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.MeetingAbilities;
|
||||
|
||||
[RegisterInIl2Cpp]
|
||||
public class MeetingAbilityBehaviour(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
{
|
||||
private bool _init = false;
|
||||
private TargetedMeetingButton _button = null!;
|
||||
public PassiveButton Button;
|
||||
public PlayerVoteArea VoteArea;
|
||||
public SpriteRenderer Renderer;
|
||||
public TextMeshPro CooldownText;
|
||||
public TextMeshPro UsesText;
|
||||
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;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (!_init) return;
|
||||
Button.enabled = true;
|
||||
Renderer.enabled = true;
|
||||
if (_button.Enabled(PlayerControl.LocalPlayer.Data.Role) && _button.IsTargetValid(VoteArea)) return;
|
||||
Button.enabled = false;
|
||||
Renderer.enabled = false;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (!_init) return;
|
||||
if (_button.Timer < 0)
|
||||
{
|
||||
Renderer.material.SetFloat("_Desat", 0.0f);
|
||||
CooldownText.text = string.Empty;
|
||||
UsesText.text = _button.LimitedUses ? _button.UsesRemaining.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.Cooldown);
|
||||
}
|
||||
|
||||
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,7 @@
|
||||
namespace MiraAPI.MeetingAbilities;
|
||||
|
||||
public enum MeetingButtonUsesMode
|
||||
{
|
||||
PerGame,
|
||||
PerMeeting
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
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;
|
||||
|
||||
public static class TargetedMeetingAbilityManager
|
||||
{
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
using System.Collections.Generic;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace MiraAPI.MeetingAbilities;
|
||||
|
||||
/// <summary>
|
||||
/// Abstract class for creating custom 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 UsesRemaining { 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.
|
||||
/// </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 <see cref="MeetingButtonUsesMode"/> for this button.
|
||||
/// </summary>
|
||||
public virtual MeetingButtonUsesMode ButtonUsesMode { 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.OnClick = new Button.ButtonClickedEvent();
|
||||
btn.OnClick.AddListener(new System.Action(() => ClickHandler(playerVoteArea)));
|
||||
var abilityBehaviour = btn.gameObject.AddComponent<MeetingAbilityBehaviour>();
|
||||
abilityBehaviour.Initialize(this, playerVoteArea);
|
||||
Timer = Cooldown;
|
||||
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="playerVoteArea">The target <see cref="PlayerVoteArea"/>.</param>
|
||||
public virtual void ClickHandler(PlayerVoteArea playerVoteArea)
|
||||
{
|
||||
if (UsesRemaining <= 0 && LimitedUses) return;
|
||||
if (Timer > 0) return;
|
||||
Timer = Cooldown;
|
||||
UsesRemaining--;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
|
||||
@@ -128,6 +130,11 @@ public static class IntroCutscenePatches
|
||||
var @event = new BeforeRoundStartEvent(true);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
|
||||
foreach (var ability in TargetedMeetingAbilityManager.Buttons.Where(x => x.ButtonUsesMode == MeetingButtonUsesMode.PerGame))
|
||||
{
|
||||
ability.UsesRemaining = ability.MaxUses;
|
||||
}
|
||||
|
||||
if (@event.IsCancelled) return;
|
||||
MiraEventManager.InvokeEvent(new RoundStartEvent(true));
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
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.Voting;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches.Voting;
|
||||
|
||||
@@ -33,6 +37,54 @@ internal static class MeetingHudPatches
|
||||
return true;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(PlayerVoteArea), nameof(PlayerVoteArea.Start))]
|
||||
public static void VoteAreaStartPostfix(PlayerVoteArea __instance)
|
||||
{
|
||||
foreach (var targetedMeetingAbility in TargetedMeetingAbilityManager.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)
|
||||
{
|
||||
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)
|
||||
@@ -54,6 +106,10 @@ internal static class MeetingHudPatches
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var ability in TargetedMeetingAbilityManager.Buttons.Where(x => x.ButtonUsesMode == MeetingButtonUsesMode.PerMeeting))
|
||||
{
|
||||
ability.UsesRemaining = ability.MaxUses;
|
||||
}
|
||||
var @event = new StartMeetingEvent(__instance);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
}
|
||||
@@ -77,6 +133,11 @@ internal static class MeetingHudPatches
|
||||
[HarmonyPatch(nameof(MeetingHud.Update))]
|
||||
public static void ForceSkipPatch(MeetingHud __instance)
|
||||
{
|
||||
foreach (var targetedMeetingAbility in TargetedMeetingAbilityManager.Buttons)
|
||||
{
|
||||
targetedMeetingAbility.UpdateHandler();
|
||||
}
|
||||
|
||||
if (__instance.state is not (MeetingHud.MeetingStates.NotVoted or MeetingHud.MeetingStates.Voted))
|
||||
{
|
||||
return;
|
||||
@@ -105,7 +166,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;
|
||||
|
||||
@@ -78,6 +79,8 @@ public class MiraPluginInfo
|
||||
|
||||
internal List<CustomActionButton> InternalButtons { get; } = [];
|
||||
|
||||
internal List<TargetedMeetingButton?> InternalMeetingAbilities { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin's ID, as defined in the plugin's BepInEx metadata.
|
||||
/// </summary>
|
||||
|
||||
@@ -16,6 +16,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;
|
||||
@@ -117,6 +118,11 @@ public sealed class MiraPluginManager
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterMeetingAbility(type, info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RegisterColorClasses(type);
|
||||
RegisterKeybinds(type, plugin);
|
||||
@@ -443,4 +449,17 @@ public sealed class MiraPluginManager
|
||||
Error($"Failed to register keybind class {type.Name}: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool RegisterMeetingAbility(Type type, MiraPluginInfo info)
|
||||
{
|
||||
try
|
||||
{
|
||||
return TargetedMeetingAbilityManager.RegisterMeetingAbility(type, info);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Error($"Failed to register meeting ability {type.Name}: {e}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user