From 68528f7decadccb4c5ed62f3fff10aeff22dcd43 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 23 Feb 2018 19:05:23 -0500 Subject: overhaul events to track the mod which added each handler, and log errors under their name (#451) --- src/SMAPI/Events/ContentEvents.cs | 28 ++- src/SMAPI/Events/ControlEvents.cs | 123 +++++------- src/SMAPI/Events/GameEvents.cs | 110 +++++------ src/SMAPI/Events/GraphicsEvents.cs | 120 +++++------- src/SMAPI/Events/InputEvents.cs | 46 ++--- src/SMAPI/Events/LocationEvents.cs | 61 +++--- src/SMAPI/Events/MenuEvents.cs | 44 +++-- src/SMAPI/Events/MineEvents.cs | 29 ++- src/SMAPI/Events/PlayerEvents.cs | 45 ++--- src/SMAPI/Events/SaveEvents.cs | 84 ++++----- src/SMAPI/Events/SpecialisedEvents.cs | 25 ++- src/SMAPI/Events/TimeEvents.cs | 41 ++-- src/SMAPI/Framework/Events/EventManager.cs | 249 +++++++++++++++++++++++++ src/SMAPI/Framework/Events/ManagedEvent.cs | 119 ++++++++++++ src/SMAPI/Framework/Events/ManagedEventBase.cs | 81 ++++++++ src/SMAPI/Framework/InternalExtensions.cs | 58 ------ src/SMAPI/Framework/SGame.cs | 142 +++++++------- src/SMAPI/Program.cs | 26 ++- src/SMAPI/StardewModdingAPI.csproj | 3 + 19 files changed, 909 insertions(+), 525 deletions(-) create mode 100644 src/SMAPI/Framework/Events/EventManager.cs create mode 100644 src/SMAPI/Framework/Events/ManagedEvent.cs create mode 100644 src/SMAPI/Framework/Events/ManagedEventBase.cs (limited to 'src/SMAPI') diff --git a/src/SMAPI/Events/ContentEvents.cs b/src/SMAPI/Events/ContentEvents.cs index 4b4e2ad0..63645258 100644 --- a/src/SMAPI/Events/ContentEvents.cs +++ b/src/SMAPI/Events/ContentEvents.cs @@ -1,29 +1,37 @@ -using System; -using StardewModdingAPI.Framework; +using System; +using StardewModdingAPI.Framework.Events; namespace StardewModdingAPI.Events { /// Events raised when the game loads content. public static class ContentEvents { + /********* + ** Properties + *********/ + /// The core event manager. + private static EventManager EventManager; + /********* ** Events *********/ /// Raised after the content language changes. - public static event EventHandler> AfterLocaleChanged; + public static event EventHandler> AfterLocaleChanged + { + add => ContentEvents.EventManager.Content_LocaleChanged.Add(value); + remove => ContentEvents.EventManager.Content_LocaleChanged.Remove(value); + } /********* - ** Internal methods + ** Public methods *********/ - /// Raise an event. - /// Encapsulates monitoring and logging. - /// The previous locale. - /// The current locale. - internal static void InvokeAfterLocaleChanged(IMonitor monitor, string oldLocale, string newLocale) + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) { - monitor.SafelyRaiseGenericEvent($"{nameof(ContentEvents)}.{nameof(ContentEvents.AfterLocaleChanged)}", ContentEvents.AfterLocaleChanged?.GetInvocationList(), null, new EventArgsValueChanged(oldLocale, newLocale)); + ContentEvents.EventManager = eventManager; } } } diff --git a/src/SMAPI/Events/ControlEvents.cs b/src/SMAPI/Events/ControlEvents.cs index 80d0f547..973bb245 100644 --- a/src/SMAPI/Events/ControlEvents.cs +++ b/src/SMAPI/Events/ControlEvents.cs @@ -1,7 +1,6 @@ -using System; -using Microsoft.Xna.Framework; +using System; using Microsoft.Xna.Framework.Input; -using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Events; namespace StardewModdingAPI.Events { @@ -9,104 +8,80 @@ namespace StardewModdingAPI.Events public static class ControlEvents { /********* - ** Events + ** Properties *********/ - /// Raised when the changes. That happens when the player presses or releases a key. - public static event EventHandler KeyboardChanged; - - /// Raised when the player presses a keyboard key. - public static event EventHandler KeyPressed; - - /// Raised when the player releases a keyboard key. - public static event EventHandler KeyReleased; - - /// Raised when the changes. That happens when the player moves the mouse, scrolls the mouse wheel, or presses/releases a button. - public static event EventHandler MouseChanged; - - /// The player pressed a controller button. This event isn't raised for trigger buttons. - public static event EventHandler ControllerButtonPressed; - - /// The player released a controller button. This event isn't raised for trigger buttons. - public static event EventHandler ControllerButtonReleased; - - /// The player pressed a controller trigger button. - public static event EventHandler ControllerTriggerPressed; - - /// The player released a controller trigger button. - public static event EventHandler ControllerTriggerReleased; + /// The core event manager. + private static EventManager EventManager; /********* - ** Internal methods + ** Events *********/ - /// Raise a event. - /// Encapsulates monitoring and logging. - /// The previous keyboard state. - /// The current keyboard state. - internal static void InvokeKeyboardChanged(IMonitor monitor, KeyboardState priorState, KeyboardState newState) + /// Raised when the changes. That happens when the player presses or releases a key. + public static event EventHandler KeyboardChanged { - monitor.SafelyRaiseGenericEvent($"{nameof(ControlEvents)}.{nameof(ControlEvents.KeyboardChanged)}", ControlEvents.KeyboardChanged?.GetInvocationList(), null, new EventArgsKeyboardStateChanged(priorState, newState)); + add => ControlEvents.EventManager.Control_KeyboardChanged.Add(value); + remove => ControlEvents.EventManager.Control_KeyboardChanged.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - /// The previous mouse state. - /// The current mouse state. - /// The previous mouse position on the screen adjusted for the zoom level. - /// The current mouse position on the screen adjusted for the zoom level. - internal static void InvokeMouseChanged(IMonitor monitor, MouseState priorState, MouseState newState, Point priorPosition, Point newPosition) + /// Raised when the player presses a keyboard key. + public static event EventHandler KeyPressed { - monitor.SafelyRaiseGenericEvent($"{nameof(ControlEvents)}.{nameof(ControlEvents.MouseChanged)}", ControlEvents.MouseChanged?.GetInvocationList(), null, new EventArgsMouseStateChanged(priorState, newState, priorPosition, newPosition)); + add => ControlEvents.EventManager.Control_KeyPressed.Add(value); + remove => ControlEvents.EventManager.Control_KeyPressed.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - /// The keyboard button that was pressed. - internal static void InvokeKeyPressed(IMonitor monitor, Keys key) + /// Raised when the player releases a keyboard key. + public static event EventHandler KeyReleased { - monitor.SafelyRaiseGenericEvent($"{nameof(ControlEvents)}.{nameof(ControlEvents.KeyPressed)}", ControlEvents.KeyPressed?.GetInvocationList(), null, new EventArgsKeyPressed(key)); + add => ControlEvents.EventManager.Control_KeyReleased.Add(value); + remove => ControlEvents.EventManager.Control_KeyReleased.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - /// The keyboard button that was released. - internal static void InvokeKeyReleased(IMonitor monitor, Keys key) + /// Raised when the changes. That happens when the player moves the mouse, scrolls the mouse wheel, or presses/releases a button. + public static event EventHandler MouseChanged { - monitor.SafelyRaiseGenericEvent($"{nameof(ControlEvents)}.{nameof(ControlEvents.KeyReleased)}", ControlEvents.KeyReleased?.GetInvocationList(), null, new EventArgsKeyPressed(key)); + add => ControlEvents.EventManager.Control_MouseChanged.Add(value); + remove => ControlEvents.EventManager.Control_MouseChanged.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - /// The controller button that was pressed. - internal static void InvokeButtonPressed(IMonitor monitor, Buttons button) + /// The player pressed a controller button. This event isn't raised for trigger buttons. + public static event EventHandler ControllerButtonPressed { - monitor.SafelyRaiseGenericEvent($"{nameof(ControlEvents)}.{nameof(ControlEvents.ControllerButtonPressed)}", ControlEvents.ControllerButtonPressed?.GetInvocationList(), null, new EventArgsControllerButtonPressed(PlayerIndex.One, button)); + add => ControlEvents.EventManager.Control_ControllerButtonPressed.Add(value); + remove => ControlEvents.EventManager.Control_ControllerButtonPressed.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - /// The controller button that was released. - internal static void InvokeButtonReleased(IMonitor monitor, Buttons button) + /// The player released a controller button. This event isn't raised for trigger buttons. + public static event EventHandler ControllerButtonReleased { - monitor.SafelyRaiseGenericEvent($"{nameof(ControlEvents)}.{nameof(ControlEvents.ControllerButtonReleased)}", ControlEvents.ControllerButtonReleased?.GetInvocationList(), null, new EventArgsControllerButtonReleased(PlayerIndex.One, button)); + add => ControlEvents.EventManager.Control_ControllerButtonReleased.Add(value); + remove => ControlEvents.EventManager.Control_ControllerButtonReleased.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - /// The trigger button that was pressed. - /// The current trigger value. - internal static void InvokeTriggerPressed(IMonitor monitor, Buttons button, float value) + /// The player pressed a controller trigger button. + public static event EventHandler ControllerTriggerPressed { - monitor.SafelyRaiseGenericEvent($"{nameof(ControlEvents)}.{nameof(ControlEvents.ControllerTriggerPressed)}", ControlEvents.ControllerTriggerPressed?.GetInvocationList(), null, new EventArgsControllerTriggerPressed(PlayerIndex.One, button, value)); + add => ControlEvents.EventManager.Control_ControllerTriggerPressed.Add(value); + remove => ControlEvents.EventManager.Control_ControllerTriggerPressed.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - /// The trigger button that was pressed. - /// The current trigger value. - internal static void InvokeTriggerReleased(IMonitor monitor, Buttons button, float value) + /// The player released a controller trigger button. + public static event EventHandler ControllerTriggerReleased + { + add => ControlEvents.EventManager.Control_ControllerTriggerReleased.Add(value); + remove => ControlEvents.EventManager.Control_ControllerTriggerReleased.Remove(value); + } + + + /********* + ** Public methods + *********/ + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) { - monitor.SafelyRaiseGenericEvent($"{nameof(ControlEvents)}.{nameof(ControlEvents.ControllerTriggerReleased)}", ControlEvents.ControllerTriggerReleased?.GetInvocationList(), null, new EventArgsControllerTriggerReleased(PlayerIndex.One, button, value)); + ControlEvents.EventManager = eventManager; } } } diff --git a/src/SMAPI/Events/GameEvents.cs b/src/SMAPI/Events/GameEvents.cs index 3466470d..92879280 100644 --- a/src/SMAPI/Events/GameEvents.cs +++ b/src/SMAPI/Events/GameEvents.cs @@ -1,5 +1,5 @@ using System; -using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Events; namespace StardewModdingAPI.Events { @@ -7,100 +7,80 @@ namespace StardewModdingAPI.Events public static class GameEvents { /********* - ** Events + ** Properties *********/ - /// Raised during launch after configuring XNA or MonoGame. The game window hasn't been opened by this point. Called after . - internal static event EventHandler InitializeInternal; - - /// Raised when the game updates its state (≈60 times per second). - public static event EventHandler UpdateTick; - - /// Raised every other tick (≈30 times per second). - public static event EventHandler SecondUpdateTick; - - /// Raised every fourth tick (≈15 times per second). - public static event EventHandler FourthUpdateTick; - - /// Raised every eighth tick (≈8 times per second). - public static event EventHandler EighthUpdateTick; - - /// Raised every 15th tick (≈4 times per second). - public static event EventHandler QuarterSecondTick; - - /// Raised every 30th tick (≈twice per second). - public static event EventHandler HalfSecondTick; - - /// Raised every 60th tick (≈once per second). - public static event EventHandler OneSecondTick; - - /// Raised once after the game initialises and all methods have been called. - public static event EventHandler FirstUpdateTick; + /// The core event manager. + private static EventManager EventManager; /********* - ** Internal methods + ** Events *********/ - /// Raise an event. - /// Encapsulates logging and monitoring. - internal static void InvokeInitialize(IMonitor monitor) + /// Raised when the game updates its state (≈60 times per second). + public static event EventHandler UpdateTick { - monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.InitializeInternal)}", GameEvents.InitializeInternal?.GetInvocationList()); + add => GameEvents.EventManager.Game_UpdateTick.Add(value); + remove => GameEvents.EventManager.Game_UpdateTick.Remove(value); } - /// Raise an event. - /// Encapsulates logging and monitoring. - internal static void InvokeUpdateTick(IMonitor monitor) + /// Raised every other tick (≈30 times per second). + public static event EventHandler SecondUpdateTick { - monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.UpdateTick)}", GameEvents.UpdateTick?.GetInvocationList()); + add => GameEvents.EventManager.Game_SecondUpdateTick.Add(value); + remove => GameEvents.EventManager.Game_SecondUpdateTick.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - internal static void InvokeSecondUpdateTick(IMonitor monitor) + /// Raised every fourth tick (≈15 times per second). + public static event EventHandler FourthUpdateTick { - monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.SecondUpdateTick)}", GameEvents.SecondUpdateTick?.GetInvocationList()); + add => GameEvents.EventManager.Game_FourthUpdateTick.Add(value); + remove => GameEvents.EventManager.Game_FourthUpdateTick.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - internal static void InvokeFourthUpdateTick(IMonitor monitor) + /// Raised every eighth tick (≈8 times per second). + public static event EventHandler EighthUpdateTick { - monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.FourthUpdateTick)}", GameEvents.FourthUpdateTick?.GetInvocationList()); + add => GameEvents.EventManager.Game_EighthUpdateTick.Add(value); + remove => GameEvents.EventManager.Game_EighthUpdateTick.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - internal static void InvokeEighthUpdateTick(IMonitor monitor) + /// Raised every 15th tick (≈4 times per second). + public static event EventHandler QuarterSecondTick { - monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.EighthUpdateTick)}", GameEvents.EighthUpdateTick?.GetInvocationList()); + add => GameEvents.EventManager.Game_QuarterSecondTick.Add(value); + remove => GameEvents.EventManager.Game_QuarterSecondTick.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - internal static void InvokeQuarterSecondTick(IMonitor monitor) + /// Raised every 30th tick (≈twice per second). + public static event EventHandler HalfSecondTick { - monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.QuarterSecondTick)}", GameEvents.QuarterSecondTick?.GetInvocationList()); + add => GameEvents.EventManager.Game_HalfSecondTick.Add(value); + remove => GameEvents.EventManager.Game_HalfSecondTick.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - internal static void InvokeHalfSecondTick(IMonitor monitor) + /// Raised every 60th tick (≈once per second). + public static event EventHandler OneSecondTick { - monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.HalfSecondTick)}", GameEvents.HalfSecondTick?.GetInvocationList()); + add => GameEvents.EventManager.Game_OneSecondTick.Add(value); + remove => GameEvents.EventManager.Game_OneSecondTick.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - internal static void InvokeOneSecondTick(IMonitor monitor) + /// Raised once after the game initialises and all methods have been called. + public static event EventHandler FirstUpdateTick { - monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.OneSecondTick)}", GameEvents.OneSecondTick?.GetInvocationList()); + add => GameEvents.EventManager.Game_FirstUpdateTick.Add(value); + remove => GameEvents.EventManager.Game_FirstUpdateTick.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - internal static void InvokeFirstUpdateTick(IMonitor monitor) + + /********* + ** Public methods + *********/ + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) { - monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.FirstUpdateTick)}", GameEvents.FirstUpdateTick?.GetInvocationList()); + GameEvents.EventManager = eventManager; } } } diff --git a/src/SMAPI/Events/GraphicsEvents.cs b/src/SMAPI/Events/GraphicsEvents.cs index fff51bed..e1ff4ee7 100644 --- a/src/SMAPI/Events/GraphicsEvents.cs +++ b/src/SMAPI/Events/GraphicsEvents.cs @@ -1,116 +1,88 @@ -using System; -using StardewModdingAPI.Framework; +using System; +using StardewModdingAPI.Framework.Events; namespace StardewModdingAPI.Events { /// Events raised during the game's draw loop, when the game is rendering content to the window. public static class GraphicsEvents { + /********* + ** Properties + *********/ + /// The core event manager. + private static EventManager EventManager; + + /********* ** Events *********/ - /**** - ** Generic events - ****/ /// Raised after the game window is resized. - public static event EventHandler Resize; + public static event EventHandler Resize + { + add => GraphicsEvents.EventManager.Graphics_Resize.Add(value); + remove => GraphicsEvents.EventManager.Graphics_Resize.Remove(value); + } /**** ** Main render events ****/ /// Raised before drawing the world to the screen. - public static event EventHandler OnPreRenderEvent; + public static event EventHandler OnPreRenderEvent + { + add => GraphicsEvents.EventManager.Graphics_OnPreRenderEvent.Add(value); + remove => GraphicsEvents.EventManager.Graphics_OnPreRenderEvent.Remove(value); + } /// Raised after drawing the world to the screen. - public static event EventHandler OnPostRenderEvent; - - /**** - ** HUD events - ****/ - /// Raised before drawing the HUD (item toolbar, clock, etc) to the screen. The HUD is available at this point, but not necessarily visible. (For example, the event is raised even if a menu is open.) - public static event EventHandler OnPreRenderHudEvent; - - /// Raised after drawing the HUD (item toolbar, clock, etc) to the screen. The HUD is available at this point, but not necessarily visible. (For example, the event is raised even if a menu is open.) - public static event EventHandler OnPostRenderHudEvent; - - /**** - ** GUI events - ****/ - /// Raised before drawing a menu to the screen during a draw loop. This includes the game's internal menus like the title screen. - public static event EventHandler OnPreRenderGuiEvent; - - /// Raised after drawing a menu to the screen during a draw loop. This includes the game's internal menus like the title screen. - public static event EventHandler OnPostRenderGuiEvent; - - - /********* - ** Internal methods - *********/ - /**** - ** Generic events - ****/ - /// Raise a event. - /// Encapsulates monitoring and logging. - internal static void InvokeResize(IMonitor monitor) + public static event EventHandler OnPostRenderEvent { - monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.Resize)}", GraphicsEvents.Resize?.GetInvocationList()); + add => GraphicsEvents.EventManager.Graphics_OnPostRenderEvent.Add(value); + remove => GraphicsEvents.EventManager.Graphics_OnPostRenderEvent.Remove(value); } /**** - ** Main render events + ** HUD events ****/ - /// Raise an event. - /// Encapsulates monitoring and logging. - internal static void InvokeOnPreRenderEvent(IMonitor monitor) - { - monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.OnPreRenderEvent)}", GraphicsEvents.OnPreRenderEvent?.GetInvocationList()); - } - - /// Raise an event. - /// Encapsulates monitoring and logging. - internal static void InvokeOnPostRenderEvent(IMonitor monitor) + /// Raised before drawing the HUD (item toolbar, clock, etc) to the screen. The HUD is available at this point, but not necessarily visible. (For example, the event is raised even if a menu is open.) + public static event EventHandler OnPreRenderHudEvent { - monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.OnPostRenderEvent)}", GraphicsEvents.OnPostRenderEvent?.GetInvocationList()); + add => GraphicsEvents.EventManager.Graphics_OnPreRenderHudEvent.Add(value); + remove => GraphicsEvents.EventManager.Graphics_OnPreRenderHudEvent.Remove(value); } - /// Get whether there are any post-render event listeners. - internal static bool HasPostRenderListeners() + /// Raised after drawing the HUD (item toolbar, clock, etc) to the screen. The HUD is available at this point, but not necessarily visible. (For example, the event is raised even if a menu is open.) + public static event EventHandler OnPostRenderHudEvent { - return GraphicsEvents.OnPostRenderEvent != null; + add => GraphicsEvents.EventManager.Graphics_OnPostRenderHudEvent.Add(value); + remove => GraphicsEvents.EventManager.Graphics_OnPostRenderHudEvent.Remove(value); } /**** ** GUI events ****/ - /// Raise an event. - /// Encapsulates monitoring and logging. - internal static void InvokeOnPreRenderGuiEvent(IMonitor monitor) + /// Raised before drawing a menu to the screen during a draw loop. This includes the game's internal menus like the title screen. + public static event EventHandler OnPreRenderGuiEvent { - monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.OnPreRenderGuiEvent)}", GraphicsEvents.OnPreRenderGuiEvent?.GetInvocationList()); + add => GraphicsEvents.EventManager.Graphics_OnPreRenderGuiEvent.Add(value); + remove => GraphicsEvents.EventManager.Graphics_OnPreRenderGuiEvent.Remove(value); } - /// Raise an event. - /// Encapsulates monitoring and logging. - internal static void InvokeOnPostRenderGuiEvent(IMonitor monitor) + /// Raised after drawing a menu to the screen during a draw loop. This includes the game's internal menus like the title screen. + public static event EventHandler OnPostRenderGuiEvent { - monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.OnPostRenderGuiEvent)}", GraphicsEvents.OnPostRenderGuiEvent?.GetInvocationList()); + add => GraphicsEvents.EventManager.Graphics_OnPostRenderGuiEvent.Add(value); + remove => GraphicsEvents.EventManager.Graphics_OnPostRenderGuiEvent.Remove(value); } - /**** - ** HUD events - ****/ - /// Raise an event. - /// Encapsulates monitoring and logging. - internal static void InvokeOnPreRenderHudEvent(IMonitor monitor) - { - monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.OnPreRenderHudEvent)}", GraphicsEvents.OnPreRenderHudEvent?.GetInvocationList()); - } - /// Raise an event. - /// Encapsulates monitoring and logging. - internal static void InvokeOnPostRenderHudEvent(IMonitor monitor) + /********* + ** Public methods + *********/ + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) { - monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.OnPostRenderHudEvent)}", GraphicsEvents.OnPostRenderHudEvent?.GetInvocationList()); + GraphicsEvents.EventManager = eventManager; } } } diff --git a/src/SMAPI/Events/InputEvents.cs b/src/SMAPI/Events/InputEvents.cs index 985aed99..84d7ce5d 100644 --- a/src/SMAPI/Events/InputEvents.cs +++ b/src/SMAPI/Events/InputEvents.cs @@ -1,44 +1,44 @@ using System; -using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Events; namespace StardewModdingAPI.Events { /// Events raised when the player uses a controller, keyboard, or mouse button. public static class InputEvents { + /********* + ** Properties + *********/ + /// The core event manager. + private static EventManager EventManager; + + /********* ** Events *********/ /// Raised when the player presses a button on the keyboard, controller, or mouse. - public static event EventHandler ButtonPressed; + public static event EventHandler ButtonPressed + { + add => InputEvents.EventManager.Input_ButtonPressed.Add(value); + remove => InputEvents.EventManager.Input_ButtonPressed.Remove(value); + } /// Raised when the player releases a keyboard key on the keyboard, controller, or mouse. - public static event EventHandler ButtonReleased; + public static event EventHandler ButtonReleased + { + add => InputEvents.EventManager.Input_ButtonReleased.Add(value); + remove => InputEvents.EventManager.Input_ButtonReleased.Remove(value); + } /********* - ** Internal methods + ** Public methods *********/ - /// Raise a event. - /// Encapsulates monitoring and logging. - /// The button on the controller, keyboard, or mouse. - /// The cursor position. - /// Whether the input should trigger actions on the affected tile. - /// Whether the input should use tools on the affected tile. - internal static void InvokeButtonPressed(IMonitor monitor, SButton button, ICursorPosition cursor, bool isActionButton, bool isUseToolButton) - { - monitor.SafelyRaiseGenericEvent($"{nameof(InputEvents)}.{nameof(InputEvents.ButtonPressed)}", InputEvents.ButtonPressed?.GetInvocationList(), null, new EventArgsInput(button, cursor, isActionButton, isUseToolButton)); - } - - /// Raise a event. - /// Encapsulates monitoring and logging. - /// The button on the controller, keyboard, or mouse. - /// The cursor position. - /// Whether the input should trigger actions on the affected tile. - /// Whether the input should use tools on the affected tile. - internal static void InvokeButtonReleased(IMonitor monitor, SButton button, ICursorPosition cursor, bool isActionButton, bool isUseToolButton) + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) { - monitor.SafelyRaiseGenericEvent($"{nameof(InputEvents)}.{nameof(InputEvents.ButtonReleased)}", InputEvents.ButtonReleased?.GetInvocationList(), null, new EventArgsInput(button, cursor, isActionButton, isUseToolButton)); + InputEvents.EventManager = eventManager; } } } diff --git a/src/SMAPI/Events/LocationEvents.cs b/src/SMAPI/Events/LocationEvents.cs index b834bc1c..81d13e9f 100644 --- a/src/SMAPI/Events/LocationEvents.cs +++ b/src/SMAPI/Events/LocationEvents.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; -using Microsoft.Xna.Framework; -using StardewModdingAPI.Framework; -using StardewValley; -using Object = StardewValley.Object; +using System; +using StardewModdingAPI.Framework.Events; namespace StardewModdingAPI.Events { @@ -11,44 +7,45 @@ namespace StardewModdingAPI.Events public static class LocationEvents { /********* - ** Events + ** Properties *********/ - /// Raised after the player warps to a new location. - public static event EventHandler CurrentLocationChanged; - - /// Raised after a game location is added or removed. - public static event EventHandler LocationsChanged; - - /// Raised after the list of objects in the current location changes (e.g. an object is added or removed). - public static event EventHandler LocationObjectsChanged; + /// The core event manager. + private static EventManager EventManager; /********* - ** Internal methods + ** Events *********/ - /// Raise a event. - /// Encapsulates monitoring and logging. - /// The player's previous location. - /// The player's current location. - internal static void InvokeCurrentLocationChanged(IMonitor monitor, GameLocation priorLocation, GameLocation newLocation) + /// Raised after the player warps to a new location. + public static event EventHandler CurrentLocationChanged { - monitor.SafelyRaiseGenericEvent($"{nameof(LocationEvents)}.{nameof(LocationEvents.CurrentLocationChanged)}", LocationEvents.CurrentLocationChanged?.GetInvocationList(), null, new EventArgsCurrentLocationChanged(priorLocation, newLocation)); + add => LocationEvents.EventManager.Location_CurrentLocationChanged.Add(value); + remove => LocationEvents.EventManager.Location_CurrentLocationChanged.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - /// The current list of game locations. - internal static void InvokeLocationsChanged(IMonitor monitor, List newLocations) + /// Raised after a game location is added or removed. + public static event EventHandler LocationsChanged + { + add => LocationEvents.EventManager.Location_LocationsChanged.Add(value); + remove => LocationEvents.EventManager.Location_LocationsChanged.Remove(value); + } + + /// Raised after the list of objects in the current location changes (e.g. an object is added or removed). + public static event EventHandler LocationObjectsChanged { - monitor.SafelyRaiseGenericEvent($"{nameof(LocationEvents)}.{nameof(LocationEvents.LocationsChanged)}", LocationEvents.LocationsChanged?.GetInvocationList(), null, new EventArgsGameLocationsChanged(newLocations)); + add => LocationEvents.EventManager.Location_LocationObjectsChanged.Add(value); + remove => LocationEvents.EventManager.Location_LocationObjectsChanged.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - /// The current list of objects in the current location. - internal static void InvokeOnNewLocationObject(IMonitor monitor, SerializableDictionary newObjects) + + /********* + ** Public methods + *********/ + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) { - monitor.SafelyRaiseGenericEvent($"{nameof(LocationEvents)}.{nameof(LocationEvents.LocationObjectsChanged)}", LocationEvents.LocationObjectsChanged?.GetInvocationList(), null, new EventArgsLocationObjectsChanged(newObjects)); + LocationEvents.EventManager = eventManager; } } } diff --git a/src/SMAPI/Events/MenuEvents.cs b/src/SMAPI/Events/MenuEvents.cs index bd8d897e..7fcc3844 100644 --- a/src/SMAPI/Events/MenuEvents.cs +++ b/src/SMAPI/Events/MenuEvents.cs @@ -1,40 +1,44 @@ -using System; -using StardewModdingAPI.Framework; -using StardewValley.Menus; +using System; +using StardewModdingAPI.Framework.Events; namespace StardewModdingAPI.Events { /// Events raised when a game menu is opened or closed (including internal menus like the title screen). public static class MenuEvents { + /********* + ** Properties + *********/ + /// The core event manager. + private static EventManager EventManager; + + /********* ** Events *********/ /// Raised after a game menu is opened or replaced with another menu. This event is not invoked when a menu is closed. - public static event EventHandler MenuChanged; + public static event EventHandler MenuChanged + { + add => MenuEvents.EventManager.Menu_Changed.Add(value); + remove => MenuEvents.EventManager.Menu_Changed.Remove(value); + } /// Raised after a game menu is closed. - public static event EventHandler MenuClosed; + public static event EventHandler MenuClosed + { + add => MenuEvents.EventManager.Menu_Closed.Add(value); + remove => MenuEvents.EventManager.Menu_Closed.Remove(value); + } /********* - ** Internal methods + ** Public methods *********/ - /// Raise a event. - /// Encapsulates monitoring and logging. - /// The previous menu. - /// The current menu. - internal static void InvokeMenuChanged(IMonitor monitor, IClickableMenu priorMenu, IClickableMenu newMenu) - { - monitor.SafelyRaiseGenericEvent($"{nameof(MenuEvents)}.{nameof(MenuEvents.MenuChanged)}", MenuEvents.MenuChanged?.GetInvocationList(), null, new EventArgsClickableMenuChanged(priorMenu, newMenu)); - } - - /// Raise a event. - /// Encapsulates monitoring and logging. - /// The menu that was closed. - internal static void InvokeMenuClosed(IMonitor monitor, IClickableMenu priorMenu) + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) { - monitor.SafelyRaiseGenericEvent($"{nameof(MenuEvents)}.{nameof(MenuEvents.MenuClosed)}", MenuEvents.MenuClosed?.GetInvocationList(), null, new EventArgsClickableMenuClosed(priorMenu)); + MenuEvents.EventManager = eventManager; } } } diff --git a/src/SMAPI/Events/MineEvents.cs b/src/SMAPI/Events/MineEvents.cs index 9cf7edac..5ee4001b 100644 --- a/src/SMAPI/Events/MineEvents.cs +++ b/src/SMAPI/Events/MineEvents.cs @@ -1,28 +1,37 @@ -using System; -using StardewModdingAPI.Framework; +using System; +using StardewModdingAPI.Framework.Events; namespace StardewModdingAPI.Events { /// Events raised when something happens in the mines. public static class MineEvents { + /********* + ** Properties + *********/ + /// The core event manager. + private static EventManager EventManager; + + /********* ** Events *********/ /// Raised after the player warps to a new level of the mine. - public static event EventHandler MineLevelChanged; + public static event EventHandler MineLevelChanged + { + add => MineEvents.EventManager.Mine_LevelChanged.Add(value); + remove => MineEvents.EventManager.Mine_LevelChanged.Remove(value); + } /********* - ** Internal methods + ** Public methods *********/ - /// Raise a event. - /// Encapsulates monitoring and logging. - /// The previous mine level. - /// The current mine level. - internal static void InvokeMineLevelChanged(IMonitor monitor, int previousMineLevel, int currentMineLevel) + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) { - monitor.SafelyRaiseGenericEvent($"{nameof(MineEvents)}.{nameof(MineEvents.MineLevelChanged)}", MineEvents.MineLevelChanged?.GetInvocationList(), null, new EventArgsMineLevelChanged(previousMineLevel, currentMineLevel)); + MineEvents.EventManager = eventManager; } } } diff --git a/src/SMAPI/Events/PlayerEvents.cs b/src/SMAPI/Events/PlayerEvents.cs index 5a9a9d5f..84a7ff63 100644 --- a/src/SMAPI/Events/PlayerEvents.cs +++ b/src/SMAPI/Events/PlayerEvents.cs @@ -1,43 +1,44 @@ using System; -using System.Collections.Generic; -using System.Linq; -using StardewModdingAPI.Framework; -using StardewValley; +using StardewModdingAPI.Framework.Events; namespace StardewModdingAPI.Events { /// Events raised when the player data changes. public static class PlayerEvents { + /********* + ** Properties + *********/ + /// The core event manager. + private static EventManager EventManager; + + /********* ** Events *********/ /// Raised after the player's inventory changes in any way (added or removed item, sorted, etc). - public static event EventHandler InventoryChanged; + public static event EventHandler InventoryChanged + { + add => PlayerEvents.EventManager.Player_InventoryChanged.Add(value); + remove => PlayerEvents.EventManager.Player_InventoryChanged.Remove(value); + } /// Raised after the player levels up a skill. This happens as soon as they level up, not when the game notifies the player after their character goes to bed. - public static event EventHandler LeveledUp; + public static event EventHandler LeveledUp + { + add => PlayerEvents.EventManager.Player_LeveledUp.Add(value); + remove => PlayerEvents.EventManager.Player_LeveledUp.Remove(value); + } /********* - ** Internal methods + ** Public methods *********/ - /// Raise an event. - /// Encapsulates monitoring and logging. - /// The player's inventory. - /// The inventory changes. - internal static void InvokeInventoryChanged(IMonitor monitor, List inventory, IEnumerable changedItems) - { - monitor.SafelyRaiseGenericEvent($"{nameof(PlayerEvents)}.{nameof(PlayerEvents.InventoryChanged)}", PlayerEvents.InventoryChanged?.GetInvocationList(), null, new EventArgsInventoryChanged(inventory, changedItems.ToList())); - } - - /// Rase a event. - /// Encapsulates monitoring and logging. - /// The player skill that leveled up. - /// The new skill level. - internal static void InvokeLeveledUp(IMonitor monitor, EventArgsLevelUp.LevelType type, int newLevel) + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) { - monitor.SafelyRaiseGenericEvent($"{nameof(PlayerEvents)}.{nameof(PlayerEvents.LeveledUp)}", PlayerEvents.LeveledUp?.GetInvocationList(), null, new EventArgsLevelUp(type, newLevel)); + PlayerEvents.EventManager = eventManager; } } } diff --git a/src/SMAPI/Events/SaveEvents.cs b/src/SMAPI/Events/SaveEvents.cs index 99b6c8d2..62184282 100644 --- a/src/SMAPI/Events/SaveEvents.cs +++ b/src/SMAPI/Events/SaveEvents.cs @@ -1,5 +1,5 @@ using System; -using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Events; namespace StardewModdingAPI.Events { @@ -7,70 +7,66 @@ namespace StardewModdingAPI.Events public static class SaveEvents { /********* - ** Events + ** Properties *********/ - /// Raised before the game creates the save file. - public static event EventHandler BeforeCreate; - - /// Raised after the game finishes creating the save file. - public static event EventHandler AfterCreate; - - /// Raised before the game begins writes data to the save file. - public static event EventHandler BeforeSave; - - /// Raised after the game finishes writing data to the save file. - public static event EventHandler AfterSave; - - /// Raised after the player loads a save slot. - public static event EventHandler AfterLoad; - - /// Raised after the game returns to the title screen. - public static event EventHandler AfterReturnToTitle; + /// The core event manager. + private static EventManager EventManager; /********* - ** Internal methods + ** Events *********/ - /// Raise a event. - /// Encapsulates monitoring and logging. - internal static void InvokeBeforeCreate(IMonitor monitor) + /// Raised before the game creates the save file. + public static event EventHandler BeforeCreate + { + add => SaveEvents.EventManager.Save_BeforeCreate.Add(value); + remove => SaveEvents.EventManager.Save_BeforeCreate.Remove(value); + } + + /// Raised after the game finishes creating the save file. + public static event EventHandler AfterCreate { - monitor.SafelyRaisePlainEvent($"{nameof(SaveEvents)}.{nameof(SaveEvents.BeforeCreate)}", SaveEvents.BeforeCreate?.GetInvocationList(), null, EventArgs.Empty); + add => SaveEvents.EventManager.Save_AfterCreate.Add(value); + remove => SaveEvents.EventManager.Save_AfterCreate.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - internal static void InvokeAfterCreated(IMonitor monitor) + /// Raised before the game begins writes data to the save file. + public static event EventHandler BeforeSave { - monitor.SafelyRaisePlainEvent($"{nameof(SaveEvents)}.{nameof(SaveEvents.AfterCreate)}", SaveEvents.AfterCreate?.GetInvocationList(), null, EventArgs.Empty); + add => SaveEvents.EventManager.Save_BeforeSave.Add(value); + remove => SaveEvents.EventManager.Save_BeforeSave.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - internal static void InvokeBeforeSave(IMonitor monitor) + /// Raised after the game finishes writing data to the save file. + public static event EventHandler AfterSave { - monitor.SafelyRaisePlainEvent($"{nameof(SaveEvents)}.{nameof(SaveEvents.BeforeSave)}", SaveEvents.BeforeSave?.GetInvocationList(), null, EventArgs.Empty); + add => SaveEvents.EventManager.Save_AfterSave.Add(value); + remove => SaveEvents.EventManager.Save_AfterSave.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - internal static void InvokeAfterSave(IMonitor monitor) + /// Raised after the player loads a save slot. + public static event EventHandler AfterLoad { - monitor.SafelyRaisePlainEvent($"{nameof(SaveEvents)}.{nameof(SaveEvents.AfterSave)}", SaveEvents.AfterSave?.GetInvocationList(), null, EventArgs.Empty); + add => SaveEvents.EventManager.Save_AfterLoad.Add(value); + remove => SaveEvents.EventManager.Save_AfterLoad.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - internal static void InvokeAfterLoad(IMonitor monitor) + /// Raised after the game returns to the title screen. + public static event EventHandler AfterReturnToTitle { - monitor.SafelyRaisePlainEvent($"{nameof(SaveEvents)}.{nameof(SaveEvents.AfterLoad)}", SaveEvents.AfterLoad?.GetInvocationList(), null, EventArgs.Empty); + add => SaveEvents.EventManager.Save_AfterReturnToTitle.Add(value); + remove => SaveEvents.EventManager.Save_AfterReturnToTitle.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - internal static void InvokeAfterReturnToTitle(IMonitor monitor) + + /********* + ** Public methods + *********/ + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) { - monitor.SafelyRaisePlainEvent($"{nameof(SaveEvents)}.{nameof(SaveEvents.AfterReturnToTitle)}", SaveEvents.AfterReturnToTitle?.GetInvocationList(), null, EventArgs.Empty); + SaveEvents.EventManager = eventManager; } } } diff --git a/src/SMAPI/Events/SpecialisedEvents.cs b/src/SMAPI/Events/SpecialisedEvents.cs index 2a36e6e4..33ebf3b2 100644 --- a/src/SMAPI/Events/SpecialisedEvents.cs +++ b/src/SMAPI/Events/SpecialisedEvents.cs @@ -1,26 +1,37 @@ using System; -using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Events; namespace StardewModdingAPI.Events { /// Events serving specialised edge cases that shouldn't be used by most mod. public static class SpecialisedEvents { + /********* + ** Properties + *********/ + /// The core event manager. + private static EventManager EventManager; + + /********* ** Events *********/ /// Raised when the game updates its state (≈60 times per second), regardless of normal SMAPI validation. This event is not thread-safe and may be invoked while game logic is running asynchronously. Changes to game state in this method may crash the game or corrupt an in-progress save. Do not use this event unless you're fully aware of the context in which your code will be run. Mods using this method will trigger a stability warning in the SMAPI console. - public static event EventHandler UnvalidatedUpdateTick; + public static event EventHandler UnvalidatedUpdateTick + { + add => SpecialisedEvents.EventManager.Specialised_UnvalidatedUpdateTick.Add(value); + remove => SpecialisedEvents.EventManager.Specialised_UnvalidatedUpdateTick.Remove(value); + } /********* - ** Internal methods + ** Public methods *********/ - /// Raise an event. - /// Encapsulates logging and monitoring. - internal static void InvokeUnvalidatedUpdateTick(IMonitor monitor) + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) { - monitor.SafelyRaisePlainEvent($"{nameof(SpecialisedEvents)}.{nameof(SpecialisedEvents.UnvalidatedUpdateTick)}", SpecialisedEvents.UnvalidatedUpdateTick?.GetInvocationList()); + SpecialisedEvents.EventManager = eventManager; } } } diff --git a/src/SMAPI/Events/TimeEvents.cs b/src/SMAPI/Events/TimeEvents.cs index 9aea5e04..f769fd08 100644 --- a/src/SMAPI/Events/TimeEvents.cs +++ b/src/SMAPI/Events/TimeEvents.cs @@ -1,5 +1,5 @@ using System; -using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Events; namespace StardewModdingAPI.Events { @@ -7,31 +7,38 @@ namespace StardewModdingAPI.Events public static class TimeEvents { /********* - ** Events + ** Properties *********/ - /// Raised after the game begins a new day, including when loading a save. - public static event EventHandler AfterDayStarted; + /// The core event manager. + private static EventManager EventManager; - /// Raised after the in-game clock changes. - public static event EventHandler TimeOfDayChanged; /********* - ** Internal methods + ** Events *********/ - /// Raise an event. - /// Encapsulates monitoring and logging. - internal static void InvokeAfterDayStarted(IMonitor monitor) + /// Raised after the game begins a new day, including when loading a save. + public static event EventHandler AfterDayStarted + { + add => TimeEvents.EventManager.Time_AfterDayStarted.Add(value); + remove => TimeEvents.EventManager.Time_AfterDayStarted.Remove(value); + } + + /// Raised after the in-game clock changes. + public static event EventHandler TimeOfDayChanged { - monitor.SafelyRaisePlainEvent($"{nameof(TimeEvents)}.{nameof(TimeEvents.AfterDayStarted)}", TimeEvents.AfterDayStarted?.GetInvocationList(), null, EventArgs.Empty); + add => TimeEvents.EventManager.Time_TimeOfDayChanged.Add(value); + remove => TimeEvents.EventManager.Time_TimeOfDayChanged.Remove(value); } - /// Raise a event. - /// Encapsulates monitoring and logging. - /// The previous time in military time format (e.g. 6:00pm is 1800). - /// The current time in military time format (e.g. 6:10pm is 1810). - internal static void InvokeTimeOfDayChanged(IMonitor monitor, int priorTime, int newTime) + + /********* + ** Public methods + *********/ + /// Initialise the events. + /// The core event manager. + internal static void Init(EventManager eventManager) { - monitor.SafelyRaiseGenericEvent($"{nameof(TimeEvents)}.{nameof(TimeEvents.TimeOfDayChanged)}", TimeEvents.TimeOfDayChanged?.GetInvocationList(), null, new EventArgsIntChanged(priorTime, newTime)); + TimeEvents.EventManager = eventManager; } } } diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs new file mode 100644 index 00000000..d7c89a76 --- /dev/null +++ b/src/SMAPI/Framework/Events/EventManager.cs @@ -0,0 +1,249 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.Xna.Framework.Input; +using StardewModdingAPI.Events; + +namespace StardewModdingAPI.Framework.Events +{ + /// Manages SMAPI events. + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Private fields are deliberately named to simplify organisation.")] + internal class EventManager + { + /********* + ** Properties + *********/ + /**** + ** ContentEvents + ****/ + /// Raised after the content language changes. + public readonly ManagedEvent> Content_LocaleChanged; + + /**** + ** ControlEvents + ****/ + /// Raised when the changes. That happens when the player presses or releases a key. + public readonly ManagedEvent Control_KeyboardChanged; + + /// Raised when the player presses a keyboard key. + public readonly ManagedEvent Control_KeyPressed; + + /// Raised when the player releases a keyboard key. + public readonly ManagedEvent Control_KeyReleased; + + /// Raised when the changes. That happens when the player moves the mouse, scrolls the mouse wheel, or presses/releases a button. + public readonly ManagedEvent Control_MouseChanged; + + /// The player pressed a controller button. This event isn't raised for trigger buttons. + public readonly ManagedEvent Control_ControllerButtonPressed; + + /// The player released a controller button. This event isn't raised for trigger buttons. + public readonly ManagedEvent Control_ControllerButtonReleased; + + /// The player pressed a controller trigger button. + public readonly ManagedEvent Control_ControllerTriggerPressed; + + /// The player released a controller trigger button. + public readonly ManagedEvent Control_ControllerTriggerReleased; + + /**** + ** GameEvents + ****/ + /// Raised once after the game initialises and all methods have been called. + public readonly ManagedEvent Game_FirstUpdateTick; + + /// Raised when the game updates its state (≈60 times per second). + public readonly ManagedEvent Game_UpdateTick; + + /// Raised every other tick (≈30 times per second). + public readonly ManagedEvent Game_SecondUpdateTick; + + /// Raised every fourth tick (≈15 times per second). + public readonly ManagedEvent Game_FourthUpdateTick; + + /// Raised every eighth tick (≈8 times per second). + public readonly ManagedEvent Game_EighthUpdateTick; + + /// Raised every 15th tick (≈4 times per second).