From 041bd2d6ba726eeea88afed3be307343a6f9286b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 23 Dec 2018 19:26:02 -0500 Subject: add Specialised.SavePreloaded event --- docs/release-notes.md | 3 ++- src/SMAPI/Events/IGameLoopEvents.cs | 2 +- src/SMAPI/Events/ISpecialisedEvents.cs | 3 +++ src/SMAPI/Events/SavePreloadedEventArgs.cs | 7 +++++++ src/SMAPI/Framework/Events/EventManager.cs | 6 +++++- src/SMAPI/Framework/Events/ModGameLoopEvents.cs | 2 +- src/SMAPI/Framework/Events/ModSpecialisedEvents.cs | 7 +++++++ src/SMAPI/Framework/SGame.cs | 23 ++++++++++++++++++---- src/SMAPI/StardewModdingAPI.csproj | 1 + 9 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 src/SMAPI/Events/SavePreloadedEventArgs.cs diff --git a/docs/release-notes.md b/docs/release-notes.md index eacf0955..3daca07f 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -5,7 +5,8 @@ * Tweaked installer to reduce antivirus false positives. * For modders: - * You can now use `ReadSaveData` or `WriteSaveData` immediately after the save is loaded, before the in-game world is initialised. + * Added `Specialised.SavePreloaded` event, which is raised immediately after a save is loaded but before the in-game world is fully initialised. + * You can now use read/write save data as soon as the save is loaded (instead of once the world is initialised). ## 2.9.3 * For players: diff --git a/src/SMAPI/Events/IGameLoopEvents.cs b/src/SMAPI/Events/IGameLoopEvents.cs index e1900f79..ea79aa74 100644 --- a/src/SMAPI/Events/IGameLoopEvents.cs +++ b/src/SMAPI/Events/IGameLoopEvents.cs @@ -26,7 +26,7 @@ namespace StardewModdingAPI.Events /// Raised after the game finishes writing data to the save file (except the initial save creation). event EventHandler Saved; - /// Raised after the player loads a save slot. + /// Raised after the player loads a save slot and the world is initialised. event EventHandler SaveLoaded; /// Raised after the game begins a new day (including when the player loads a save). diff --git a/src/SMAPI/Events/ISpecialisedEvents.cs b/src/SMAPI/Events/ISpecialisedEvents.cs index 928cd05d..2a19113c 100644 --- a/src/SMAPI/Events/ISpecialisedEvents.cs +++ b/src/SMAPI/Events/ISpecialisedEvents.cs @@ -5,6 +5,9 @@ namespace StardewModdingAPI.Events /// Events serving specialised edge cases that shouldn't be used by most mods. public interface ISpecialisedEvents { + /// Raised immediately after the player loads a save slot, but before the world is fully initialised. The save and game data are available at this point, but some in-game content (like location maps) haven't been initialised yet. + event EventHandler SavePreloaded; + /// Raised before the game state is updated (≈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 event will trigger a stability warning in the SMAPI console. event EventHandler UnvalidatedUpdateTicking; diff --git a/src/SMAPI/Events/SavePreloadedEventArgs.cs b/src/SMAPI/Events/SavePreloadedEventArgs.cs new file mode 100644 index 00000000..03990f5a --- /dev/null +++ b/src/SMAPI/Events/SavePreloadedEventArgs.cs @@ -0,0 +1,7 @@ +using System; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for an event. + public class SavePreloadedEventArgs : EventArgs { } +} diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs index 0ad85adf..bd862046 100644 --- a/src/SMAPI/Framework/Events/EventManager.cs +++ b/src/SMAPI/Framework/Events/EventManager.cs @@ -70,7 +70,7 @@ namespace StardewModdingAPI.Framework.Events /// Raised after the game finishes writing data to the save file (except the initial save creation). public readonly ManagedEvent Saved; - /// Raised after the player loads a save slot. + /// Raised after the player loads a save slot and the world is initialised. public readonly ManagedEvent SaveLoaded; /// Raised after the game begins a new day, including when loading a save. @@ -151,6 +151,9 @@ namespace StardewModdingAPI.Framework.Events /**** ** Specialised ****/ + /// Raised immediately after the player loads a save slot, but before the world is fully initialised. + public readonly ManagedEvent SavePreloaded; + /// Raised before the game performs its overall update tick (≈60 times per second). See notes on . public readonly ManagedEvent UnvalidatedUpdateTicking; @@ -408,6 +411,7 @@ namespace StardewModdingAPI.Framework.Events this.ObjectListChanged = ManageEventOf(nameof(IModEvents.World), nameof(IWorldEvents.ObjectListChanged)); this.TerrainFeatureListChanged = ManageEventOf(nameof(IModEvents.World), nameof(IWorldEvents.TerrainFeatureListChanged)); + this.SavePreloaded = ManageEventOf(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.SavePreloaded)); this.UnvalidatedUpdateTicking = ManageEventOf(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.UnvalidatedUpdateTicking)); this.UnvalidatedUpdateTicked = ManageEventOf(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.UnvalidatedUpdateTicked)); diff --git a/src/SMAPI/Framework/Events/ModGameLoopEvents.cs b/src/SMAPI/Framework/Events/ModGameLoopEvents.cs index a5beac99..3a764ab0 100644 --- a/src/SMAPI/Framework/Events/ModGameLoopEvents.cs +++ b/src/SMAPI/Framework/Events/ModGameLoopEvents.cs @@ -58,7 +58,7 @@ namespace StardewModdingAPI.Framework.Events remove => this.EventManager.Saved.Remove(value); } - /// Raised after the player loads a save slot. + /// Raised after the player loads a save slot and the world is initialised. public event EventHandler SaveLoaded { add => this.EventManager.SaveLoaded.Add(value); diff --git a/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs b/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs index 17c32bb8..83e349cf 100644 --- a/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs +++ b/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs @@ -9,6 +9,13 @@ namespace StardewModdingAPI.Framework.Events /********* ** Accessors *********/ + /// Raised immediately after the player loads a save slot, but before the world is fully initialised. The save and game data are available at this point, but some in-game content (like location maps) haven't been initialised yet. + public event EventHandler SavePreloaded + { + add => this.EventManager.SavePreloaded.Add(value); + remove => this.EventManager.SavePreloaded.Remove(value); + } + /// Raised before the game state is updated (≈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 event will trigger a stability warning in the SMAPI console. public event EventHandler UnvalidatedUpdateTicking { diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index d515d3ad..befd9cef 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -69,8 +69,11 @@ namespace StardewModdingAPI.Framework /// Skipping a few frames ensures the game finishes initialising the world before mods try to change it. private readonly Countdown AfterLoadTimer = new Countdown(5); + /// Whether was raised for this session. + private bool RaisedPreloadedEvent; + /// Whether the after-load events were raised for this session. - private bool RaisedAfterLoadEvent; + private bool RaisedLoadedEvent; /// Whether the game is saving and SMAPI has already raised . private bool IsBetweenSaveEvents; @@ -217,6 +220,7 @@ namespace StardewModdingAPI.Framework private void OnReturnedToTitle() { this.Monitor.Log("Context: returned to title", LogLevel.Trace); + this.RaisedPreloadedEvent = false; this.Multiplayer.CleanupOnMultiplayerExit(); this.Events.ReturnedToTitle.RaiseEmpty(); #if !SMAPI_3_0_STRICT @@ -466,7 +470,7 @@ namespace StardewModdingAPI.Framework *********/ if (wasWorldReady && !Context.IsWorldReady) this.OnReturnedToTitle(); - else if (!this.RaisedAfterLoadEvent && Context.IsWorldReady) + else if (!this.RaisedLoadedEvent && Context.IsWorldReady) { // print context string context = $"Context: loaded saved game '{Constants.SaveFolderName}', starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}."; @@ -480,7 +484,7 @@ namespace StardewModdingAPI.Framework this.Monitor.Log(context, LogLevel.Trace); // raise events - this.RaisedAfterLoadEvent = true; + this.RaisedLoadedEvent = true; this.Events.SaveLoaded.RaiseEmpty(); this.Events.DayStarted.RaiseEmpty(); #if !SMAPI_3_0_STRICT @@ -824,8 +828,19 @@ namespace StardewModdingAPI.Framework ** Game update *********/ this.TicksElapsed++; + + // game launched if (this.TicksElapsed == 1) this.Events.GameLaunched.Raise(new GameLaunchedEventArgs()); + + // preloaded + if (Context.IsSaveLoaded && !this.RaisedPreloadedEvent) + { + this.RaisedPreloadedEvent = true; + this.Events.SavePreloaded.RaiseEmpty(); + } + + // update tick this.Events.UnvalidatedUpdateTicking.Raise(new UnvalidatedUpdateTickingEventArgs(this.TicksElapsed)); this.Events.UpdateTicking.Raise(new UpdateTickingEventArgs(this.TicksElapsed)); try @@ -1639,7 +1654,7 @@ namespace StardewModdingAPI.Framework { Context.IsWorldReady = false; this.AfterLoadTimer.Reset(); - this.RaisedAfterLoadEvent = false; + this.RaisedLoadedEvent = false; } #if !SMAPI_3_0_STRICT diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj index 9b00e777..36fa7e0b 100644 --- a/src/SMAPI/StardewModdingAPI.csproj +++ b/src/SMAPI/StardewModdingAPI.csproj @@ -150,6 +150,7 @@ + -- cgit