summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/SMAPI/Events/GameLoopLaunchedEventArgs.cs7
-rw-r--r--src/SMAPI/Events/GameLoopUpdatedEventArgs.cs36
-rw-r--r--src/SMAPI/Events/GameLoopUpdatingEventArgs.cs36
-rw-r--r--src/SMAPI/Events/IGameLoopEvents.cs17
-rw-r--r--src/SMAPI/Events/IModEvents.cs3
-rw-r--r--src/SMAPI/Framework/Events/EventManager.cs45
-rw-r--r--src/SMAPI/Framework/Events/ModEvents.cs4
-rw-r--r--src/SMAPI/Framework/Events/ModGameLoopEvents.cs39
-rw-r--r--src/SMAPI/Framework/SGame.cs17
-rw-r--r--src/SMAPI/StardewModdingAPI.csproj5
10 files changed, 186 insertions, 23 deletions
diff --git a/src/SMAPI/Events/GameLoopLaunchedEventArgs.cs b/src/SMAPI/Events/GameLoopLaunchedEventArgs.cs
new file mode 100644
index 00000000..6a42e4f9
--- /dev/null
+++ b/src/SMAPI/Events/GameLoopLaunchedEventArgs.cs
@@ -0,0 +1,7 @@
+using System;
+
+namespace StardewModdingAPI.Events
+{
+ /// <summary>Event arguments for an <see cref="IGameLoopEvents.Launched"/> event.</summary>
+ public class GameLoopLaunchedEventArgs : EventArgs { }
+}
diff --git a/src/SMAPI/Events/GameLoopUpdatedEventArgs.cs b/src/SMAPI/Events/GameLoopUpdatedEventArgs.cs
new file mode 100644
index 00000000..3ad34b69
--- /dev/null
+++ b/src/SMAPI/Events/GameLoopUpdatedEventArgs.cs
@@ -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;
+ }
+ }
+}
diff --git a/src/SMAPI/Events/GameLoopUpdatingEventArgs.cs b/src/SMAPI/Events/GameLoopUpdatingEventArgs.cs
new file mode 100644
index 00000000..d6a8b5c2
--- /dev/null
+++ b/src/SMAPI/Events/GameLoopUpdatingEventArgs.cs
@@ -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;
+ }
+ }
+}
diff --git a/src/SMAPI/Events/IGameLoopEvents.cs b/src/SMAPI/Events/IGameLoopEvents.cs
new file mode 100644
index 00000000..a56b3de3
--- /dev/null
+++ b/src/SMAPI/Events/IGameLoopEvents.cs
@@ -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;
+ }
+}
diff --git a/src/SMAPI/Events/IModEvents.cs b/src/SMAPI/Events/IModEvents.cs
index 16ec6557..cf2f8cb8 100644
--- a/src/SMAPI/Events/IModEvents.cs
+++ b/src/SMAPI/Events/IModEvents.cs
@@ -3,6 +3,9 @@ namespace StardewModdingAPI.Events
/// <summary>Manages access to events raised by SMAPI.</summary>
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>
IInputEvents Input { get; }
diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs
index b05d82ce..3d5d0124 100644
--- a/src/SMAPI/Framework/Events/EventManager.cs
+++ b/src/SMAPI/Framework/Events/EventManager.cs
@@ -12,6 +12,33 @@ namespace StardewModdingAPI.Framework.Events
** 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
****/
/// <summary>Raised after a game location is added or removed.</summary>
@@ -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>
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)
@@ -252,6 +264,9 @@ namespace StardewModdingAPI.Framework.Events
ManagedEvent ManageEvent(string typeName, string eventName) => new ManagedEvent($"{typeName}.{eventName}", monitor, modRegistry);
// 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_ButtonReleased = ManageEventOf<InputButtonReleasedArgsInput>(nameof(IModEvents.Input), nameof(IInputEvents.ButtonReleased));
this.Input_CursorMoved = ManageEventOf<InputCursorMovedArgsInput>(nameof(IModEvents.Input), nameof(IInputEvents.CursorMoved));
diff --git a/src/SMAPI/Framework/Events/ModEvents.cs b/src/SMAPI/Framework/Events/ModEvents.cs
index 90853141..9e474457 100644
--- a/src/SMAPI/Framework/Events/ModEvents.cs
+++ b/src/SMAPI/Framework/Events/ModEvents.cs
@@ -8,6 +8,9 @@ namespace StardewModdingAPI.Framework.Events
/*********
** 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>
public IInputEvents Input { get; }
@@ -23,6 +26,7 @@ namespace StardewModdingAPI.Framework.Events
/// <param name="eventManager">The underlying event manager.</param>
public ModEvents(IModMetadata mod, EventManager eventManager)
{
+ this.GameLoop = new ModGameLoopEvents(mod, eventManager);
this.Input = new ModInputEvents(mod, eventManager);
this.World = new ModWorldEvents(mod, eventManager);
}
diff --git a/src/SMAPI/Framework/Events/ModGameLoopEvents.cs b/src/SMAPI/Framework/Events/ModGameLoopEvents.cs
new file mode 100644
index 00000000..1a142b0f
--- /dev/null
+++ b/src/SMAPI/Framework/Events/ModGameLoopEvents.cs
@@ -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) { }
+ }
+}
diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs
index d3865316..777bc478 100644
--- a/src/SMAPI/Framework/SGame.cs
+++ b/src/SMAPI/Framework/SGame.cs
@@ -94,8 +94,8 @@ namespace StardewModdingAPI.Framework
/// <summary>Whether post-game-startup initialisation has been performed.</summary>
private bool IsInitialised;
- /// <summary>Whether this is the very first update tick since the game started.</summary>
- private bool FirstUpdate;
+ /// <summary>The number of update ticks which have already executed.</summary>
+ private uint TicksElapsed = 0;
/// <summary>Whether the next content manager requested by the game will be for <see cref="Game1.content"/>.</summary>
private bool NextContentManagerIsMain;
@@ -138,7 +138,6 @@ namespace StardewModdingAPI.Framework
// init SMAPI
this.Monitor = monitor;
this.Events = eventManager;
- this.FirstUpdate = true;
this.Reflection = reflection;
this.OnGameInitialised = onGameInitialised;
this.OnGameExiting = onGameExiting;
@@ -669,25 +668,27 @@ namespace StardewModdingAPI.Framework
/*********
** 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
{
+ this.Input.UpdateSuppression();
base.Update(gameTime);
}
catch (Exception ex)
{
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
*********/
this.Events.Specialised_UnvalidatedUpdateTick.Raise();
- if (this.FirstUpdate)
- {
- this.FirstUpdate = false;
+ if (this.TicksElapsed == 1)
this.Events.Game_FirstUpdateTick.Raise();
- }
this.Events.Game_UpdateTick.Raise();
if (this.CurrentUpdateTick % 2 == 0)
this.Events.Game_SecondUpdateTick.Raise();
diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj
index 57c2c9e8..27e98f3a 100644
--- a/src/SMAPI/StardewModdingAPI.csproj
+++ b/src/SMAPI/StardewModdingAPI.csproj
@@ -90,15 +90,19 @@
<Compile Include="..\..\build\GlobalAssemblyInfo.cs">
<Link>Properties\GlobalAssemblyInfo.cs</Link>
</Compile>
+ <Compile Include="Events\GameLoopUpdatedEventArgs.cs" />
+ <Compile Include="Events\GameLoopLaunchedEventArgs.cs" />
<Compile Include="Events\InputMouseWheelScrolledEventArgs.cs" />
<Compile Include="Events\InputCursorMovedEventArgs.cs" />
<Compile Include="Events\InputButtonReleasedEventArgs.cs" />
<Compile Include="Events\InputButtonPressedEventArgs.cs" />
<Compile Include="Events\EventArgsLocationBuildingsChanged.cs" />
<Compile Include="Events\IInputEvents.cs" />
+ <Compile Include="Events\IGameLoopEvents.cs" />
<Compile Include="Events\IWorldEvents.cs" />
<Compile Include="Events\MultiplayerEvents.cs" />
<Compile Include="Events\WorldDebrisListChangedEventArgs.cs" />
+ <Compile Include="Events\GameLoopUpdatingEventArgs.cs" />
<Compile Include="Events\WorldNpcListChangedEventArgs.cs" />
<Compile Include="Events\WorldLargeTerrainFeatureListChangedEventArgs.cs" />
<Compile Include="Events\WorldTerrainFeatureListChangedEventArgs.cs" />
@@ -125,6 +129,7 @@
<Compile Include="Framework\Content\ContentCache.cs" />
<Compile Include="Framework\Events\ManagedEventBase.cs" />
<Compile Include="Framework\Events\ModEvents.cs" />
+ <Compile Include="Framework\Events\ModGameLoopEvents.cs" />
<Compile Include="Framework\Events\ModInputEvents.cs" />
<Compile Include="Framework\Input\GamePadStateBuilder.cs" />
<Compile Include="Framework\ModHelpers\InputHelper.cs" />