overhaul events to track the mod which added each handler, and log errors under their name (#451)

This commit is contained in:
Jesse Plamondon-Willard 2018-02-23 19:05:23 -05:00
parent c8162c2fb6
commit 68528f7dec
20 changed files with 922 additions and 537 deletions

View File

@ -3,6 +3,7 @@
* For players:
* **Added support for content packs**.
<small>_Content packs are collections of files for a SMAPI mod to load. These can be installed directly under `Mods` like a normal SMAPI mod, get automatic update and compatibility checks, and provide convenient APIs to the mods that read them._</small>
* Added mod detection to unhandled errors (i.e. most errors will now mention which mod caused them).
* Added install scripts for Linux/Mac (no more manual terminal commands!).
* Added the required mod's name and URL to dependency errors.
* Fixed unhandled mod errors being logged under `[SMAPI]` instead of the mod name.

View File

@ -1,29 +1,37 @@
using System;
using StardewModdingAPI.Framework;
using System;
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Events
{
/// <summary>Events raised when the game loads content.</summary>
public static class ContentEvents
{
/*********
** Properties
*********/
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
/*********
** Events
*********/
/// <summary>Raised after the content language changes.</summary>
public static event EventHandler<EventArgsValueChanged<string>> AfterLocaleChanged;
public static event EventHandler<EventArgsValueChanged<string>> AfterLocaleChanged
{
add => ContentEvents.EventManager.Content_LocaleChanged.Add(value);
remove => ContentEvents.EventManager.Content_LocaleChanged.Remove(value);
}
/*********
** Internal methods
** Public methods
*********/
/// <summary>Raise an <see cref="AfterLocaleChanged"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="oldLocale">The previous locale.</param>
/// <param name="newLocale">The current locale.</param>
internal static void InvokeAfterLocaleChanged(IMonitor monitor, string oldLocale, string newLocale)
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
internal static void Init(EventManager eventManager)
{
monitor.SafelyRaiseGenericEvent($"{nameof(ContentEvents)}.{nameof(ContentEvents.AfterLocaleChanged)}", ContentEvents.AfterLocaleChanged?.GetInvocationList(), null, new EventArgsValueChanged<string>(oldLocale, newLocale));
ContentEvents.EventManager = eventManager;
}
}
}

View File

@ -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
*********/
/// <summary>Raised when the <see cref="KeyboardState"/> changes. That happens when the player presses or releases a key.</summary>
public static event EventHandler<EventArgsKeyboardStateChanged> KeyboardChanged;
/// <summary>Raised when the player presses a keyboard key.</summary>
public static event EventHandler<EventArgsKeyPressed> KeyPressed;
/// <summary>Raised when the player releases a keyboard key.</summary>
public static event EventHandler<EventArgsKeyPressed> KeyReleased;
/// <summary>Raised when the <see cref="MouseState"/> changes. That happens when the player moves the mouse, scrolls the mouse wheel, or presses/releases a button.</summary>
public static event EventHandler<EventArgsMouseStateChanged> MouseChanged;
/// <summary>The player pressed a controller button. This event isn't raised for trigger buttons.</summary>
public static event EventHandler<EventArgsControllerButtonPressed> ControllerButtonPressed;
/// <summary>The player released a controller button. This event isn't raised for trigger buttons.</summary>
public static event EventHandler<EventArgsControllerButtonReleased> ControllerButtonReleased;
/// <summary>The player pressed a controller trigger button.</summary>
public static event EventHandler<EventArgsControllerTriggerPressed> ControllerTriggerPressed;
/// <summary>The player released a controller trigger button.</summary>
public static event EventHandler<EventArgsControllerTriggerReleased> ControllerTriggerReleased;
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
/*********
** Internal methods
** Events
*********/
/// <summary>Raise a <see cref="KeyboardChanged"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="priorState">The previous keyboard state.</param>
/// <param name="newState">The current keyboard state.</param>
internal static void InvokeKeyboardChanged(IMonitor monitor, KeyboardState priorState, KeyboardState newState)
/// <summary>Raised when the <see cref="KeyboardState"/> changes. That happens when the player presses or releases a key.</summary>
public static event EventHandler<EventArgsKeyboardStateChanged> 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);
}
/// <summary>Raise a <see cref="MouseChanged"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="priorState">The previous mouse state.</param>
/// <param name="newState">The current mouse state.</param>
/// <param name="priorPosition">The previous mouse position on the screen adjusted for the zoom level.</param>
/// <param name="newPosition">The current mouse position on the screen adjusted for the zoom level.</param>
internal static void InvokeMouseChanged(IMonitor monitor, MouseState priorState, MouseState newState, Point priorPosition, Point newPosition)
/// <summary>Raised when the player presses a keyboard key.</summary>
public static event EventHandler<EventArgsKeyPressed> 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);
}
/// <summary>Raise a <see cref="KeyPressed"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="key">The keyboard button that was pressed.</param>
internal static void InvokeKeyPressed(IMonitor monitor, Keys key)
/// <summary>Raised when the player releases a keyboard key.</summary>
public static event EventHandler<EventArgsKeyPressed> 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);
}
/// <summary>Raise a <see cref="KeyReleased"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="key">The keyboard button that was released.</param>
internal static void InvokeKeyReleased(IMonitor monitor, Keys key)
/// <summary>Raised when the <see cref="MouseState"/> changes. That happens when the player moves the mouse, scrolls the mouse wheel, or presses/releases a button.</summary>
public static event EventHandler<EventArgsMouseStateChanged> 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);
}
/// <summary>Raise a <see cref="ControllerButtonPressed"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="button">The controller button that was pressed.</param>
internal static void InvokeButtonPressed(IMonitor monitor, Buttons button)
/// <summary>The player pressed a controller button. This event isn't raised for trigger buttons.</summary>
public static event EventHandler<EventArgsControllerButtonPressed> 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);
}
/// <summary>Raise a <see cref="ControllerButtonReleased"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="button">The controller button that was released.</param>
internal static void InvokeButtonReleased(IMonitor monitor, Buttons button)
/// <summary>The player released a controller button. This event isn't raised for trigger buttons.</summary>
public static event EventHandler<EventArgsControllerButtonReleased> 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);
}
/// <summary>Raise a <see cref="ControllerTriggerPressed"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="button">The trigger button that was pressed.</param>
/// <param name="value">The current trigger value.</param>
internal static void InvokeTriggerPressed(IMonitor monitor, Buttons button, float value)
/// <summary>The player pressed a controller trigger button.</summary>
public static event EventHandler<EventArgsControllerTriggerPressed> 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);
}
/// <summary>Raise a <see cref="ControllerTriggerReleased"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="button">The trigger button that was pressed.</param>
/// <param name="value">The current trigger value.</param>
internal static void InvokeTriggerReleased(IMonitor monitor, Buttons button, float value)
/// <summary>The player released a controller trigger button.</summary>
public static event EventHandler<EventArgsControllerTriggerReleased> ControllerTriggerReleased
{
monitor.SafelyRaiseGenericEvent($"{nameof(ControlEvents)}.{nameof(ControlEvents.ControllerTriggerReleased)}", ControlEvents.ControllerTriggerReleased?.GetInvocationList(), null, new EventArgsControllerTriggerReleased(PlayerIndex.One, button, value));
add => ControlEvents.EventManager.Control_ControllerTriggerReleased.Add(value);
remove => ControlEvents.EventManager.Control_ControllerTriggerReleased.Remove(value);
}
/*********
** Public methods
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
internal static void Init(EventManager eventManager)
{
ControlEvents.EventManager = eventManager;
}
}
}

View File

@ -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
*********/
/// <summary>Raised during launch after configuring XNA or MonoGame. The game window hasn't been opened by this point. Called after <see cref="Microsoft.Xna.Framework.Game.Initialize"/>.</summary>
internal static event EventHandler InitializeInternal;
/// <summary>Raised when the game updates its state (≈60 times per second).</summary>
public static event EventHandler UpdateTick;
/// <summary>Raised every other tick (≈30 times per second).</summary>
public static event EventHandler SecondUpdateTick;
/// <summary>Raised every fourth tick (≈15 times per second).</summary>
public static event EventHandler FourthUpdateTick;
/// <summary>Raised every eighth tick (≈8 times per second).</summary>
public static event EventHandler EighthUpdateTick;
/// <summary>Raised every 15th tick (≈4 times per second).</summary>
public static event EventHandler QuarterSecondTick;
/// <summary>Raised every 30th tick (≈twice per second).</summary>
public static event EventHandler HalfSecondTick;
/// <summary>Raised every 60th tick (≈once per second).</summary>
public static event EventHandler OneSecondTick;
/// <summary>Raised once after the game initialises and all <see cref="IMod.Entry"/> methods have been called.</summary>
public static event EventHandler FirstUpdateTick;
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
/*********
** Internal methods
** Events
*********/
/// <summary>Raise an <see cref="InitializeInternal"/> event.</summary>
/// <param name="monitor">Encapsulates logging and monitoring.</param>
internal static void InvokeInitialize(IMonitor monitor)
/// <summary>Raised when the game updates its state (≈60 times per second).</summary>
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);
}
/// <summary>Raise an <see cref="UpdateTick"/> event.</summary>
/// <param name="monitor">Encapsulates logging and monitoring.</param>
internal static void InvokeUpdateTick(IMonitor monitor)
/// <summary>Raised every other tick (≈30 times per second).</summary>
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);
}
/// <summary>Raise a <see cref="SecondUpdateTick"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeSecondUpdateTick(IMonitor monitor)
/// <summary>Raised every fourth tick (≈15 times per second).</summary>
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);
}
/// <summary>Raise a <see cref="FourthUpdateTick"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeFourthUpdateTick(IMonitor monitor)
/// <summary>Raised every eighth tick (≈8 times per second).</summary>
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);
}
/// <summary>Raise a <see cref="EighthUpdateTick"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeEighthUpdateTick(IMonitor monitor)
/// <summary>Raised every 15th tick (≈4 times per second).</summary>
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);
}
/// <summary>Raise a <see cref="QuarterSecondTick"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeQuarterSecondTick(IMonitor monitor)
/// <summary>Raised every 30th tick (≈twice per second).</summary>
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);
}
/// <summary>Raise a <see cref="HalfSecondTick"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeHalfSecondTick(IMonitor monitor)
/// <summary>Raised every 60th tick (≈once per second).</summary>
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);
}
/// <summary>Raise a <see cref="OneSecondTick"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeOneSecondTick(IMonitor monitor)
/// <summary>Raised once after the game initialises and all <see cref="IMod.Entry"/> methods have been called.</summary>
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);
}
/// <summary>Raise a <see cref="FirstUpdateTick"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeFirstUpdateTick(IMonitor monitor)
/*********
** Public methods
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
internal static void Init(EventManager eventManager)
{
monitor.SafelyRaisePlainEvent($"{nameof(GameEvents)}.{nameof(GameEvents.FirstUpdateTick)}", GameEvents.FirstUpdateTick?.GetInvocationList());
GameEvents.EventManager = eventManager;
}
}
}

View File

@ -1,116 +1,88 @@
using System;
using StardewModdingAPI.Framework;
using System;
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Events
{
/// <summary>Events raised during the game's draw loop, when the game is rendering content to the window.</summary>
public static class GraphicsEvents
{
/*********
** Properties
*********/
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
/*********
** Events
*********/
/****
** Generic events
****/
/// <summary>Raised after the game window is resized.</summary>
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
****/
/// <summary>Raised before drawing the world to the screen.</summary>
public static event EventHandler OnPreRenderEvent;
public static event EventHandler OnPreRenderEvent
{
add => GraphicsEvents.EventManager.Graphics_OnPreRenderEvent.Add(value);
remove => GraphicsEvents.EventManager.Graphics_OnPreRenderEvent.Remove(value);
}
/// <summary>Raised after drawing the world to the screen.</summary>
public static event EventHandler OnPostRenderEvent;
public static event EventHandler OnPostRenderEvent
{
add => GraphicsEvents.EventManager.Graphics_OnPostRenderEvent.Add(value);
remove => GraphicsEvents.EventManager.Graphics_OnPostRenderEvent.Remove(value);
}
/****
** HUD events
****/
/// <summary>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.)</summary>
public static event EventHandler OnPreRenderHudEvent;
public static event EventHandler OnPreRenderHudEvent
{
add => GraphicsEvents.EventManager.Graphics_OnPreRenderHudEvent.Add(value);
remove => GraphicsEvents.EventManager.Graphics_OnPreRenderHudEvent.Remove(value);
}
/// <summary>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.)</summary>
public static event EventHandler OnPostRenderHudEvent;
public static event EventHandler OnPostRenderHudEvent
{
add => GraphicsEvents.EventManager.Graphics_OnPostRenderHudEvent.Add(value);
remove => GraphicsEvents.EventManager.Graphics_OnPostRenderHudEvent.Remove(value);
}
/****
** GUI events
****/
/// <summary>Raised before drawing a menu to the screen during a draw loop. This includes the game's internal menus like the title screen.</summary>
public static event EventHandler OnPreRenderGuiEvent;
public static event EventHandler OnPreRenderGuiEvent
{
add => GraphicsEvents.EventManager.Graphics_OnPreRenderGuiEvent.Add(value);
remove => GraphicsEvents.EventManager.Graphics_OnPreRenderGuiEvent.Remove(value);
}
/// <summary>Raised after drawing a menu to the screen during a draw loop. This includes the game's internal menus like the title screen.</summary>
public static event EventHandler OnPostRenderGuiEvent;
public static event EventHandler OnPostRenderGuiEvent
{
add => GraphicsEvents.EventManager.Graphics_OnPostRenderGuiEvent.Add(value);
remove => GraphicsEvents.EventManager.Graphics_OnPostRenderGuiEvent.Remove(value);
}
/*********
** Internal methods
** Public methods
*********/
/****
** Generic events
****/
/// <summary>Raise a <see cref="Resize"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeResize(IMonitor monitor)
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
internal static void Init(EventManager eventManager)
{
monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.Resize)}", GraphicsEvents.Resize?.GetInvocationList());
}
/****
** Main render events
****/
/// <summary>Raise an <see cref="OnPreRenderEvent"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeOnPreRenderEvent(IMonitor monitor)
{
monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.OnPreRenderEvent)}", GraphicsEvents.OnPreRenderEvent?.GetInvocationList());
}
/// <summary>Raise an <see cref="OnPostRenderEvent"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeOnPostRenderEvent(IMonitor monitor)
{
monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.OnPostRenderEvent)}", GraphicsEvents.OnPostRenderEvent?.GetInvocationList());
}
/// <summary>Get whether there are any post-render event listeners.</summary>
internal static bool HasPostRenderListeners()
{
return GraphicsEvents.OnPostRenderEvent != null;
}
/****
** GUI events
****/
/// <summary>Raise an <see cref="OnPreRenderGuiEvent"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeOnPreRenderGuiEvent(IMonitor monitor)
{
monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.OnPreRenderGuiEvent)}", GraphicsEvents.OnPreRenderGuiEvent?.GetInvocationList());
}
/// <summary>Raise an <see cref="OnPostRenderGuiEvent"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeOnPostRenderGuiEvent(IMonitor monitor)
{
monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.OnPostRenderGuiEvent)}", GraphicsEvents.OnPostRenderGuiEvent?.GetInvocationList());
}
/****
** HUD events
****/
/// <summary>Raise an <see cref="OnPreRenderHudEvent"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeOnPreRenderHudEvent(IMonitor monitor)
{
monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.OnPreRenderHudEvent)}", GraphicsEvents.OnPreRenderHudEvent?.GetInvocationList());
}
/// <summary>Raise an <see cref="OnPostRenderHudEvent"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeOnPostRenderHudEvent(IMonitor monitor)
{
monitor.SafelyRaisePlainEvent($"{nameof(GraphicsEvents)}.{nameof(GraphicsEvents.OnPostRenderHudEvent)}", GraphicsEvents.OnPostRenderHudEvent?.GetInvocationList());
GraphicsEvents.EventManager = eventManager;
}
}
}

View File

@ -1,5 +1,5 @@
using System;
using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Events
{
@ -7,38 +7,38 @@ namespace StardewModdingAPI.Events
public static class InputEvents
{
/*********
** Events
** Properties
*********/
/// <summary>Raised when the player presses a button on the keyboard, controller, or mouse.</summary>
public static event EventHandler<EventArgsInput> ButtonPressed;
/// <summary>Raised when the player releases a keyboard key on the keyboard, controller, or mouse.</summary>
public static event EventHandler<EventArgsInput> ButtonReleased;
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
/*********
** Internal methods
** Events
*********/
/// <summary>Raise a <see cref="ButtonPressed"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="button">The button on the controller, keyboard, or mouse.</param>
/// <param name="cursor">The cursor position.</param>
/// <param name="isActionButton">Whether the input should trigger actions on the affected tile.</param>
/// <param name="isUseToolButton">Whether the input should use tools on the affected tile.</param>
internal static void InvokeButtonPressed(IMonitor monitor, SButton button, ICursorPosition cursor, bool isActionButton, bool isUseToolButton)
/// <summary>Raised when the player presses a button on the keyboard, controller, or mouse.</summary>
public static event EventHandler<EventArgsInput> ButtonPressed
{
monitor.SafelyRaiseGenericEvent($"{nameof(InputEvents)}.{nameof(InputEvents.ButtonPressed)}", InputEvents.ButtonPressed?.GetInvocationList(), null, new EventArgsInput(button, cursor, isActionButton, isUseToolButton));
add => InputEvents.EventManager.Input_ButtonPressed.Add(value);
remove => InputEvents.EventManager.Input_ButtonPressed.Remove(value);
}
/// <summary>Raise a <see cref="ButtonReleased"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="button">The button on the controller, keyboard, or mouse.</param>
/// <param name="cursor">The cursor position.</param>
/// <param name="isActionButton">Whether the input should trigger actions on the affected tile.</param>
/// <param name="isUseToolButton">Whether the input should use tools on the affected tile.</param>
internal static void InvokeButtonReleased(IMonitor monitor, SButton button, ICursorPosition cursor, bool isActionButton, bool isUseToolButton)
/// <summary>Raised when the player releases a keyboard key on the keyboard, controller, or mouse.</summary>
public static event EventHandler<EventArgsInput> ButtonReleased
{
monitor.SafelyRaiseGenericEvent($"{nameof(InputEvents)}.{nameof(InputEvents.ButtonReleased)}", InputEvents.ButtonReleased?.GetInvocationList(), null, new EventArgsInput(button, cursor, isActionButton, isUseToolButton));
add => InputEvents.EventManager.Input_ButtonReleased.Add(value);
remove => InputEvents.EventManager.Input_ButtonReleased.Remove(value);
}
/*********
** Public methods
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
internal static void Init(EventManager eventManager)
{
InputEvents.EventManager = eventManager;
}
}
}

View File

@ -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
*********/
/// <summary>Raised after the player warps to a new location.</summary>
public static event EventHandler<EventArgsCurrentLocationChanged> CurrentLocationChanged;
/// <summary>Raised after a game location is added or removed.</summary>
public static event EventHandler<EventArgsGameLocationsChanged> LocationsChanged;
/// <summary>Raised after the list of objects in the current location changes (e.g. an object is added or removed).</summary>
public static event EventHandler<EventArgsLocationObjectsChanged> LocationObjectsChanged;
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
/*********
** Internal methods
** Events
*********/
/// <summary>Raise a <see cref="CurrentLocationChanged"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="priorLocation">The player's previous location.</param>
/// <param name="newLocation">The player's current location.</param>
internal static void InvokeCurrentLocationChanged(IMonitor monitor, GameLocation priorLocation, GameLocation newLocation)
/// <summary>Raised after the player warps to a new location.</summary>
public static event EventHandler<EventArgsCurrentLocationChanged> 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);
}
/// <summary>Raise a <see cref="LocationsChanged"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="newLocations">The current list of game locations.</param>
internal static void InvokeLocationsChanged(IMonitor monitor, List<GameLocation> newLocations)
/// <summary>Raised after a game location is added or removed.</summary>
public static event EventHandler<EventArgsGameLocationsChanged> LocationsChanged
{
monitor.SafelyRaiseGenericEvent($"{nameof(LocationEvents)}.{nameof(LocationEvents.LocationsChanged)}", LocationEvents.LocationsChanged?.GetInvocationList(), null, new EventArgsGameLocationsChanged(newLocations));
add => LocationEvents.EventManager.Location_LocationsChanged.Add(value);
remove => LocationEvents.EventManager.Location_LocationsChanged.Remove(value);
}
/// <summary>Raise a <see cref="LocationObjectsChanged"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="newObjects">The current list of objects in the current location.</param>
internal static void InvokeOnNewLocationObject(IMonitor monitor, SerializableDictionary<Vector2, Object> newObjects)
/// <summary>Raised after the list of objects in the current location changes (e.g. an object is added or removed).</summary>
public static event EventHandler<EventArgsLocationObjectsChanged> LocationObjectsChanged
{
monitor.SafelyRaiseGenericEvent($"{nameof(LocationEvents)}.{nameof(LocationEvents.LocationObjectsChanged)}", LocationEvents.LocationObjectsChanged?.GetInvocationList(), null, new EventArgsLocationObjectsChanged(newObjects));
add => LocationEvents.EventManager.Location_LocationObjectsChanged.Add(value);
remove => LocationEvents.EventManager.Location_LocationObjectsChanged.Remove(value);
}
/*********
** Public methods
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
internal static void Init(EventManager eventManager)
{
LocationEvents.EventManager = eventManager;
}
}
}

View File

@ -1,6 +1,5 @@
using System;
using StardewModdingAPI.Framework;
using StardewValley.Menus;
using System;
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Events
{
@ -8,33 +7,38 @@ namespace StardewModdingAPI.Events
public static class MenuEvents
{
/*********
** Events
** Properties
*********/
/// <summary>Raised after a game menu is opened or replaced with another menu. This event is not invoked when a menu is closed.</summary>
public static event EventHandler<EventArgsClickableMenuChanged> MenuChanged;
/// <summary>Raised after a game menu is closed.</summary>
public static event EventHandler<EventArgsClickableMenuClosed> MenuClosed;
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
/*********
** Internal methods
** Events
*********/
/// <summary>Raise a <see cref="MenuChanged"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="priorMenu">The previous menu.</param>
/// <param name="newMenu">The current menu.</param>
internal static void InvokeMenuChanged(IMonitor monitor, IClickableMenu priorMenu, IClickableMenu newMenu)
/// <summary>Raised after a game menu is opened or replaced with another menu. This event is not invoked when a menu is closed.</summary>
public static event EventHandler<EventArgsClickableMenuChanged> MenuChanged
{
monitor.SafelyRaiseGenericEvent($"{nameof(MenuEvents)}.{nameof(MenuEvents.MenuChanged)}", MenuEvents.MenuChanged?.GetInvocationList(), null, new EventArgsClickableMenuChanged(priorMenu, newMenu));
add => MenuEvents.EventManager.Menu_Changed.Add(value);
remove => MenuEvents.EventManager.Menu_Changed.Remove(value);
}
/// <summary>Raise a <see cref="MenuClosed"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="priorMenu">The menu that was closed.</param>
internal static void InvokeMenuClosed(IMonitor monitor, IClickableMenu priorMenu)
/// <summary>Raised after a game menu is closed.</summary>
public static event EventHandler<EventArgsClickableMenuClosed> MenuClosed
{
monitor.SafelyRaiseGenericEvent($"{nameof(MenuEvents)}.{nameof(MenuEvents.MenuClosed)}", MenuEvents.MenuClosed?.GetInvocationList(), null, new EventArgsClickableMenuClosed(priorMenu));
add => MenuEvents.EventManager.Menu_Closed.Add(value);
remove => MenuEvents.EventManager.Menu_Closed.Remove(value);
}
/*********
** Public methods
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
internal static void Init(EventManager eventManager)
{
MenuEvents.EventManager = eventManager;
}
}
}

View File

@ -1,5 +1,5 @@
using System;
using StardewModdingAPI.Framework;
using System;
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Events
{
@ -7,22 +7,31 @@ namespace StardewModdingAPI.Events
public static class MineEvents
{
/*********
** Events
** Properties
*********/
/// <summary>Raised after the player warps to a new level of the mine.</summary>
public static event EventHandler<EventArgsMineLevelChanged> MineLevelChanged;
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
/*********
** Internal methods
** Events
*********/
/// <summary>Raise a <see cref="MineLevelChanged"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="previousMineLevel">The previous mine level.</param>
/// <param name="currentMineLevel">The current mine level.</param>
internal static void InvokeMineLevelChanged(IMonitor monitor, int previousMineLevel, int currentMineLevel)
/// <summary>Raised after the player warps to a new level of the mine.</summary>
public static event EventHandler<EventArgsMineLevelChanged> MineLevelChanged
{
monitor.SafelyRaiseGenericEvent($"{nameof(MineEvents)}.{nameof(MineEvents.MineLevelChanged)}", MineEvents.MineLevelChanged?.GetInvocationList(), null, new EventArgsMineLevelChanged(previousMineLevel, currentMineLevel));
add => MineEvents.EventManager.Mine_LevelChanged.Add(value);
remove => MineEvents.EventManager.Mine_LevelChanged.Remove(value);
}
/*********
** Public methods
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
internal static void Init(EventManager eventManager)
{
MineEvents.EventManager = eventManager;
}
}
}

View File

@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using StardewModdingAPI.Framework;
using StardewValley;
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Events
{
@ -10,34 +7,38 @@ namespace StardewModdingAPI.Events
public static class PlayerEvents
{
/*********
** Events
** Properties
*********/
/// <summary>Raised after the player's inventory changes in any way (added or removed item, sorted, etc).</summary>
public static event EventHandler<EventArgsInventoryChanged> InventoryChanged;
/// <summary> 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.</summary>
public static event EventHandler<EventArgsLevelUp> LeveledUp;
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
/*********
** Internal methods
** Events
*********/
/// <summary>Raise an <see cref="InventoryChanged"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="inventory">The player's inventory.</param>
/// <param name="changedItems">The inventory changes.</param>
internal static void InvokeInventoryChanged(IMonitor monitor, List<Item> inventory, IEnumerable<ItemStackChange> changedItems)
/// <summary>Raised after the player's inventory changes in any way (added or removed item, sorted, etc).</summary>
public static event EventHandler<EventArgsInventoryChanged> InventoryChanged
{
monitor.SafelyRaiseGenericEvent($"{nameof(PlayerEvents)}.{nameof(PlayerEvents.InventoryChanged)}", PlayerEvents.InventoryChanged?.GetInvocationList(), null, new EventArgsInventoryChanged(inventory, changedItems.ToList()));
add => PlayerEvents.EventManager.Player_InventoryChanged.Add(value);
remove => PlayerEvents.EventManager.Player_InventoryChanged.Remove(value);
}
/// <summary>Rase a <see cref="LeveledUp"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="type">The player skill that leveled up.</param>
/// <param name="newLevel">The new skill level.</param>
internal static void InvokeLeveledUp(IMonitor monitor, EventArgsLevelUp.LevelType type, int newLevel)
/// <summary> 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.</summary>
public static event EventHandler<EventArgsLevelUp> LeveledUp
{
monitor.SafelyRaiseGenericEvent($"{nameof(PlayerEvents)}.{nameof(PlayerEvents.LeveledUp)}", PlayerEvents.LeveledUp?.GetInvocationList(), null, new EventArgsLevelUp(type, newLevel));
add => PlayerEvents.EventManager.Player_LeveledUp.Add(value);
remove => PlayerEvents.EventManager.Player_LeveledUp.Remove(value);
}
/*********
** Public methods
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
internal static void Init(EventManager eventManager)
{
PlayerEvents.EventManager = eventManager;
}
}
}

View File

@ -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
*********/
/// <summary>Raised before the game creates the save file.</summary>
public static event EventHandler BeforeCreate;
/// <summary>Raised after the game finishes creating the save file.</summary>
public static event EventHandler AfterCreate;
/// <summary>Raised before the game begins writes data to the save file.</summary>
public static event EventHandler BeforeSave;
/// <summary>Raised after the game finishes writing data to the save file.</summary>
public static event EventHandler AfterSave;
/// <summary>Raised after the player loads a save slot.</summary>
public static event EventHandler AfterLoad;
/// <summary>Raised after the game returns to the title screen.</summary>
public static event EventHandler AfterReturnToTitle;
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
/*********
** Internal methods
** Events
*********/
/// <summary>Raise a <see cref="BeforeCreate"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeBeforeCreate(IMonitor monitor)
/// <summary>Raised before the game creates the save file.</summary>
public static event EventHandler BeforeCreate
{
monitor.SafelyRaisePlainEvent($"{nameof(SaveEvents)}.{nameof(SaveEvents.BeforeCreate)}", SaveEvents.BeforeCreate?.GetInvocationList(), null, EventArgs.Empty);
add => SaveEvents.EventManager.Save_BeforeCreate.Add(value);
remove => SaveEvents.EventManager.Save_BeforeCreate.Remove(value);
}
/// <summary>Raise a <see cref="AfterCreate"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeAfterCreated(IMonitor monitor)
/// <summary>Raised after the game finishes creating the save file.</summary>
public static event EventHandler AfterCreate
{
monitor.SafelyRaisePlainEvent($"{nameof(SaveEvents)}.{nameof(SaveEvents.AfterCreate)}", SaveEvents.AfterCreate?.GetInvocationList(), null, EventArgs.Empty);
add => SaveEvents.EventManager.Save_AfterCreate.Add(value);
remove => SaveEvents.EventManager.Save_AfterCreate.Remove(value);
}
/// <summary>Raise a <see cref="BeforeSave"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeBeforeSave(IMonitor monitor)
/// <summary>Raised before the game begins writes data to the save file.</summary>
public static event EventHandler BeforeSave
{
monitor.SafelyRaisePlainEvent($"{nameof(SaveEvents)}.{nameof(SaveEvents.BeforeSave)}", SaveEvents.BeforeSave?.GetInvocationList(), null, EventArgs.Empty);
add => SaveEvents.EventManager.Save_BeforeSave.Add(value);
remove => SaveEvents.EventManager.Save_BeforeSave.Remove(value);
}
/// <summary>Raise a <see cref="AfterSave"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeAfterSave(IMonitor monitor)
/// <summary>Raised after the game finishes writing data to the save file.</summary>
public static event EventHandler AfterSave
{
monitor.SafelyRaisePlainEvent($"{nameof(SaveEvents)}.{nameof(SaveEvents.AfterSave)}", SaveEvents.AfterSave?.GetInvocationList(), null, EventArgs.Empty);
add => SaveEvents.EventManager.Save_AfterSave.Add(value);
remove => SaveEvents.EventManager.Save_AfterSave.Remove(value);
}
/// <summary>Raise a <see cref="AfterLoad"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeAfterLoad(IMonitor monitor)
/// <summary>Raised after the player loads a save slot.</summary>
public static event EventHandler AfterLoad
{
monitor.SafelyRaisePlainEvent($"{nameof(SaveEvents)}.{nameof(SaveEvents.AfterLoad)}", SaveEvents.AfterLoad?.GetInvocationList(), null, EventArgs.Empty);
add => SaveEvents.EventManager.Save_AfterLoad.Add(value);
remove => SaveEvents.EventManager.Save_AfterLoad.Remove(value);
}
/// <summary>Raise a <see cref="AfterReturnToTitle"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeAfterReturnToTitle(IMonitor monitor)
/// <summary>Raised after the game returns to the title screen.</summary>
public static event EventHandler AfterReturnToTitle
{
monitor.SafelyRaisePlainEvent($"{nameof(SaveEvents)}.{nameof(SaveEvents.AfterReturnToTitle)}", SaveEvents.AfterReturnToTitle?.GetInvocationList(), null, EventArgs.Empty);
add => SaveEvents.EventManager.Save_AfterReturnToTitle.Add(value);
remove => SaveEvents.EventManager.Save_AfterReturnToTitle.Remove(value);
}
/*********
** Public methods
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
internal static void Init(EventManager eventManager)
{
SaveEvents.EventManager = eventManager;
}
}
}

View File

@ -1,5 +1,5 @@
using System;
using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Events
{
@ -7,20 +7,31 @@ namespace StardewModdingAPI.Events
public static class SpecialisedEvents
{
/*********
** Events
** Properties
*********/
/// <summary>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.</summary>
public static event EventHandler UnvalidatedUpdateTick;
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
/*********
** Internal methods
** Events
*********/
/// <summary>Raise an <see cref="UnvalidatedUpdateTick"/> event.</summary>
/// <param name="monitor">Encapsulates logging and monitoring.</param>
internal static void InvokeUnvalidatedUpdateTick(IMonitor monitor)
/// <summary>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.</summary>
public static event EventHandler UnvalidatedUpdateTick
{
monitor.SafelyRaisePlainEvent($"{nameof(SpecialisedEvents)}.{nameof(SpecialisedEvents.UnvalidatedUpdateTick)}", SpecialisedEvents.UnvalidatedUpdateTick?.GetInvocationList());
add => SpecialisedEvents.EventManager.Specialised_UnvalidatedUpdateTick.Add(value);
remove => SpecialisedEvents.EventManager.Specialised_UnvalidatedUpdateTick.Remove(value);
}
/*********
** Public methods
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
internal static void Init(EventManager eventManager)
{
SpecialisedEvents.EventManager = eventManager;
}
}
}

View File

@ -1,37 +1,44 @@
using System;
using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Events;
namespace StardewModdingAPI.Events
{
/// <summary>Events raised when the in-game date or time changes.</summary>
public static class TimeEvents
{
/*********
** Properties
*********/
/// <summary>The core event manager.</summary>
private static EventManager EventManager;
/*********
** Events
*********/
/// <summary>Raised after the game begins a new day, including when loading a save.</summary>
public static event EventHandler AfterDayStarted;
/// <summary>Raised after the in-game clock changes.</summary>
public static event EventHandler<EventArgsIntChanged> TimeOfDayChanged;
/*********
** Internal methods
*********/
/// <summary>Raise an <see cref="AfterDayStarted"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
internal static void InvokeAfterDayStarted(IMonitor monitor)
public static event EventHandler AfterDayStarted
{
monitor.SafelyRaisePlainEvent($"{nameof(TimeEvents)}.{nameof(TimeEvents.AfterDayStarted)}", TimeEvents.AfterDayStarted?.GetInvocationList(), null, EventArgs.Empty);
add => TimeEvents.EventManager.Time_AfterDayStarted.Add(value);
remove => TimeEvents.EventManager.Time_AfterDayStarted.Remove(value);
}
/// <summary>Raise a <see cref="TimeOfDayChanged"/> event.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="priorTime">The previous time in military time format (e.g. 6:00pm is 1800).</param>
/// <param name="newTime">The current time in military time format (e.g. 6:10pm is 1810).</param>
internal static void InvokeTimeOfDayChanged(IMonitor monitor, int priorTime, int newTime)
/// <summary>Raised after the in-game clock changes.</summary>
public static event EventHandler<EventArgsIntChanged> TimeOfDayChanged
{
monitor.SafelyRaiseGenericEvent($"{nameof(TimeEvents)}.{nameof(TimeEvents.TimeOfDayChanged)}", TimeEvents.TimeOfDayChanged?.GetInvocationList(), null, new EventArgsIntChanged(priorTime, newTime));
add => TimeEvents.EventManager.Time_TimeOfDayChanged.Add(value);
remove => TimeEvents.EventManager.Time_TimeOfDayChanged.Remove(value);
}
/*********
** Public methods
*********/
/// <summary>Initialise the events.</summary>
/// <param name="eventManager">The core event manager.</param>
internal static void Init(EventManager eventManager)
{
TimeEvents.EventManager = eventManager;
}
}
}

View File

@ -0,0 +1,249 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.Xna.Framework.Input;
using StardewModdingAPI.Events;
namespace StardewModdingAPI.Framework.Events
{
/// <summary>Manages SMAPI events.</summary>
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Private fields are deliberately named to simplify organisation.")]
internal class EventManager
{
/*********
** Properties
*********/
/****
** ContentEvents
****/
/// <summary>Raised after the content language changes.</summary>
public readonly ManagedEvent<EventArgsValueChanged<string>> Content_LocaleChanged;
/****
** ControlEvents
****/
/// <summary>Raised when the <see cref="KeyboardState"/> changes. That happens when the player presses or releases a key.</summary>
public readonly ManagedEvent<EventArgsKeyboardStateChanged> Control_KeyboardChanged;
/// <summary>Raised when the player presses a keyboard key.</summary>
public readonly ManagedEvent<EventArgsKeyPressed> Control_KeyPressed;
/// <summary>Raised when the player releases a keyboard key.</summary>
public readonly ManagedEvent<EventArgsKeyPressed> Control_KeyReleased;
/// <summary>Raised when the <see cref="MouseState"/> changes. That happens when the player moves the mouse, scrolls the mouse wheel, or presses/releases a button.</summary>
public readonly ManagedEvent<EventArgsMouseStateChanged> Control_MouseChanged;
/// <summary>The player pressed a controller button. This event isn't raised for trigger buttons.</summary>
public readonly ManagedEvent<EventArgsControllerButtonPressed> Control_ControllerButtonPressed;
/// <summary>The player released a controller button. This event isn't raised for trigger buttons.</summary>
public readonly ManagedEvent<EventArgsControllerButtonReleased> Control_ControllerButtonReleased;
/// <summary>The player pressed a controller trigger button.</summary>
public readonly ManagedEvent<EventArgsControllerTriggerPressed> Control_ControllerTriggerPressed;
/// <summary>The player released a controller trigger button.</summary>
public readonly ManagedEvent<EventArgsControllerTriggerReleased> Control_ControllerTriggerReleased;
/****
** GameEvents
****/
/// <summary>Raised once after the game initialises and all <see cref="IMod.Entry"/> methods have been called.</summary>
public readonly ManagedEvent Game_FirstUpdateTick;
/// <summary>Raised when the game updates its state (≈60 times per second).</summary>
public readonly ManagedEvent Game_UpdateTick;
/// <summary>Raised every other tick (≈30 times per second).</summary>
public readonly ManagedEvent Game_SecondUpdateTick;
/// <summary>Raised every fourth tick (≈15 times per second).</summary>
public readonly ManagedEvent Game_FourthUpdateTick;
/// <summary>Raised every eighth tick (≈8 times per second).</summary>
public readonly ManagedEvent Game_EighthUpdateTick;
/// <summary>Raised every 15th tick (≈4 times per second).</summary>
public readonly ManagedEvent Game_QuarterSecondTick;
/// <summary>Raised every 30th tick (≈twice per second).</summary>
public readonly ManagedEvent Game_HalfSecondTick;
/// <summary>Raised every 60th tick (≈once per second).</summary>
public readonly ManagedEvent Game_OneSecondTick;
/****
** GraphicsEvents
****/
/// <summary>Raised after the game window is resized.</summary>
public readonly ManagedEvent Graphics_Resize;
/// <summary>Raised before drawing the world to the screen.</summary>
public readonly ManagedEvent Graphics_OnPreRenderEvent;
/// <summary>Raised after drawing the world to the screen.</summary>
public readonly ManagedEvent Graphics_OnPostRenderEvent;
/// <summary>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.)</summary>
public readonly ManagedEvent Graphics_OnPreRenderHudEvent;
/// <summary>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.)</summary>
public readonly ManagedEvent Graphics_OnPostRenderHudEvent;
/// <summary>Raised before drawing a menu to the screen during a draw loop. This includes the game's internal menus like the title screen.</summary>
public readonly ManagedEvent Graphics_OnPreRenderGuiEvent;
/// <summary>Raised after drawing a menu to the screen during a draw loop. This includes the game's internal menus like the title screen.</summary>
public readonly ManagedEvent Graphics_OnPostRenderGuiEvent;
/****
** InputEvents
****/
/// <summary>Raised when the player presses a button on the keyboard, controller, or mouse.</summary>
public readonly ManagedEvent<EventArgsInput> Input_ButtonPressed;
/// <summary>Raised when the player releases a keyboard key on the keyboard, controller, or mouse.</summary>
public readonly ManagedEvent<EventArgsInput> Input_ButtonReleased;
/****
** LocationEvents
****/
/// <summary>Raised after the player warps to a new location.</summary>
public readonly ManagedEvent<EventArgsCurrentLocationChanged> Location_CurrentLocationChanged;
/// <summary>Raised after a game location is added or removed.</summary>
public readonly ManagedEvent<EventArgsGameLocationsChanged> Location_LocationsChanged;
/// <summary>Raised after the list of objects in the current location changes (e.g. an object is added or removed).</summary>
public readonly ManagedEvent<EventArgsLocationObjectsChanged> Location_LocationObjectsChanged;
/****
** MenuEvents
****/
/// <summary>Raised after a game menu is opened or replaced with another menu. This event is not invoked when a menu is closed.</summary>
public readonly ManagedEvent<EventArgsClickableMenuChanged> Menu_Changed;
/// <summary>Raised after a game menu is closed.</summary>
public readonly ManagedEvent<EventArgsClickableMenuClosed> Menu_Closed;
/****
** MineEvents
****/
/// <summary>Raised after the player warps to a new level of the mine.</summary>
public readonly ManagedEvent<EventArgsMineLevelChanged> Mine_LevelChanged;
/****
** PlayerEvents
****/
/// <summary>Raised after the player's inventory changes in any way (added or removed item, sorted, etc).</summary>
public readonly ManagedEvent<EventArgsInventoryChanged> Player_InventoryChanged;
/// <summary> 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.</summary>
public readonly ManagedEvent<EventArgsLevelUp> Player_LeveledUp;
/****
** SaveEvents
****/
/// <summary>Raised before the game creates the save file.</summary>
public readonly ManagedEvent Save_BeforeCreate;
/// <summary>Raised after the game finishes creating the save file.</summary>
public readonly ManagedEvent Save_AfterCreate;
/// <summary>Raised before the game begins writes data to the save file.</summary>
public readonly ManagedEvent Save_BeforeSave;
/// <summary>Raised after the game finishes writing data to the save file.</summary>
public readonly ManagedEvent Save_AfterSave;
/// <summary>Raised after the player loads a save slot.</summary>
public readonly ManagedEvent Save_AfterLoad;
/// <summary>Raised after the game returns to the title screen.</summary>
public readonly ManagedEvent Save_AfterReturnToTitle;
/****
** SpecialisedEvents
****/
/// <summary>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.</summary>
public readonly ManagedEvent Specialised_UnvalidatedUpdateTick;
/****
** TimeEvents
****/
/// <summary>Raised after the game begins a new day, including when loading a save.</summary>
public readonly ManagedEvent Time_AfterDayStarted;
/// <summary>Raised after the in-game clock changes.</summary>
public readonly ManagedEvent<EventArgsIntChanged> Time_TimeOfDayChanged;
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="monitor">Writes messages to the log.</param>
/// <param name="modRegistry">The mod registry with which to identify mods.</param>
public EventManager(IMonitor monitor, ModRegistry modRegistry)
{
// create shortcut initialisers
ManagedEvent<TEventArgs> ManageEventOf<TEventArgs>(string typeName, string eventName) => new ManagedEvent<TEventArgs>($"{typeName}.{eventName}", monitor, modRegistry);
ManagedEvent ManageEvent(string typeName, string eventName) => new ManagedEvent($"{typeName}.{eventName}", monitor, modRegistry);
// init events
this.Content_LocaleChanged = ManageEventOf<EventArgsValueChanged<string>>(nameof(ContentEvents), nameof(ContentEvents.AfterLocaleChanged));
this.Control_ControllerButtonPressed = ManageEventOf<EventArgsControllerButtonPressed>(nameof(ControlEvents), nameof(ControlEvents.ControllerButtonPressed));
this.Control_ControllerButtonReleased = ManageEventOf<EventArgsControllerButtonReleased>(nameof(ControlEvents), nameof(ControlEvents.ControllerButtonReleased));
this.Control_ControllerTriggerPressed = ManageEventOf<EventArgsControllerTriggerPressed>(nameof(ControlEvents), nameof(ControlEvents.ControllerTriggerPressed));
this.Control_ControllerTriggerReleased = ManageEventOf<EventArgsControllerTriggerReleased>(nameof(ControlEvents), nameof(ControlEvents.ControllerTriggerReleased));
this.Control_KeyboardChanged = ManageEventOf<EventArgsKeyboardStateChanged>(nameof(ControlEvents), nameof(ControlEvents.KeyboardChanged));
this.Control_KeyPressed = ManageEventOf<EventArgsKeyPressed>(nameof(ControlEvents), nameof(ControlEvents.KeyPressed));
this.Control_KeyReleased = ManageEventOf<EventArgsKeyPressed>(nameof(ControlEvents), nameof(ControlEvents.KeyReleased));
this.Control_MouseChanged = ManageEventOf<EventArgsMouseStateChanged>(nameof(ControlEvents), nameof(ControlEvents.MouseChanged));
this.Game_FirstUpdateTick = ManageEvent(nameof(GameEvents), nameof(GameEvents.FirstUpdateTick));
this.Game_UpdateTick = ManageEvent(nameof(GameEvents), nameof(GameEvents.UpdateTick));
this.Game_SecondUpdateTick = ManageEvent(nameof(GameEvents), nameof(GameEvents.SecondUpdateTick));
this.Game_FourthUpdateTick = ManageEvent(nameof(GameEvents), nameof(GameEvents.FourthUpdateTick));
this.Game_EighthUpdateTick = ManageEvent(nameof(GameEvents), nameof(GameEvents.EighthUpdateTick));
this.Game_QuarterSecondTick = ManageEvent(nameof(GameEvents), nameof(GameEvents.QuarterSecondTick));
this.Game_HalfSecondTick = ManageEvent(nameof(GameEvents), nameof(GameEvents.HalfSecondTick));
this.Game_OneSecondTick = ManageEvent(nameof(GameEvents), nameof(GameEvents.OneSecondTick));
this.Graphics_Resize = ManageEvent(nameof(GraphicsEvents), nameof(GraphicsEvents.Resize));
this.Graphics_OnPreRenderEvent = ManageEvent(nameof(GraphicsEvents), nameof(GraphicsEvents.OnPreRenderEvent));
this.Graphics_OnPostRenderEvent = ManageEvent(nameof(GraphicsEvents), nameof(GraphicsEvents.OnPostRenderEvent));
this.Graphics_OnPreRenderHudEvent = ManageEvent(nameof(GraphicsEvents), nameof(GraphicsEvents.OnPreRenderHudEvent));
this.Graphics_OnPostRenderHudEvent = ManageEvent(nameof(GraphicsEvents), nameof(GraphicsEvents.OnPostRenderHudEvent));
this.Graphics_OnPreRenderGuiEvent = ManageEvent(nameof(GraphicsEvents), nameof(GraphicsEvents.OnPreRenderGuiEvent));
this.Graphics_OnPostRenderGuiEvent = ManageEvent(nameof(GraphicsEvents), nameof(GraphicsEvents.OnPostRenderGuiEvent));
this.Input_ButtonPressed = ManageEventOf<EventArgsInput>(nameof(InputEvents), nameof(InputEvents.ButtonPressed));
this.Input_ButtonReleased = ManageEventOf<EventArgsInput>(nameof(InputEvents), nameof(InputEvents.ButtonReleased));
this.Location_CurrentLocationChanged = ManageEventOf<EventArgsCurrentLocationChanged>(nameof(LocationEvents), nameof(LocationEvents.CurrentLocationChanged));
this.Location_LocationsChanged = ManageEventOf<EventArgsGameLocationsChanged>(nameof(LocationEvents), nameof(LocationEvents.LocationsChanged));
this.Location_LocationObjectsChanged = ManageEventOf<EventArgsLocationObjectsChanged>(nameof(LocationEvents), nameof(LocationEvents.LocationObjectsChanged));
this.Menu_Changed = ManageEventOf<EventArgsClickableMenuChanged>(nameof(MenuEvents), nameof(MenuEvents.MenuChanged));
this.Menu_Closed = ManageEventOf<EventArgsClickableMenuClosed>(nameof(MenuEvents), nameof(MenuEvents.MenuClosed));
this.Mine_LevelChanged = ManageEventOf<EventArgsMineLevelChanged>(nameof(MineEvents), nameof(MineEvents.MineLevelChanged));
this.Player_InventoryChanged = ManageEventOf<EventArgsInventoryChanged>(nameof(PlayerEvents), nameof(PlayerEvents.InventoryChanged));
this.Player_LeveledUp = ManageEventOf<EventArgsLevelUp>(nameof(PlayerEvents), nameof(PlayerEvents.LeveledUp));
this.Save_BeforeCreate = ManageEvent(nameof(SaveEvents), nameof(SaveEvents.BeforeCreate));
this.Save_AfterCreate = ManageEvent(nameof(SaveEvents), nameof(SaveEvents.AfterCreate));
this.Save_BeforeSave = ManageEvent(nameof(SaveEvents), nameof(SaveEvents.BeforeSave));
this.Save_AfterSave = ManageEvent(nameof(SaveEvents), nameof(SaveEvents.AfterSave));
this.Save_AfterLoad = ManageEvent(nameof(SaveEvents), nameof(SaveEvents.AfterLoad));
this.Save_AfterReturnToTitle = ManageEvent(nameof(SaveEvents), nameof(SaveEvents.AfterReturnToTitle));
this.Specialised_UnvalidatedUpdateTick = ManageEvent(nameof(SpecialisedEvents), nameof(SpecialisedEvents.UnvalidatedUpdateTick));
this.Time_AfterDayStarted = ManageEvent(nameof(TimeEvents), nameof(TimeEvents.AfterDayStarted));
this.Time_TimeOfDayChanged = ManageEventOf<EventArgsIntChanged>(nameof(TimeEvents), nameof(TimeEvents.TimeOfDayChanged));
}
}
}

View File

@ -0,0 +1,119 @@
using System;
using System.Linq;
namespace StardewModdingAPI.Framework.Events
{
/// <summary>An event wrapper which intercepts and logs errors in handler code.</summary>
/// <typeparam name="TEventArgs">The event arguments type.</typeparam>
internal class ManagedEvent<TEventArgs> : ManagedEventBase<EventHandler<TEventArgs>>
{
/*********
** Properties
*********/
/// <summary>The underlying event.</summary>
private event EventHandler<TEventArgs> Event;
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="eventName">A human-readable name for the event.</param>
/// <param name="monitor">Writes messages to the log.</param>
/// <param name="modRegistry">The mod registry with which to identify mods.</param>
public ManagedEvent(string eventName, IMonitor monitor, ModRegistry modRegistry)
: base(eventName, monitor, modRegistry) { }
/// <summary>Add an event handler.</summary>
/// <param name="handler">The event handler.</param>
public void Add(EventHandler<TEventArgs> handler)
{
this.Event += handler;
this.AddTracking(handler, this.Event?.GetInvocationList().Cast<EventHandler<TEventArgs>>());
}
/// <summary>Remove an event handler.</summary>
/// <param name="handler">The event handler.</param>
public void Remove(EventHandler<TEventArgs> handler)
{
this.Event -= handler;
this.RemoveTracking(handler, this.Event?.GetInvocationList().Cast<EventHandler<TEventArgs>>());
}
/// <summary>Raise the event and notify all handlers.</summary>
/// <param name="args">The event arguments to pass.</param>
public void Raise(TEventArgs args)
{
if (this.Event == null)
return;
foreach (EventHandler<TEventArgs> handler in this.CachedInvocationList)
{
try
{
handler.Invoke(null, args);
}
catch (Exception ex)
{
this.LogError(handler, ex);
}
}
}
}
/// <summary>An event wrapper which intercepts and logs errors in handler code.</summary>
internal class ManagedEvent : ManagedEventBase<EventHandler>
{
/*********
** Properties
*********/
/// <summary>The underlying event.</summary>
private event EventHandler Event;
/*********
** Public methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="eventName">A human-readable name for the event.</param>
/// <param name="monitor">Writes messages to the log.</param>
/// <param name="modRegistry">The mod registry with which to identify mods.</param>
public ManagedEvent(string eventName, IMonitor monitor, ModRegistry modRegistry)
: base(eventName, monitor, modRegistry) { }
/// <summary>Add an event handler.</summary>
/// <param name="handler">The event handler.</param>
public void Add(EventHandler handler)
{
this.Event += handler;
this.AddTracking(handler, this.Event?.GetInvocationList().Cast<EventHandler>());
}
/// <summary>Remove an event handler.</summary>
/// <param name="handler">The event handler.</param>
public void Remove(EventHandler handler)
{
this.Event -= handler;
this.RemoveTracking(handler, this.Event?.GetInvocationList().Cast<EventHandler>());
}
/// <summary>Raise the event and notify all handlers.</summary>
public void Raise()
{
if (this.Event == null)
return;
foreach (EventHandler handler in this.CachedInvocationList)
{
try
{
handler.Invoke(null, EventArgs.Empty);
}
catch (Exception ex)
{
this.LogError(handler, ex);
}
}
}
}
}

View File

@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace StardewModdingAPI.Framework.Events
{
/// <summary>The base implementation for an event wrapper which intercepts and logs errors in handler code.</summary>
internal abstract class ManagedEventBase<TEventHandler>
{
/*********
** Properties
*********/
/// <summary>A human-readable name for the event.</summary>
private readonly string EventName;
/// <summary>Writes messages to the log.</summary>
private readonly IMonitor Monitor;
/// <summary>The mod registry with which to identify mods.</summary>
private readonly ModRegistry ModRegistry;
/// <summary>The display names for the mods which added each delegate.</summary>
private readonly IDictionary<TEventHandler, IModMetadata> SourceMods = new Dictionary<TEventHandler, IModMetadata>();
/// <summary>The cached invocation list.</summary>
protected TEventHandler[] CachedInvocationList { get; private set; }
/*********
** Public methods
*********/
/// <summary>Get whether anything is listening to the event.</summary>
public bool HasListeners()
{
return this.CachedInvocationList?.Length > 0;
}
/*********
** Protected methods
*********/
/// <summary>Construct an instance.</summary>
/// <param name="eventName">A human-readable name for the event.</param>
/// <param name="monitor">Writes messages to the log.</param>
/// <param name="modRegistry">The mod registry with which to identify mods.</param>
protected ManagedEventBase(string eventName, IMonitor monitor, ModRegistry modRegistry)
{
this.EventName = eventName;
this.Monitor = monitor;
this.ModRegistry = modRegistry;
}
/// <summary>Track an event handler.</summary>
/// <param name="handler">The event handler.</param>
/// <param name="invocationList">The updated event invocation list.</param>
protected void AddTracking(TEventHandler handler, IEnumerable<TEventHandler> invocationList)
{
this.SourceMods[handler] = this.ModRegistry.GetFromStack();
this.CachedInvocationList = invocationList.ToArray();
}
/// <summary>Remove tracking for an event handler.</summary>
/// <param name="handler">The event handler.</param>
/// <param name="invocationList">The updated event invocation list.</param>
protected void RemoveTracking(TEventHandler handler, IEnumerable<TEventHandler> invocationList)
{
this.SourceMods.Remove(handler);
this.CachedInvocationList = invocationList.ToArray();
}
/// <summary>Log an exception from an event handler.</summary>
/// <param name="handler">The event handler instance.</param>
/// <param name="ex">The exception that was raised.</param>
protected void LogError(TEventHandler handler, Exception ex)
{
if (this.SourceMods.TryGetValue(handler, out IModMetadata mod))
mod.LogAsMod($"This mod failed in the {this.EventName} event. Technical details: \n{ex.GetLogSummary()}", LogLevel.Error);
else
this.Monitor.Log($"A mod failed in the {this.EventName} event. Technical details: \n{ex.GetLogSummary()}", LogLevel.Error);
}
}
}

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Xna.Framework.Graphics;
using Newtonsoft.Json.Linq;
@ -15,63 +14,6 @@ namespace StardewModdingAPI.Framework
/****
** IMonitor
****/
/// <summary>Safely raise an <see cref="EventHandler"/> event, and intercept any exceptions thrown by its handlers.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="name">The event name for error messages.</param>
/// <param name="handlers">The event handlers.</param>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments (or <c>null</c> to pass <see cref="EventArgs.Empty"/>).</param>
public static void SafelyRaisePlainEvent(this IMonitor monitor, string name, IEnumerable<Delegate> handlers, object sender = null, EventArgs args = null)
{
if (handlers == null)
return;
foreach (EventHandler handler in handlers.Cast<EventHandler>())
{
// handle SMAPI exiting
if (monitor.IsExiting)
{
monitor.Log($"SMAPI shutting down: aborting {name} event.", LogLevel.Warn);
return;
}
// raise event
try
{
handler.Invoke(sender, args ?? EventArgs.Empty);
}
catch (Exception ex)
{
monitor.Log($"A mod failed handling the {name} event:\n{ex.GetLogSummary()}", LogLevel.Error);
}
}
}
/// <summary>Safely raise an <see cref="EventHandler{TEventArgs}"/> event, and intercept any exceptions thrown by its handlers.</summary>
/// <typeparam name="TEventArgs">The event argument object type.</typeparam>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="name">The event name for error messages.</param>
/// <param name="handlers">The event handlers.</param>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
public static void SafelyRaiseGenericEvent<TEventArgs>(this IMonitor monitor, string name, IEnumerable<Delegate> handlers, object sender, TEventArgs args)
{
if (handlers == null)
return;
foreach (EventHandler<TEventArgs> handler in handlers.Cast<EventHandler<TEventArgs>>())
{
try
{
handler.Invoke(sender, args);
}
catch (Exception ex)
{
monitor.Log($"A mod failed handling the {name} event:\n{ex.GetLogSummary()}", LogLevel.Error);
}
}
}
/// <summary>Log a message for the player or developer the first time it occurs.</summary>
/// <param name="monitor">The monitor through which to log the message.</param>
/// <param name="hash">The hash of logged messages.</param>

View File

@ -10,6 +10,7 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using StardewModdingAPI.Events;
using StardewModdingAPI.Framework.Events;
using StardewModdingAPI.Framework.Input;
using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Framework.Utilities;
@ -35,6 +36,9 @@ namespace StardewModdingAPI.Framework
/// <summary>Encapsulates monitoring and logging.</summary>
private readonly IMonitor Monitor;
/// <summary>Manages SMAPI events for mods.</summary>
private readonly EventManager Events;
/// <summary>The maximum number of consecutive attempts SMAPI should make to recover from a draw error.</summary>
private readonly Countdown DrawCrashTimer = new Countdown(60); // 60 ticks = roughly one second
@ -117,6 +121,9 @@ namespace StardewModdingAPI.Framework
/// <summary>The current game instance.</summary>
private static SGame Instance;
/// <summary>A callback to invoke after the game finishes initialising.</summary>
private readonly Action OnGameInitialised;
/****
** Private wrappers
****/
@ -132,9 +139,9 @@ namespace StardewModdingAPI.Framework
set => SGame.Reflection.GetField<float>(typeof(Game1), nameof(_fps)).SetValue(value);
}
private static Task _newDayTask => SGame.Reflection.GetField<Task>(typeof(Game1), nameof(_newDayTask)).GetValue();
private Color bgColor => SGame.Reflection.GetField<Color>(this, nameof(bgColor)).GetValue();
private Color bgColor => SGame.Reflection.GetField<Color>(this, nameof(this.bgColor)).GetValue();
public RenderTarget2D screenWrapper => SGame.Reflection.GetProperty<RenderTarget2D>(this, "screen").GetValue(); // deliberately renamed to avoid an infinite loop
public BlendState lightingBlend => SGame.Reflection.GetField<BlendState>(this, nameof(lightingBlend)).GetValue();
public BlendState lightingBlend => SGame.Reflection.GetField<BlendState>(this, nameof(this.lightingBlend)).GetValue();
private readonly Action drawFarmBuildings = () => SGame.Reflection.GetMethod(SGame.Instance, nameof(drawFarmBuildings)).Invoke();
private readonly Action drawHUD = () => SGame.Reflection.GetMethod(SGame.Instance, nameof(drawHUD)).Invoke();
private readonly Action drawDialogueBox = () => SGame.Reflection.GetMethod(SGame.Instance, nameof(drawDialogueBox)).Invoke();
@ -158,13 +165,17 @@ namespace StardewModdingAPI.Framework
/// <summary>Construct an instance.</summary>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
/// <param name="reflection">Simplifies access to private game code.</param>
internal SGame(IMonitor monitor, Reflector reflection)
/// <param name="eventManager">Manages SMAPI events for mods.</param>
/// <param name="onGameInitialised">A callback to invoke after the game finishes initialising.</param>
internal SGame(IMonitor monitor, Reflector reflection, EventManager eventManager, Action onGameInitialised)
{
// initialise
this.Monitor = monitor;
this.Events = eventManager;
this.FirstUpdate = true;
SGame.Instance = this;
SGame.Reflection = reflection;
this.OnGameInitialised = onGameInitialised;
// set XNA option required by Stardew Valley
Game1.graphics.GraphicsProfile = GraphicsProfile.HiDef;
@ -229,7 +240,7 @@ namespace StardewModdingAPI.Framework
if (SGame._newDayTask != null)
{
base.Update(gameTime);
SpecialisedEvents.InvokeUnvalidatedUpdateTick(this.Monitor);
this.Events.Specialised_UnvalidatedUpdateTick.Raise();
return;
}
@ -237,7 +248,7 @@ namespace StardewModdingAPI.Framework
if (Game1.gameMode == Game1.loadingMode)
{
base.Update(gameTime);
SpecialisedEvents.InvokeUnvalidatedUpdateTick(this.Monitor);
this.Events.Specialised_UnvalidatedUpdateTick.Raise();
return;
}
@ -256,20 +267,20 @@ namespace StardewModdingAPI.Framework
{
this.IsBetweenCreateEvents = true;
this.Monitor.Log("Context: before save creation.", LogLevel.Trace);
SaveEvents.InvokeBeforeCreate(this.Monitor);
this.Events.Save_BeforeCreate.Raise();
}
// raise before-save
if (Context.IsWorldReady && !this.IsBetweenSaveEvents)
{
this.IsBetweenSaveEvents = true;
this.Monitor.Log("Context: before save.", LogLevel.Trace);
SaveEvents.InvokeBeforeSave(this.Monitor);
this.Events.Save_BeforeSave.Raise();
}
// suppress non-save events
base.Update(gameTime);
SpecialisedEvents.InvokeUnvalidatedUpdateTick(this.Monitor);
this.Events.Specialised_UnvalidatedUpdateTick.Raise();
return;
}
if (this.IsBetweenCreateEvents)
@ -277,22 +288,22 @@ namespace StardewModdingAPI.Framework
// raise after-create
this.IsBetweenCreateEvents = false;
this.Monitor.Log($"Context: after save creation, starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace);
SaveEvents.InvokeAfterCreated(this.Monitor);
this.Events.Save_AfterCreate.Raise();
}
if (this.IsBetweenSaveEvents)
{
// raise after-save
this.IsBetweenSaveEvents = false;
this.Monitor.Log($"Context: after save, starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace);
SaveEvents.InvokeAfterSave(this.Monitor);
TimeEvents.InvokeAfterDayStarted(this.Monitor);
this.Events.Save_AfterSave.Raise();
this.Events.Time_AfterDayStarted.Raise();
}
/*********
** Game loaded events
** Notify SMAPI that game is initialised
*********/
if (this.FirstUpdate)
GameEvents.InvokeInitialize(this.Monitor);
this.OnGameInitialised();
/*********
** Locale changed events
@ -305,7 +316,8 @@ namespace StardewModdingAPI.Framework
this.Monitor.Log($"Context: locale set to {newValue}.", LogLevel.Trace);
if (oldValue != null)
ContentEvents.InvokeAfterLocaleChanged(this.Monitor, oldValue.ToString(), newValue.ToString());
this.Events.Content_LocaleChanged.Raise(new EventArgsValueChanged<string>(oldValue.ToString(), newValue.ToString()));
this.PreviousLocale = newValue;
}
@ -322,8 +334,8 @@ namespace StardewModdingAPI.Framework
this.Monitor.Log($"Context: loaded saved game '{Constants.SaveFolderName}', starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace);
Context.IsWorldReady = true;
SaveEvents.InvokeAfterLoad(this.Monitor);
TimeEvents.InvokeAfterDayStarted(this.Monitor);
this.Events.Save_AfterLoad.Raise();
this.Events.Time_AfterDayStarted.Raise();
}
}
@ -341,7 +353,7 @@ namespace StardewModdingAPI.Framework
this.IsExitingToTitle = false;
this.CleanupAfterReturnToTitle();
SaveEvents.InvokeAfterReturnToTitle(this.Monitor);
this.Events.Save_AfterReturnToTitle.Raise();
}
/*********
@ -354,7 +366,7 @@ namespace StardewModdingAPI.Framework
if (Game1.viewport.Width != this.PreviousWindowSize.X || Game1.viewport.Height != this.PreviousWindowSize.Y)
{
Point size = new Point(Game1.viewport.Width, Game1.viewport.Height);
GraphicsEvents.InvokeResize(this.Monitor);
this.Events.Graphics_Resize.Raise();
this.PreviousWindowSize = size;
}
@ -394,47 +406,47 @@ namespace StardewModdingAPI.Framework
if (status == InputStatus.Pressed)
{
InputEvents.InvokeButtonPressed(this.Monitor, button, cursor, button.IsActionButton(), button.IsUseToolButton());
this.Events.Input_ButtonPressed.Raise(new EventArgsInput(button, cursor, button.IsActionButton(), button.IsUseToolButton()));
// legacy events
if (button.TryGetKeyboard(out Keys key))
{
if (key != Keys.None)
ControlEvents.InvokeKeyPressed(this.Monitor, key);
this.Events.Control_KeyPressed.Raise(new EventArgsKeyPressed(key));
}
else if (button.TryGetController(out Buttons controllerButton))
{
if (controllerButton == Buttons.LeftTrigger || controllerButton == Buttons.RightTrigger)
ControlEvents.InvokeTriggerPressed(this.Monitor, controllerButton, controllerButton == Buttons.LeftTrigger ? inputState.ControllerState.Triggers.Left : inputState.ControllerState.Triggers.Right);
this.Events.Control_ControllerTriggerPressed.Raise(new EventArgsControllerTriggerPressed(PlayerIndex.One, controllerButton, controllerButton == Buttons.LeftTrigger ? inputState.ControllerState.Triggers.Left : inputState.ControllerState.Triggers.Right));
else
ControlEvents.InvokeButtonPressed(this.Monitor, controllerButton);
this.Events.Control_ControllerButtonPressed.Raise(new EventArgsControllerButtonPressed(PlayerIndex.One, controllerButton));
}
}
else if (status == InputStatus.Released)
{
InputEvents.InvokeButtonReleased(this.Monitor, button, cursor, button.IsActionButton(), button.IsUseToolButton());
this.Events.Input_ButtonReleased.Raise(new EventArgsInput(button, cursor, button.IsActionButton(), button.IsUseToolButton()));
// legacy events
if (button.TryGetKeyboard(out Keys key))
{
if (key != Keys.None)
ControlEvents.InvokeKeyReleased(this.Monitor, key);
this.Events.Control_KeyReleased.Raise(new EventArgsKeyPressed(key));
}
else if (button.TryGetController(out Buttons controllerButton))
{
if (controllerButton == Buttons.LeftTrigger || controllerButton == Buttons.RightTrigger)
ControlEvents.InvokeTriggerReleased(this.Monitor, controllerButton, controllerButton == Buttons.LeftTrigger ? inputState.ControllerState.Triggers.Left : inputState.ControllerState.Triggers.Right);
this.Events.Control_ControllerTriggerReleased.Raise(new EventArgsControllerTriggerReleased(PlayerIndex.One, controllerButton, controllerButton == Buttons.LeftTrigger ? inputState.ControllerState.Triggers.Left : inputState.ControllerState.Triggers.Right));
else
ControlEvents.InvokeButtonReleased(this.Monitor, controllerButton);
this.Events.Control_ControllerButtonReleased.Raise(new EventArgsControllerButtonReleased(PlayerIndex.One, controllerButton));
}
}
}
// raise legacy state-changed events
if (inputState.KeyboardState != this.PreviousInput.KeyboardState)
ControlEvents.InvokeKeyboardChanged(this.Monitor, this.PreviousInput.KeyboardState, inputState.KeyboardState);
this.Events.Control_KeyboardChanged.Raise(new EventArgsKeyboardStateChanged(this.PreviousInput.KeyboardState, inputState.KeyboardState));
if (inputState.MouseState != this.PreviousInput.MouseState)
ControlEvents.InvokeMouseChanged(this.Monitor, this.PreviousInput.MouseState, inputState.MouseState, this.PreviousInput.MousePosition, inputState.MousePosition);
this.Events.Control_MouseChanged.Raise(new EventArgsMouseStateChanged(this.PreviousInput.MouseState, inputState.MouseState, this.PreviousInput.MousePosition, inputState.MousePosition));
// track state
this.PreviousInput = inputState;
@ -461,9 +473,9 @@ namespace StardewModdingAPI.Framework
// raise menu events
if (newMenu != null)
MenuEvents.InvokeMenuChanged(this.Monitor, previousMenu, newMenu);
this.Events.Menu_Changed.Raise(new EventArgsClickableMenuChanged(previousMenu, newMenu));
else
MenuEvents.InvokeMenuClosed(this.Monitor, previousMenu);
this.Events.Menu_Closed.Raise(new EventArgsClickableMenuClosed(previousMenu));
// update previous menu
// (if the menu was changed in one of the handlers, deliberately defer detection until the next update so mods can be notified of the new menu change)
@ -480,46 +492,46 @@ namespace StardewModdingAPI.Framework
{
if (this.VerboseLogging)
this.Monitor.Log($"Context: set location to {Game1.currentLocation?.Name ?? "(none)"}.", LogLevel.Trace);
LocationEvents.InvokeCurrentLocationChanged(this.Monitor, this.PreviousGameLocation, Game1.currentLocation);
this.Events.Location_CurrentLocationChanged.Raise(new EventArgsCurrentLocationChanged(this.PreviousGameLocation, Game1.currentLocation));
}
// raise location list changed
if (this.GetHash(Game1.locations) != this.PreviousGameLocations)
LocationEvents.InvokeLocationsChanged(this.Monitor, Game1.locations);
this.Events.Location_LocationsChanged.Raise(new EventArgsGameLocationsChanged(Game1.locations));
// raise events that shouldn't be triggered on initial load
if (Game1.uniqueIDForThisGame == this.PreviousSaveID)
{
// raise player leveled up a skill
if (Game1.player.combatLevel != this.PreviousCombatLevel)
PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Combat, Game1.player.combatLevel);
this.Events.Player_LeveledUp.Raise(new EventArgsLevelUp(EventArgsLevelUp.LevelType.Combat, Game1.player.combatLevel));
if (Game1.player.farmingLevel != this.PreviousFarmingLevel)
PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Farming, Game1.player.farmingLevel);
this.Events.Player_LeveledUp.Raise(new EventArgsLevelUp(EventArgsLevelUp.LevelType.Farming, Game1.player.farmingLevel));
if (Game1.player.fishingLevel != this.PreviousFishingLevel)
PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Fishing, Game1.player.fishingLevel);
this.Events.Player_LeveledUp.Raise(new EventArgsLevelUp(EventArgsLevelUp.LevelType.Fishing, Game1.player.fishingLevel));
if (Game1.player.foragingLevel != this.PreviousForagingLevel)
PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Foraging, Game1.player.foragingLevel);
this.Events.Player_LeveledUp.Raise(new EventArgsLevelUp(EventArgsLevelUp.LevelType.Foraging, Game1.player.foragingLevel));
if (Game1.player.miningLevel != this.PreviousMiningLevel)
PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Mining, Game1.player.miningLevel);
this.Events.Player_LeveledUp.Raise(new EventArgsLevelUp(EventArgsLevelUp.LevelType.Mining, Game1.player.miningLevel));
if (Game1.player.luckLevel != this.PreviousLuckLevel)
PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Luck, Game1.player.luckLevel);
this.Events.Player_LeveledUp.Raise(new EventArgsLevelUp(EventArgsLevelUp.LevelType.Luck, Game1.player.luckLevel));
// raise player inventory changed
ItemStackChange[] changedItems = this.GetInventoryChanges(Game1.player.items, this.PreviousItems).ToArray();
if (changedItems.Any())
PlayerEvents.InvokeInventoryChanged(this.Monitor, Game1.player.items, changedItems);
this.Events.Player_InventoryChanged.Raise(new EventArgsInventoryChanged(Game1.player.items, changedItems.ToList()));
// raise current location's object list changed
if (this.GetHash(Game1.currentLocation.objects) != this.PreviousLocationObjects)
LocationEvents.InvokeOnNewLocationObject(this.Monitor, Game1.currentLocation.objects);
this.Events.Location_LocationObjectsChanged.Raise(new EventArgsLocationObjectsChanged(Game1.currentLocation.objects));
// raise time changed
if (Game1.timeOfDay != this.PreviousTime)
TimeEvents.InvokeTimeOfDayChanged(this.Monitor, this.PreviousTime, Game1.timeOfDay);
this.Events.Time_TimeOfDayChanged.Raise(new EventArgsIntChanged(this.PreviousTime, Game1.timeOfDay));
// raise mine level changed
if (Game1.mine != null && Game1.mine.mineLevel != this.PreviousMineLevel)
MineEvents.InvokeMineLevelChanged(this.Monitor, this.PreviousMineLevel, Game1.mine.mineLevel);
this.Events.Mine_LevelChanged.Raise(new EventArgsMineLevelChanged(this.PreviousMineLevel, Game1.mine.mineLevel));
}
// update state
@ -553,25 +565,25 @@ namespace StardewModdingAPI.Framework
/*********
** Update events
*********/
SpecialisedEvents.InvokeUnvalidatedUpdateTick(this.Monitor);
this.Events.Specialised_UnvalidatedUpdateTick.Raise();
if (this.FirstUpdate)
{
this.FirstUpdate = false;
GameEvents.InvokeFirstUpdateTick(this.Monitor);
this.Events.Game_FirstUpdateTick.Raise();
}
GameEvents.InvokeUpdateTick(this.Monitor);
this.Events.Game_UpdateTick.Raise();
if (this.CurrentUpdateTick % 2 == 0)
GameEvents.InvokeSecondUpdateTick(this.Monitor);
this.Events.Game_SecondUpdateTick.Raise();
if (this.CurrentUpdateTick % 4 == 0)
GameEvents.InvokeFourthUpdateTick(this.Monitor);
this.Events.Game_FourthUpdateTick.Raise();
if (this.CurrentUpdateTick % 8 == 0)
GameEvents.InvokeEighthUpdateTick(this.Monitor);
this.Events.Game_EighthUpdateTick.Raise();
if (this.CurrentUpdateTick % 15 == 0)
GameEvents.InvokeQuarterSecondTick(this.Monitor);
this.Events.Game_QuarterSecondTick.Raise();
if (this.CurrentUpdateTick % 30 == 0)
GameEvents.InvokeHalfSecondTick(this.Monitor);
this.Events.Game_HalfSecondTick.Raise();
if (this.CurrentUpdateTick % 60 == 0)
GameEvents.InvokeOneSecondTick(this.Monitor);
this.Events.Game_OneSecondTick.Raise();
this.CurrentUpdateTick += 1;
if (this.CurrentUpdateTick >= 60)
this.CurrentUpdateTick = 0;
@ -680,9 +692,9 @@ namespace StardewModdingAPI.Framework
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
try
{
GraphicsEvents.InvokeOnPreRenderGuiEvent(this.Monitor);
this.Events.Graphics_OnPreRenderGuiEvent.Raise();
activeClickableMenu.draw(Game1.spriteBatch);
GraphicsEvents.InvokeOnPostRenderGuiEvent(this.Monitor);
this.Events.Graphics_OnPostRenderGuiEvent.Raise();
}
catch (Exception ex)
{
@ -704,9 +716,9 @@ namespace StardewModdingAPI.Framework
try
{
Game1.activeClickableMenu.drawBackground(Game1.spriteBatch);
GraphicsEvents.InvokeOnPreRenderGuiEvent(this.Monitor);
this.Events.Graphics_OnPreRenderGuiEvent.Raise();
Game1.activeClickableMenu.draw(Game1.spriteBatch);
GraphicsEvents.InvokeOnPostRenderGuiEvent(this.Monitor);
this.Events.Graphics_OnPostRenderGuiEvent.Raise();
}
catch (Exception ex)
{
@ -771,9 +783,9 @@ namespace StardewModdingAPI.Framework
{
try
{
GraphicsEvents.InvokeOnPreRenderGuiEvent(this.Monitor);
this.Events.Graphics_OnPreRenderGuiEvent.Raise();
Game1.activeClickableMenu.draw(Game1.spriteBatch);
GraphicsEvents.InvokeOnPostRenderGuiEvent(this.Monitor);
this.Events.Graphics_OnPostRenderGuiEvent.Raise();
}
catch (Exception ex)
{
@ -858,7 +870,7 @@ namespace StardewModdingAPI.Framework
Game1.bloom.BeginDraw();
this.GraphicsDevice.Clear(this.bgColor);
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null);
GraphicsEvents.InvokeOnPreRenderEvent(this.Monitor);
this.Events.Graphics_OnPreRenderEvent.Raise();
if (Game1.background != null)
Game1.background.draw(Game1.spriteBatch);
Game1.mapDisplayDevice.BeginScene(Game1.spriteBatch);
@ -1129,9 +1141,9 @@ namespace StardewModdingAPI.Framework
this.drawBillboard();
if ((Game1.displayHUD || Game1.eventUp) && (Game1.currentBillboard == 0 && (int)Game1.gameMode == 3) && (!Game1.freezeControls && !Game1.panMode))
{
GraphicsEvents.InvokeOnPreRenderHudEvent(this.Monitor);
this.Events.Graphics_OnPreRenderHudEvent.Raise();
this.drawHUD();
GraphicsEvents.InvokeOnPostRenderHudEvent(this.Monitor);
this.Events.Graphics_OnPostRenderHudEvent.Raise();
}
else if (Game1.activeClickableMenu == null && Game1.farmEvent == null)
Game1.spriteBatch.Draw(Game1.mouseCursors, new Vector2((float)Game1.getOldMouseX(), (float)Game1.getOldMouseY()), new Microsoft.Xna.Framework.Rectangle?(Game1.getSourceRectForStandardTileSheet(Game1.mouseCursors, 0, 16, 16)), Color.White, 0.0f, Vector2.Zero, (float)(4.0 + (double)Game1.dialogueButtonScale / 150.0), SpriteEffects.None, 1f);
@ -1263,9 +1275,9 @@ namespace StardewModdingAPI.Framework
{
try
{
GraphicsEvents.InvokeOnPreRenderGuiEvent(this.Monitor);
this.Events.Graphics_OnPreRenderGuiEvent.Raise();
Game1.activeClickableMenu.draw(Game1.spriteBatch);
GraphicsEvents.InvokeOnPostRenderGuiEvent(this.Monitor);
this.Events.Graphics_OnPostRenderGuiEvent.Raise();
}
catch (Exception ex)
{
@ -1350,11 +1362,11 @@ namespace StardewModdingAPI.Framework
/// <param name="needsNewBatch">Whether to create a new sprite batch.</param>
private void RaisePostRender(bool needsNewBatch = false)
{
if (GraphicsEvents.HasPostRenderListeners())
if (this.Events.Graphics_OnPostRenderEvent.HasListeners())
{
if (needsNewBatch)
Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
GraphicsEvents.InvokeOnPostRenderEvent(this.Monitor);
this.Events.Graphics_OnPostRenderEvent.Raise();
if (needsNewBatch)
Game1.spriteBatch.End();
}

View File

@ -17,6 +17,7 @@ using Newtonsoft.Json;
using StardewModdingAPI.Common.Models;
using StardewModdingAPI.Events;
using StardewModdingAPI.Framework;
using StardewModdingAPI.Framework.Events;
using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.Logging;
using StardewModdingAPI.Framework.ModData;
@ -65,7 +66,7 @@ namespace StardewModdingAPI
/// <summary>Tracks the installed mods.</summary>
/// <remarks>This is initialised after the game starts.</remarks>
private ModRegistry ModRegistry;
private readonly ModRegistry ModRegistry = new ModRegistry();
/// <summary>Manages deprecation warnings.</summary>
/// <remarks>This is initialised after the game starts.</remarks>
@ -75,6 +76,9 @@ namespace StardewModdingAPI
/// <remarks>This is initialised after the game starts.</remarks>
private CommandManager CommandManager;
/// <summary>Manages SMAPI events for mods.</summary>
private readonly EventManager EventManager;
/// <summary>Whether the game is currently running.</summary>
private bool IsGameRunning;
@ -128,8 +132,24 @@ namespace StardewModdingAPI
/// <param name="logPath">The full file path to which to write log messages.</param>
public Program(bool writeToConsole, string logPath)
{
// init basics
this.LogFile = new LogFileManager(logPath);
this.Monitor = new Monitor("SMAPI", this.ConsoleManager, this.LogFile, this.CancellationTokenSource) { WriteToConsole = writeToConsole };
this.EventManager = new EventManager(this.Monitor, this.ModRegistry);
// hook up events
ContentEvents.Init(this.EventManager);
ControlEvents.Init(this.EventManager);
GameEvents.Init(this.EventManager);
GraphicsEvents.Init(this.EventManager);
InputEvents.Init(this.EventManager);
LocationEvents.Init(this.EventManager);
MenuEvents.Init(this.EventManager);
MineEvents.Init(this.EventManager);
PlayerEvents.Init(this.EventManager);
SaveEvents.Init(this.EventManager);
SpecialisedEvents.Init(this.EventManager);
TimeEvents.Init(this.EventManager);
}
/// <summary>Launch SMAPI.</summary>
@ -170,7 +190,7 @@ namespace StardewModdingAPI
AppDomain.CurrentDomain.UnhandledException += (sender, e) => this.Monitor.Log($"Critical app domain exception: {e.ExceptionObject}", LogLevel.Error);
// override game
this.GameInstance = new SGame(this.Monitor, this.Reflection);
this.GameInstance = new SGame(this.Monitor, this.Reflection, this.EventManager, this.InitialiseAfterGameStart);
StardewValley.Program.gamePtr = this.GameInstance;
// add exit handler
@ -198,7 +218,6 @@ namespace StardewModdingAPI
((Form)Control.FromHandle(this.GameInstance.Window.Handle)).FormClosing += (sender, args) => this.Dispose();
#endif
this.GameInstance.Exiting += (sender, e) => this.Dispose();
GameEvents.InitializeInternal += (sender, e) => this.InitialiseAfterGameStart();
ContentEvents.AfterLocaleChanged += (sender, e) => this.OnLocaleChanged();
// set window titles
@ -326,7 +345,6 @@ namespace StardewModdingAPI
this.GameInstance.VerboseLogging = this.Settings.VerboseLogging;
// load core components
this.ModRegistry = new ModRegistry();
this.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry);
this.CommandManager = new CommandManager();

View File

@ -85,9 +85,12 @@
<Compile Include="..\..\build\GlobalAssemblyInfo.cs">
<Link>Properties\GlobalAssemblyInfo.cs</Link>
</Compile>
<Compile Include="Framework\Events\EventManager.cs" />
<Compile Include="Framework\Events\ManagedEvent.cs" />
<Compile Include="Events\SpecialisedEvents.cs" />
<Compile Include="Framework\ContentPack.cs" />
<Compile Include="Framework\Content\ContentCache.cs" />
<Compile Include="Framework\Events\ManagedEventBase.cs" />
<Compile Include="Framework\Input\InputState.cs" />
<Compile Include="Framework\Input\InputStatus.cs" />
<Compile Include="Framework\LegacyManifestVersion.cs" />