diff --git a/MiraAPI.Example/ExampleEventHandlers.cs b/MiraAPI.Example/ExampleEventHandlers.cs new file mode 100644 index 0000000..e8b2c80 --- /dev/null +++ b/MiraAPI.Example/ExampleEventHandlers.cs @@ -0,0 +1,27 @@ +using MiraAPI.Events; +using MiraAPI.Events.Mira; +using MiraAPI.Example.Buttons.Freezer; +using Reactor.Utilities; + +namespace MiraAPI.Example; + +public static class ExampleEventHandlers +{ + public static void Initialize() + { + // Register event handlers here + MiraEventManager.RegisterEventHandler>(FreezeButtonClickHandler, 1); + } + + // Example event handler + public static void FreezeButtonClickHandler(MiraButtonClickEvent @event) + { + Logger.Warning("Freeze button clicked!"); + + if (PlayerControl.LocalPlayer.Data.PlayerName == "stupid") + { + @event.Cancel(); + @event.Button.SetTimer(15f); + } + } +} diff --git a/MiraAPI.Example/ExamplePlugin.cs b/MiraAPI.Example/ExamplePlugin.cs index eb4402c..6740f22 100644 --- a/MiraAPI.Example/ExamplePlugin.cs +++ b/MiraAPI.Example/ExamplePlugin.cs @@ -25,11 +25,15 @@ public partial class ExamplePlugin : BasePlugin, IMiraPlugin public ConfigFile GetConfigFile() => Config; public override void Load() { - MiraEventManager.RegisterEventHandler>(e=> + MiraEventManager.RegisterEventHandler>(e=> { Logger.Warning("Freeze button clicked!"); - e.Cancel(); - e.Button.SetTimer(15f); + + if (PlayerControl.LocalPlayer.Data.PlayerName == "stupid") + { + e.Cancel(); + e.Button.SetTimer(15f); + } }); MiraEventManager.RegisterEventHandler>(e=> diff --git a/MiraAPI/Events/Mira/MiraButtonCancelledEvent.cs b/MiraAPI/Events/Mira/MiraButtonCancelledEvent.cs index 41c8bba..f4a6ebf 100644 --- a/MiraAPI/Events/Mira/MiraButtonCancelledEvent.cs +++ b/MiraAPI/Events/Mira/MiraButtonCancelledEvent.cs @@ -22,3 +22,23 @@ public sealed class MiraButtonCancelledEvent : MiraEvent where T : CustomActi Button = button; } } + +/// +/// Invoked when a Mira Button click is cancelled. Do not use for vanilla buttons. +/// +public sealed class MiraButtonCancelledEvent : MiraEvent +{ + /// + /// Gets Mira Button whose click was cancelled. + /// + public CustomActionButton Button { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The Mira Button whose click was cancelled. + public MiraButtonCancelledEvent(CustomActionButton button) + { + Button = button; + } +} diff --git a/MiraAPI/Events/Mira/MiraButtonClickEvent.cs b/MiraAPI/Events/Mira/MiraButtonClickEvent.cs index 9dc58eb..c070383 100644 --- a/MiraAPI/Events/Mira/MiraButtonClickEvent.cs +++ b/MiraAPI/Events/Mira/MiraButtonClickEvent.cs @@ -13,12 +13,19 @@ public sealed class MiraButtonClickEvent : MiraCancelableEvent where T : Cust /// public T Button { get; } + /// + /// Gets the generic click event that is invoked before this button-specific event. + /// + public MiraButtonClickEvent GenericClickEvent { get; } + /// /// Initializes a new instance of the class. /// /// The Mira Button that was clicked. - public MiraButtonClickEvent(T button) + /// The generic click event invoked before button-specific events. + public MiraButtonClickEvent(T button, MiraButtonClickEvent genericClickEvent) { + GenericClickEvent = genericClickEvent; Button = button; } } diff --git a/MiraAPI/Events/MiraEventManager.cs b/MiraAPI/Events/MiraEventManager.cs index b2fa3a1..7d6486c 100644 --- a/MiraAPI/Events/MiraEventManager.cs +++ b/MiraAPI/Events/MiraEventManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using BepInEx.Unity.IL2CPP; using Reactor.Utilities; namespace MiraAPI.Events; @@ -9,26 +10,29 @@ namespace MiraAPI.Events; /// public static class MiraEventManager { - private static readonly Dictionary> EventHandlers = []; + private static readonly Dictionary> EventWrappers = []; /// /// Invoke an event. /// /// The event instance. /// Type of Event. - public static void InvokeEvent(T eventInstance) where T : MiraEvent + /// If there was an event handler invoked for this event, return true. Otherwise, return false. + public static bool InvokeEvent(T eventInstance) where T : MiraEvent { - EventHandlers.TryGetValue(typeof(T), out var handlers); + EventWrappers.TryGetValue(typeof(T), out var handlers); if (handlers == null) { Logger.Warning("No handlers for event " + typeof(T).Name); - return; + return false; } foreach (var handler in handlers) { - ((Action)handler).Invoke(eventInstance); + ((Action)handler.EventHandler).Invoke(eventInstance); } + + return true; } /// @@ -36,33 +40,48 @@ public static class MiraEventManager /// /// The event instance. /// The type to use for handler lookup. - public static void InvokeEvent(MiraEvent eventInstance, Type type) + /// If there was an event handler invoked for this event, return true. Otherwise, return false. + public static bool InvokeEvent(MiraEvent eventInstance, Type type) { - EventHandlers.TryGetValue(type, out var handlers); + EventWrappers.TryGetValue(type, out var handlers); if (handlers == null) { Logger.Warning("No handlers for event " + type.Name); - return; + return false; } foreach (var handler in handlers) { - handler.DynamicInvoke(eventInstance); + handler.EventHandler.DynamicInvoke(eventInstance); } + + return true; } /// /// Register an event. /// /// The callback method/handler for the event. + /// The priority of the event handler. Higher values are called first. /// Type of event. - public static void RegisterEventHandler(Action handler) where T : MiraEvent + public static void RegisterEventHandler(Action handler, int priority = 0) where T : MiraEvent { - if (!EventHandlers.ContainsKey(typeof(T))) + if (!EventWrappers.ContainsKey(typeof(T))) { - EventHandlers.Add(typeof(T), []); + EventWrappers.Add(typeof(T), []); } - EventHandlers[typeof(T)].Add(handler); + + var handlers = EventWrappers[typeof(T)]; + handlers.Add(new MiraEventWrapper(handler, priority)); + Logger.Info("Registered event handler for " + typeof(T).Name); } + + internal static void SortAllHandlers() + { + foreach (var handlers in EventWrappers.Values) + { + handlers.Sort((a, b) => a.Priority.CompareTo(b.Priority)); + } + } } diff --git a/MiraAPI/Events/MiraEventWrapper.cs b/MiraAPI/Events/MiraEventWrapper.cs new file mode 100644 index 0000000..0251ea9 --- /dev/null +++ b/MiraAPI/Events/MiraEventWrapper.cs @@ -0,0 +1,30 @@ +using System; + +namespace MiraAPI.Events; + +/// +/// Wrapper for event handlers. +/// +public class MiraEventWrapper +{ + /// + /// Gets the event handler delegate. + /// + public Delegate EventHandler { get; } + + /// + /// Gets the priority of the handler. Higher values are called first. + /// + public int Priority { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The action for the event handler. + /// The priority of the handler. + public MiraEventWrapper(Delegate eventHandler, int priority) + { + EventHandler = eventHandler; + Priority = priority; + } +} diff --git a/MiraAPI/Hud/CustomActionButton.cs b/MiraAPI/Hud/CustomActionButton.cs index 970eea3..0c89062 100644 --- a/MiraAPI/Hud/CustomActionButton.cs +++ b/MiraAPI/Hud/CustomActionButton.cs @@ -101,18 +101,38 @@ public abstract class CustomActionButton pb.OnClick = new Button.ButtonClickedEvent(); pb.OnClick.AddListener((UnityAction)(() => { + // Invoke the generic button click event. + var genericEvent = new MiraButtonClickEvent(this); + MiraEventManager.InvokeEvent(genericEvent); + if (genericEvent.IsCancelled) + { + MiraEventManager.InvokeEvent(new MiraButtonCancelledEvent(this)); + } + + // Invoke the button click event for specific button. var eventType = CustomButtonManager.ButtonEventTypes[GetType()]; var @event = (MiraCancelableEvent)Activator.CreateInstance(eventType, this)!; - MiraEventManager.InvokeEvent(@event, eventType); + var specificInvoked = MiraEventManager.InvokeEvent(@event, eventType); if (@event.IsCancelled) { var cancelEventType = CustomButtonManager.ButtonCancelledEventTypes[GetType()]; var cancelEvent = (MiraEvent)Activator.CreateInstance(cancelEventType, this)!; MiraEventManager.InvokeEvent(cancelEvent, cancelEventType); } + + if (specificInvoked) + { + if (!@event.IsCancelled) + { + ClickHandler(); + } + } else { - ClickHandler(); + if (!genericEvent.IsCancelled) + { + ClickHandler(); + } } })); } diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index feeba48..acb6ebe 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using BepInEx.Unity.IL2CPP; using MiraAPI.Colors; +using MiraAPI.Events; using MiraAPI.GameOptions; using MiraAPI.GameOptions.Attributes; using MiraAPI.Hud; @@ -51,6 +52,7 @@ public sealed class MiraPluginManager Logger.Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API."); }; IL2CPPChainloader.Instance.Finished += PaletteManager.RegisterAllColors; + IL2CPPChainloader.Instance.Finished += MiraEventManager.SortAllHandlers; } ///