From 3b078d55daccd13332e2cba1fd5c76f775505d7a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 8 Jul 2018 20:06:33 -0400 Subject: add GameLoop events for SMAPI 3.0 (#310) --- src/SMAPI/Framework/Events/EventManager.cs | 45 ++++++++++++++++--------- src/SMAPI/Framework/Events/ModEvents.cs | 4 +++ src/SMAPI/Framework/Events/ModGameLoopEvents.cs | 39 +++++++++++++++++++++ src/SMAPI/Framework/SGame.cs | 17 +++++----- 4 files changed, 82 insertions(+), 23 deletions(-) create mode 100644 src/SMAPI/Framework/Events/ModGameLoopEvents.cs (limited to 'src/SMAPI/Framework') 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 @@ -11,6 +11,33 @@ namespace StardewModdingAPI.Framework.Events /********* ** Events (new) *********/ + /**** + ** Game loop + ****/ + /// Raised after the game is launched, right before the first update tick. + public readonly ManagedEvent GameLoop_Launched; + + /// Raised before the game performs its overall update tick (≈60 times per second). + public readonly ManagedEvent GameLoop_Updating; + + /// Raised after the game performs its overall update tick (≈60 times per second). + public readonly ManagedEvent GameLoop_Updated; + + /**** + ** Input + ****/ + /// Raised after the player presses a button on the keyboard, controller, or mouse. + public readonly ManagedEvent Input_ButtonPressed; + + /// Raised after the player released a button on the keyboard, controller, or mouse. + public readonly ManagedEvent Input_ButtonReleased; + + /// Raised after the player moves the in-game cursor. + public readonly ManagedEvent Input_CursorMoved; + + /// Raised after the player scrolls the mouse wheel. + public readonly ManagedEvent Input_MouseWheelScrolled; + /**** ** World ****/ @@ -35,21 +62,6 @@ namespace StardewModdingAPI.Framework.Events /// Raised after terrain features (like floors and trees) are added or removed in a location. public readonly ManagedEvent World_TerrainFeatureListChanged; - /**** - ** Input - ****/ - /// Raised after the player presses a button on the keyboard, controller, or mouse. - public readonly ManagedEvent Input_ButtonPressed; - - /// Raised after the player released a button on the keyboard, controller, or mouse. - public readonly ManagedEvent Input_ButtonReleased; - - /// Raised after the player moves the in-game cursor. - public readonly ManagedEvent Input_CursorMoved; - - /// Raised after the player scrolls the mouse wheel. - public readonly ManagedEvent 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(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.Updating)); + this.GameLoop_Updated = ManageEventOf(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.Updated)); + this.Input_ButtonPressed = ManageEventOf(nameof(IModEvents.Input), nameof(IInputEvents.ButtonPressed)); this.Input_ButtonReleased = ManageEventOf(nameof(IModEvents.Input), nameof(IInputEvents.ButtonReleased)); this.Input_CursorMoved = ManageEventOf(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 *********/ + /// 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 if possible. + public IGameLoopEvents GameLoop { get; } + /// Events raised when the player provides input using a controller, keyboard, or mouse. public IInputEvents Input { get; } @@ -23,6 +26,7 @@ namespace StardewModdingAPI.Framework.Events /// The underlying event manager. 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 +{ + /// 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 if possible. + internal class ModGameLoopEvents : ModEventsBase, IGameLoopEvents + { + /********* + ** Accessors + *********/ + /// Raised after the game is launched, right before the first update tick. + public event EventHandler Launched; + + /// Raised before the game performs its overall update tick (≈60 times per second). + public event EventHandler Updating + { + add => this.EventManager.GameLoop_Updating.Add(value); + remove => this.EventManager.GameLoop_Updating.Remove(value); + } + + /// Raised after the game performs its overall update tick (≈60 times per second). + public event EventHandler Updated + { + add => this.EventManager.GameLoop_Updated.Add(value); + remove => this.EventManager.GameLoop_Updated.Remove(value); + } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The mod which uses this instance. + /// The underlying event manager. + 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 /// Whether post-game-startup initialisation has been performed. private bool IsInitialised; - /// Whether this is the very first update tick since the game started. - private bool FirstUpdate; + /// The number of update ticks which have already executed. + private uint TicksElapsed = 0; /// Whether the next content manager requested by the game will be for . 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(); -- cgit