add GameLoop events for SMAPI 3.0 (#310)
This commit is contained in:
parent
7e46cc2463
commit
3b078d55da
|
@ -0,0 +1,7 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace StardewModdingAPI.Events
|
||||||
|
{
|
||||||
|
/// <summary>Event arguments for an <see cref="IGameLoopEvents.Launched"/> event.</summary>
|
||||||
|
public class GameLoopLaunchedEventArgs : EventArgs { }
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace StardewModdingAPI.Events
|
||||||
|
{
|
||||||
|
/// <summary>Event arguments for an <see cref="IGameLoopEvents.Updated"/> event.</summary>
|
||||||
|
public class GameLoopUpdatedEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
/*********
|
||||||
|
** Accessors
|
||||||
|
*********/
|
||||||
|
/// <summary>The number of ticks elapsed since the game started, including the current tick.</summary>
|
||||||
|
public uint Ticks { get; }
|
||||||
|
|
||||||
|
/// <summary>Whether <see cref="Ticks"/> is a multiple of 60, which happens approximately once per second.</summary>
|
||||||
|
public bool IsOneSecond { get; }
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Public methods
|
||||||
|
*********/
|
||||||
|
/// <summary>Construct an instance.</summary>
|
||||||
|
/// <param name="ticks">The number of ticks elapsed since the game started, including the current tick.</param>
|
||||||
|
public GameLoopUpdatedEventArgs(uint ticks)
|
||||||
|
{
|
||||||
|
this.Ticks = ticks;
|
||||||
|
this.IsOneSecond = this.IsMultipleOf(60);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Get whether <see cref="Ticks"/> is a multiple of the given <paramref name="number"/>. This is mainly useful if you want to run logic intermittently (e.g. <code>e.IsMultipleOf(30)</code> for every half-second).</summary>
|
||||||
|
/// <param name="number">The factor to check.</param>
|
||||||
|
public bool IsMultipleOf(uint number)
|
||||||
|
{
|
||||||
|
return this.Ticks % number == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace StardewModdingAPI.Events
|
||||||
|
{
|
||||||
|
/// <summary>Event arguments for an <see cref="IGameLoopEvents.Updating"/> event.</summary>
|
||||||
|
public class GameLoopUpdatingEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
/*********
|
||||||
|
** Accessors
|
||||||
|
*********/
|
||||||
|
/// <summary>The number of ticks elapsed since the game started, including the current tick.</summary>
|
||||||
|
public uint Ticks { get; }
|
||||||
|
|
||||||
|
/// <summary>Whether <see cref="Ticks"/> is a multiple of 60, which happens approximately once per second.</summary>
|
||||||
|
public bool IsOneSecond { get; }
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Public methods
|
||||||
|
*********/
|
||||||
|
/// <summary>Construct an instance.</summary>
|
||||||
|
/// <param name="ticks">The number of ticks elapsed since the game started, including the current tick.</param>
|
||||||
|
public GameLoopUpdatingEventArgs(uint ticks)
|
||||||
|
{
|
||||||
|
this.Ticks = ticks;
|
||||||
|
this.IsOneSecond = this.IsMultipleOf(60);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Get whether <see cref="Ticks"/> is a multiple of the given <paramref name="number"/>. This is mainly useful if you want to run logic intermittently (e.g. <code>e.IsMultipleOf(30)</code> for every half-second).</summary>
|
||||||
|
/// <param name="number">The factor to check.</param>
|
||||||
|
public bool IsMultipleOf(uint number)
|
||||||
|
{
|
||||||
|
return this.Ticks % number == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace StardewModdingAPI.Events
|
||||||
|
{
|
||||||
|
/// <summary>Events linked to the game's update loop. The update loop runs roughly ≈60 times/second to run game logic like state changes, action handling, etc. These can be useful, but you should consider more semantic events like <see cref="IInputEvents"/> if possible.</summary>
|
||||||
|
public interface IGameLoopEvents
|
||||||
|
{
|
||||||
|
/// <summary>Raised after the game is launched, right before the first update tick. This happens once per game session (unrelated to loading saves). All mods are loaded and initialised at this point, so this is a good time to set up mod integrations.</summary>
|
||||||
|
event EventHandler<GameLoopLaunchedEventArgs> Launched;
|
||||||
|
|
||||||
|
/// <summary>Raised before the game performs its overall update tick (≈60 times per second).</summary>
|
||||||
|
event EventHandler<GameLoopUpdatingEventArgs> Updating;
|
||||||
|
|
||||||
|
/// <summary>Raised after the game performs its overall update tick (≈60 times per second).</summary>
|
||||||
|
event EventHandler<GameLoopUpdatedEventArgs> Updated;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,9 @@ namespace StardewModdingAPI.Events
|
||||||
/// <summary>Manages access to events raised by SMAPI.</summary>
|
/// <summary>Manages access to events raised by SMAPI.</summary>
|
||||||
public interface IModEvents
|
public interface IModEvents
|
||||||
{
|
{
|
||||||
|
/// <summary>Events linked to the game's update loop. The update loop runs roughly ≈60 times/second to run game logic like state changes, action handling, etc. These can be useful, but you should consider more semantic events like <see cref="Input"/> if possible.</summary>
|
||||||
|
IGameLoopEvents GameLoop { get; }
|
||||||
|
|
||||||
/// <summary>Events raised when the player provides input using a controller, keyboard, or mouse.</summary>
|
/// <summary>Events raised when the player provides input using a controller, keyboard, or mouse.</summary>
|
||||||
IInputEvents Input { get; }
|
IInputEvents Input { get; }
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,33 @@ namespace StardewModdingAPI.Framework.Events
|
||||||
/*********
|
/*********
|
||||||
** Events (new)
|
** Events (new)
|
||||||
*********/
|
*********/
|
||||||
|
/****
|
||||||
|
** Game loop
|
||||||
|
****/
|
||||||
|
/// <summary>Raised after the game is launched, right before the first update tick.</summary>
|
||||||
|
public readonly ManagedEvent<GameLoopLaunchedEventArgs> GameLoop_Launched;
|
||||||
|
|
||||||
|
/// <summary>Raised before the game performs its overall update tick (≈60 times per second).</summary>
|
||||||
|
public readonly ManagedEvent<GameLoopUpdatingEventArgs> GameLoop_Updating;
|
||||||
|
|
||||||
|
/// <summary>Raised after the game performs its overall update tick (≈60 times per second).</summary>
|
||||||
|
public readonly ManagedEvent<GameLoopUpdatedEventArgs> GameLoop_Updated;
|
||||||
|
|
||||||
|
/****
|
||||||
|
** Input
|
||||||
|
****/
|
||||||
|
/// <summary>Raised after the player presses a button on the keyboard, controller, or mouse.</summary>
|
||||||
|
public readonly ManagedEvent<InputButtonPressedArgsInput> Input_ButtonPressed;
|
||||||
|
|
||||||
|
/// <summary>Raised after the player released a button on the keyboard, controller, or mouse.</summary>
|
||||||
|
public readonly ManagedEvent<InputButtonReleasedArgsInput> Input_ButtonReleased;
|
||||||
|
|
||||||
|
/// <summary>Raised after the player moves the in-game cursor.</summary>
|
||||||
|
public readonly ManagedEvent<InputCursorMovedArgsInput> Input_CursorMoved;
|
||||||
|
|
||||||
|
/// <summary>Raised after the player scrolls the mouse wheel.</summary>
|
||||||
|
public readonly ManagedEvent<InputMouseWheelScrolledEventArgs> Input_MouseWheelScrolled;
|
||||||
|
|
||||||
/****
|
/****
|
||||||
** World
|
** World
|
||||||
****/
|
****/
|
||||||
|
@ -35,21 +62,6 @@ namespace StardewModdingAPI.Framework.Events
|
||||||
/// <summary>Raised after terrain features (like floors and trees) are added or removed in a location.</summary>
|
/// <summary>Raised after terrain features (like floors and trees) are added or removed in a location.</summary>
|
||||||
public readonly ManagedEvent<WorldTerrainFeatureListChangedEventArgs> World_TerrainFeatureListChanged;
|
public readonly ManagedEvent<WorldTerrainFeatureListChangedEventArgs> World_TerrainFeatureListChanged;
|
||||||
|
|
||||||
/****
|
|
||||||
** Input
|
|
||||||
****/
|
|
||||||
/// <summary>Raised after the player presses a button on the keyboard, controller, or mouse.</summary>
|
|
||||||
public readonly ManagedEvent<InputButtonPressedArgsInput> Input_ButtonPressed;
|
|
||||||
|
|
||||||
/// <summary>Raised after the player released a button on the keyboard, controller, or mouse.</summary>
|
|
||||||
public readonly ManagedEvent<InputButtonReleasedArgsInput> Input_ButtonReleased;
|
|
||||||
|
|
||||||
/// <summary>Raised after the player moves the in-game cursor.</summary>
|
|
||||||
public readonly ManagedEvent<InputCursorMovedArgsInput> Input_CursorMoved;
|
|
||||||
|
|
||||||
/// <summary>Raised after the player scrolls the mouse wheel.</summary>
|
|
||||||
public readonly ManagedEvent<InputMouseWheelScrolledEventArgs> Input_MouseWheelScrolled;
|
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
** Events (old)
|
** Events (old)
|
||||||
|
@ -252,6 +264,9 @@ namespace StardewModdingAPI.Framework.Events
|
||||||
ManagedEvent ManageEvent(string typeName, string eventName) => new ManagedEvent($"{typeName}.{eventName}", monitor, modRegistry);
|
ManagedEvent ManageEvent(string typeName, string eventName) => new ManagedEvent($"{typeName}.{eventName}", monitor, modRegistry);
|
||||||
|
|
||||||
// init events (new)
|
// init events (new)
|
||||||
|
this.GameLoop_Updating = ManageEventOf<GameLoopUpdatingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.Updating));
|
||||||
|
this.GameLoop_Updated = ManageEventOf<GameLoopUpdatedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.Updated));
|
||||||
|
|
||||||
this.Input_ButtonPressed = ManageEventOf<InputButtonPressedArgsInput>(nameof(IModEvents.Input), nameof(IInputEvents.ButtonPressed));
|
this.Input_ButtonPressed = ManageEventOf<InputButtonPressedArgsInput>(nameof(IModEvents.Input), nameof(IInputEvents.ButtonPressed));
|
||||||
this.Input_ButtonReleased = ManageEventOf<InputButtonReleasedArgsInput>(nameof(IModEvents.Input), nameof(IInputEvents.ButtonReleased));
|
this.Input_ButtonReleased = ManageEventOf<InputButtonReleasedArgsInput>(nameof(IModEvents.Input), nameof(IInputEvents.ButtonReleased));
|
||||||
this.Input_CursorMoved = ManageEventOf<InputCursorMovedArgsInput>(nameof(IModEvents.Input), nameof(IInputEvents.CursorMoved));
|
this.Input_CursorMoved = ManageEventOf<InputCursorMovedArgsInput>(nameof(IModEvents.Input), nameof(IInputEvents.CursorMoved));
|
||||||
|
|
|
@ -8,6 +8,9 @@ namespace StardewModdingAPI.Framework.Events
|
||||||
/*********
|
/*********
|
||||||
** Accessors
|
** Accessors
|
||||||
*********/
|
*********/
|
||||||
|
/// <summary>Events linked to the game's update loop. The update loop runs roughly ≈60 times/second to run game logic like state changes, action handling, etc. These can be useful, but you should consider more semantic events like <see cref="IModEvents.Input"/> if possible.</summary>
|
||||||
|
public IGameLoopEvents GameLoop { get; }
|
||||||
|
|
||||||
/// <summary>Events raised when the player provides input using a controller, keyboard, or mouse.</summary>
|
/// <summary>Events raised when the player provides input using a controller, keyboard, or mouse.</summary>
|
||||||
public IInputEvents Input { get; }
|
public IInputEvents Input { get; }
|
||||||
|
|
||||||
|
@ -23,6 +26,7 @@ namespace StardewModdingAPI.Framework.Events
|
||||||
/// <param name="eventManager">The underlying event manager.</param>
|
/// <param name="eventManager">The underlying event manager.</param>
|
||||||
public ModEvents(IModMetadata mod, EventManager eventManager)
|
public ModEvents(IModMetadata mod, EventManager eventManager)
|
||||||
{
|
{
|
||||||
|
this.GameLoop = new ModGameLoopEvents(mod, eventManager);
|
||||||
this.Input = new ModInputEvents(mod, eventManager);
|
this.Input = new ModInputEvents(mod, eventManager);
|
||||||
this.World = new ModWorldEvents(mod, eventManager);
|
this.World = new ModWorldEvents(mod, eventManager);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
using System;
|
||||||
|
using StardewModdingAPI.Events;
|
||||||
|
|
||||||
|
namespace StardewModdingAPI.Framework.Events
|
||||||
|
{
|
||||||
|
/// <summary>Events linked to the game's update loop. The update loop runs roughly ≈60 times/second to run game logic like state changes, action handling, etc. These can be useful, but you should consider more semantic events like <see cref="IInputEvents"/> if possible.</summary>
|
||||||
|
internal class ModGameLoopEvents : ModEventsBase, IGameLoopEvents
|
||||||
|
{
|
||||||
|
/*********
|
||||||
|
** Accessors
|
||||||
|
*********/
|
||||||
|
/// <summary>Raised after the game is launched, right before the first update tick.</summary>
|
||||||
|
public event EventHandler<GameLoopLaunchedEventArgs> Launched;
|
||||||
|
|
||||||
|
/// <summary>Raised before the game performs its overall update tick (≈60 times per second).</summary>
|
||||||
|
public event EventHandler<GameLoopUpdatingEventArgs> Updating
|
||||||
|
{
|
||||||
|
add => this.EventManager.GameLoop_Updating.Add(value);
|
||||||
|
remove => this.EventManager.GameLoop_Updating.Remove(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Raised after the game performs its overall update tick (≈60 times per second).</summary>
|
||||||
|
public event EventHandler<GameLoopUpdatedEventArgs> Updated
|
||||||
|
{
|
||||||
|
add => this.EventManager.GameLoop_Updated.Add(value);
|
||||||
|
remove => this.EventManager.GameLoop_Updated.Remove(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Public methods
|
||||||
|
*********/
|
||||||
|
/// <summary>Construct an instance.</summary>
|
||||||
|
/// <param name="mod">The mod which uses this instance.</param>
|
||||||
|
/// <param name="eventManager">The underlying event manager.</param>
|
||||||
|
internal ModGameLoopEvents(IModMetadata mod, EventManager eventManager)
|
||||||
|
: base(mod, eventManager) { }
|
||||||
|
}
|
||||||
|
}
|
|
@ -94,8 +94,8 @@ namespace StardewModdingAPI.Framework
|
||||||
/// <summary>Whether post-game-startup initialisation has been performed.</summary>
|
/// <summary>Whether post-game-startup initialisation has been performed.</summary>
|
||||||
private bool IsInitialised;
|
private bool IsInitialised;
|
||||||
|
|
||||||
/// <summary>Whether this is the very first update tick since the game started.</summary>
|
/// <summary>The number of update ticks which have already executed.</summary>
|
||||||
private bool FirstUpdate;
|
private uint TicksElapsed = 0;
|
||||||
|
|
||||||
/// <summary>Whether the next content manager requested by the game will be for <see cref="Game1.content"/>.</summary>
|
/// <summary>Whether the next content manager requested by the game will be for <see cref="Game1.content"/>.</summary>
|
||||||
private bool NextContentManagerIsMain;
|
private bool NextContentManagerIsMain;
|
||||||
|
@ -138,7 +138,6 @@ namespace StardewModdingAPI.Framework
|
||||||
// init SMAPI
|
// init SMAPI
|
||||||
this.Monitor = monitor;
|
this.Monitor = monitor;
|
||||||
this.Events = eventManager;
|
this.Events = eventManager;
|
||||||
this.FirstUpdate = true;
|
|
||||||
this.Reflection = reflection;
|
this.Reflection = reflection;
|
||||||
this.OnGameInitialised = onGameInitialised;
|
this.OnGameInitialised = onGameInitialised;
|
||||||
this.OnGameExiting = onGameExiting;
|
this.OnGameExiting = onGameExiting;
|
||||||
|
@ -669,25 +668,27 @@ namespace StardewModdingAPI.Framework
|
||||||
/*********
|
/*********
|
||||||
** Game update
|
** Game update
|
||||||
*********/
|
*********/
|
||||||
this.Input.UpdateSuppression();
|
this.TicksElapsed++;
|
||||||
|
if (this.TicksElapsed == 1)
|
||||||
|
this.Events.GameLoop_Launched.Raise(new GameLoopLaunchedEventArgs());
|
||||||
|
this.Events.GameLoop_Updating.Raise(new GameLoopUpdatingEventArgs(this.TicksElapsed));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
this.Input.UpdateSuppression();
|
||||||
base.Update(gameTime);
|
base.Update(gameTime);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.Monitor.Log($"An error occured in the base update loop: {ex.GetLogSummary()}", LogLevel.Error);
|
this.Monitor.Log($"An error occured in the base update loop: {ex.GetLogSummary()}", LogLevel.Error);
|
||||||
}
|
}
|
||||||
|
this.Events.GameLoop_Updated.Raise(new GameLoopUpdatedEventArgs(this.TicksElapsed));
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
** Update events
|
** Update events
|
||||||
*********/
|
*********/
|
||||||
this.Events.Specialised_UnvalidatedUpdateTick.Raise();
|
this.Events.Specialised_UnvalidatedUpdateTick.Raise();
|
||||||
if (this.FirstUpdate)
|
if (this.TicksElapsed == 1)
|
||||||
{
|
|
||||||
this.FirstUpdate = false;
|
|
||||||
this.Events.Game_FirstUpdateTick.Raise();
|
this.Events.Game_FirstUpdateTick.Raise();
|
||||||
}
|
|
||||||
this.Events.Game_UpdateTick.Raise();
|
this.Events.Game_UpdateTick.Raise();
|
||||||
if (this.CurrentUpdateTick % 2 == 0)
|
if (this.CurrentUpdateTick % 2 == 0)
|
||||||
this.Events.Game_SecondUpdateTick.Raise();
|
this.Events.Game_SecondUpdateTick.Raise();
|
||||||
|
|
|
@ -90,15 +90,19 @@
|
||||||
<Compile Include="..\..\build\GlobalAssemblyInfo.cs">
|
<Compile Include="..\..\build\GlobalAssemblyInfo.cs">
|
||||||
<Link>Properties\GlobalAssemblyInfo.cs</Link>
|
<Link>Properties\GlobalAssemblyInfo.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Events\GameLoopUpdatedEventArgs.cs" />
|
||||||
|
<Compile Include="Events\GameLoopLaunchedEventArgs.cs" />
|
||||||
<Compile Include="Events\InputMouseWheelScrolledEventArgs.cs" />
|
<Compile Include="Events\InputMouseWheelScrolledEventArgs.cs" />
|
||||||
<Compile Include="Events\InputCursorMovedEventArgs.cs" />
|
<Compile Include="Events\InputCursorMovedEventArgs.cs" />
|
||||||
<Compile Include="Events\InputButtonReleasedEventArgs.cs" />
|
<Compile Include="Events\InputButtonReleasedEventArgs.cs" />
|
||||||
<Compile Include="Events\InputButtonPressedEventArgs.cs" />
|
<Compile Include="Events\InputButtonPressedEventArgs.cs" />
|
||||||
<Compile Include="Events\EventArgsLocationBuildingsChanged.cs" />
|
<Compile Include="Events\EventArgsLocationBuildingsChanged.cs" />
|
||||||
<Compile Include="Events\IInputEvents.cs" />
|
<Compile Include="Events\IInputEvents.cs" />
|
||||||
|
<Compile Include="Events\IGameLoopEvents.cs" />
|
||||||
<Compile Include="Events\IWorldEvents.cs" />
|
<Compile Include="Events\IWorldEvents.cs" />
|
||||||
<Compile Include="Events\MultiplayerEvents.cs" />
|
<Compile Include="Events\MultiplayerEvents.cs" />
|
||||||
<Compile Include="Events\WorldDebrisListChangedEventArgs.cs" />
|
<Compile Include="Events\WorldDebrisListChangedEventArgs.cs" />
|
||||||
|
<Compile Include="Events\GameLoopUpdatingEventArgs.cs" />
|
||||||
<Compile Include="Events\WorldNpcListChangedEventArgs.cs" />
|
<Compile Include="Events\WorldNpcListChangedEventArgs.cs" />
|
||||||
<Compile Include="Events\WorldLargeTerrainFeatureListChangedEventArgs.cs" />
|
<Compile Include="Events\WorldLargeTerrainFeatureListChangedEventArgs.cs" />
|
||||||
<Compile Include="Events\WorldTerrainFeatureListChangedEventArgs.cs" />
|
<Compile Include="Events\WorldTerrainFeatureListChangedEventArgs.cs" />
|
||||||
|
@ -125,6 +129,7 @@
|
||||||
<Compile Include="Framework\Content\ContentCache.cs" />
|
<Compile Include="Framework\Content\ContentCache.cs" />
|
||||||
<Compile Include="Framework\Events\ManagedEventBase.cs" />
|
<Compile Include="Framework\Events\ManagedEventBase.cs" />
|
||||||
<Compile Include="Framework\Events\ModEvents.cs" />
|
<Compile Include="Framework\Events\ModEvents.cs" />
|
||||||
|
<Compile Include="Framework\Events\ModGameLoopEvents.cs" />
|
||||||
<Compile Include="Framework\Events\ModInputEvents.cs" />
|
<Compile Include="Framework\Events\ModInputEvents.cs" />
|
||||||
<Compile Include="Framework\Input\GamePadStateBuilder.cs" />
|
<Compile Include="Framework\Input\GamePadStateBuilder.cs" />
|
||||||
<Compile Include="Framework\ModHelpers\InputHelper.cs" />
|
<Compile Include="Framework\ModHelpers\InputHelper.cs" />
|
||||||
|
|
Loading…
Reference in New Issue