summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r--src/SMAPI/Framework/Events/EventManager.cs6
-rw-r--r--src/SMAPI/Framework/Events/ModSpecialisedEvents.cs8
-rw-r--r--src/SMAPI/Framework/SCore.cs13
-rw-r--r--src/SMAPI/Framework/SGame.cs89
4 files changed, 73 insertions, 43 deletions
diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs
index bd862046..b7f00f52 100644
--- a/src/SMAPI/Framework/Events/EventManager.cs
+++ b/src/SMAPI/Framework/Events/EventManager.cs
@@ -151,8 +151,8 @@ namespace StardewModdingAPI.Framework.Events
/****
** Specialised
****/
- /// <summary>Raised immediately after the player loads a save slot, but before the world is fully initialised.</summary>
- public readonly ManagedEvent<SavePreloadedEventArgs> SavePreloaded;
+ /// <summary>Raised when the low-level stage in the game's loading process has changed. See notes on <see cref="ISpecialisedEvents.LoadStageChanged"/>.</summary>
+ public readonly ManagedEvent<LoadStageChangedEventArgs> LoadStageChanged;
/// <summary>Raised before the game performs its overall update tick (≈60 times per second). See notes on <see cref="ISpecialisedEvents.UnvalidatedUpdateTicking"/>.</summary>
public readonly ManagedEvent<UnvalidatedUpdateTickingEventArgs> UnvalidatedUpdateTicking;
@@ -411,7 +411,7 @@ namespace StardewModdingAPI.Framework.Events
this.ObjectListChanged = ManageEventOf<ObjectListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.ObjectListChanged));
this.TerrainFeatureListChanged = ManageEventOf<TerrainFeatureListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.TerrainFeatureListChanged));
- this.SavePreloaded = ManageEventOf<SavePreloadedEventArgs>(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.SavePreloaded));
+ this.LoadStageChanged = ManageEventOf<LoadStageChangedEventArgs>(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.LoadStageChanged));
this.UnvalidatedUpdateTicking = ManageEventOf<UnvalidatedUpdateTickingEventArgs>(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.UnvalidatedUpdateTicking));
this.UnvalidatedUpdateTicked = ManageEventOf<UnvalidatedUpdateTickedEventArgs>(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.UnvalidatedUpdateTicked));
diff --git a/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs b/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs
index 83e349cf..7c3e9dee 100644
--- a/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs
+++ b/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs
@@ -9,11 +9,11 @@ namespace StardewModdingAPI.Framework.Events
/*********
** Accessors
*********/
- /// <summary>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.</summary>
- public event EventHandler<SavePreloadedEventArgs> SavePreloaded
+ /// <summary>Raised when the low-level stage in the game's loading process has changed. This is an advanced event for mods which need to run code at specific points in the loading process. The available stages or when they happen might change without warning in future versions (e.g. due to changes in the game's load process), so mods using this event are more likely to break or have bugs. Most mods should use <see cref="IGameLoopEvents"/> instead.</summary>
+ public event EventHandler<LoadStageChangedEventArgs> LoadStageChanged
{
- add => this.EventManager.SavePreloaded.Add(value);
- remove => this.EventManager.SavePreloaded.Remove(value);
+ add => this.EventManager.LoadStageChanged.Add(value);
+ remove => this.EventManager.LoadStageChanged.Remove(value);
}
/// <summary>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.</summary>
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index 679838ba..00801b72 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -181,12 +181,6 @@ namespace StardewModdingAPI.Framework
return;
}
#endif
-
- // apply game patches
- new GamePatcher(this.Monitor).Apply(
- new DialogueErrorPatch(this.MonitorForGame, this.Reflection),
- new ObjectErrorPatch()
- );
}
/// <summary>Launch SMAPI.</summary>
@@ -237,6 +231,13 @@ namespace StardewModdingAPI.Framework
this.GameInstance = new SGame(this.Monitor, this.MonitorForGame, this.Reflection, this.EventManager, this.Toolkit.JsonHelper, this.ModRegistry, SCore.DeprecationManager, this.OnLocaleChanged, this.InitialiseAfterGameStart, this.Dispose);
StardewValley.Program.gamePtr = this.GameInstance;
+ // apply game patches
+ new GamePatcher(this.Monitor).Apply(
+ new DialogueErrorPatch(this.MonitorForGame, this.Reflection),
+ new ObjectErrorPatch(),
+ new LoadForNewGamePatch(this.Reflection, this.GameInstance.OnLoadStageChanged)
+ );
+
// add exit handler
new Thread(() =>
{
diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs
index befd9cef..cb62de2a 100644
--- a/src/SMAPI/Framework/SGame.cs
+++ b/src/SMAPI/Framework/SGame.cs
@@ -69,11 +69,8 @@ namespace StardewModdingAPI.Framework
/// <remarks>Skipping a few frames ensures the game finishes initialising the world before mods try to change it.</remarks>
private readonly Countdown AfterLoadTimer = new Countdown(5);
- /// <summary>Whether <see cref="EventManager.SavePreloaded"/> was raised for this session.</summary>
- private bool RaisedPreloadedEvent;
-
- /// <summary>Whether the after-load events were raised for this session.</summary>
- private bool RaisedLoadedEvent;
+ /// <summary>The current stage in the game's loading process.</summary>
+ private LoadStage LoadStage = LoadStage.None;
/// <summary>Whether the game is saving and SMAPI has already raised <see cref="IGameLoopEvents.Saving"/>.</summary>
private bool IsBetweenSaveEvents;
@@ -216,16 +213,33 @@ namespace StardewModdingAPI.Framework
this.Events.ModMessageReceived.RaiseForMods(new ModMessageReceivedEventArgs(message), mod => mod != null && modIDs.Contains(mod.Manifest.UniqueID));
}
- /// <summary>A callback raised when the player quits a save and returns to the title screen.</summary>
- private void OnReturnedToTitle()
+ /// <summary>A callback invoked when the game's low-level load stage changes.</summary>
+ /// <param name="newStage">The new load stage.</param>
+ internal void OnLoadStageChanged(LoadStage newStage)
{
- this.Monitor.Log("Context: returned to title", LogLevel.Trace);
- this.RaisedPreloadedEvent = false;
- this.Multiplayer.CleanupOnMultiplayerExit();
- this.Events.ReturnedToTitle.RaiseEmpty();
+ // nothing to do
+ if (newStage == this.LoadStage)
+ return;
+
+ // update data
+ LoadStage oldStage = this.LoadStage;
+ this.LoadStage = newStage;
+ if (newStage == LoadStage.None)
+ {
+ this.Monitor.Log("Context: returned to title", LogLevel.Trace);
+ this.Multiplayer.CleanupOnMultiplayerExit();
+ }
+ this.Monitor.VerboseLog($"Context: load stage changed to {newStage}");
+
+ // raise events
+ this.Events.LoadStageChanged.Raise(new LoadStageChangedEventArgs(oldStage, newStage));
+ if (newStage == LoadStage.None)
+ {
+ this.Events.ReturnedToTitle.RaiseEmpty();
#if !SMAPI_3_0_STRICT
- this.Events.Legacy_AfterReturnToTitle.Raise();
+ this.Events.Legacy_AfterReturnToTitle.Raise();
#endif
+ }
}
/// <summary>Constructor a content manager to read XNB files.</summary>
@@ -284,7 +298,29 @@ namespace StardewModdingAPI.Framework
{
this.Monitor.Log("Game loader synchronising...", LogLevel.Trace);
while (Game1.currentLoader?.MoveNext() == true)
- ;
+ {
+ // raise load stage changed
+ switch (Game1.currentLoader.Current)
+ {
+ case 20:
+ this.OnLoadStageChanged(LoadStage.SaveParsed);
+ break;
+
+ case 36:
+ this.OnLoadStageChanged(LoadStage.SaveLoadedBasicInfo);
+ break;
+
+ case 50:
+ this.OnLoadStageChanged(LoadStage.SaveLoadedLocations);
+ break;
+
+ default:
+ if (Game1.gameMode == Game1.playingGameMode)
+ this.OnLoadStageChanged(LoadStage.Preloaded);
+ break;
+ }
+ }
+
Game1.currentLoader = null;
this.Monitor.Log("Game loader done.", LogLevel.Trace);
}
@@ -411,6 +447,7 @@ 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);
+ this.OnLoadStageChanged(LoadStage.CreatedSaveFile);
this.Events.SaveCreated.RaiseEmpty();
#if !SMAPI_3_0_STRICT
this.Events.Legacy_AfterCreateSave.Raise();
@@ -434,7 +471,10 @@ namespace StardewModdingAPI.Framework
*********/
bool wasWorldReady = Context.IsWorldReady;
if ((Context.IsWorldReady && !Context.IsSaveLoaded) || Game1.exitToTitle)
- this.MarkWorldNotReady();
+ {
+ Context.IsWorldReady = false;
+ this.AfterLoadTimer.Reset();
+ }
else if (Context.IsSaveLoaded && this.AfterLoadTimer.Current > 0 && Game1.currentLocation != null)
{
if (Game1.dayOfMonth != 0) // wait until new-game intro finishes (world not fully initialised yet)
@@ -469,8 +509,8 @@ namespace StardewModdingAPI.Framework
** Load / return-to-title events
*********/
if (wasWorldReady && !Context.IsWorldReady)
- this.OnReturnedToTitle();
- else if (!this.RaisedLoadedEvent && Context.IsWorldReady)
+ this.OnLoadStageChanged(LoadStage.None);
+ else if (Context.IsWorldReady && this.LoadStage != LoadStage.Ready)
{
// print context
string context = $"Context: loaded saved game '{Constants.SaveFolderName}', starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.";
@@ -484,7 +524,7 @@ namespace StardewModdingAPI.Framework
this.Monitor.Log(context, LogLevel.Trace);
// raise events
- this.RaisedLoadedEvent = true;
+ this.OnLoadStageChanged(LoadStage.Ready);
this.Events.SaveLoaded.RaiseEmpty();
this.Events.DayStarted.RaiseEmpty();
#if !SMAPI_3_0_STRICT
@@ -834,11 +874,8 @@ namespace StardewModdingAPI.Framework
this.Events.GameLaunched.Raise(new GameLaunchedEventArgs());
// preloaded
- if (Context.IsSaveLoaded && !this.RaisedPreloadedEvent)
- {
- this.RaisedPreloadedEvent = true;
- this.Events.SavePreloaded.RaiseEmpty();
- }
+ if (Context.IsSaveLoaded && this.LoadStage != LoadStage.Loaded && this.LoadStage != LoadStage.Ready)
+ this.OnLoadStageChanged(LoadStage.Loaded);
// update tick
this.Events.UnvalidatedUpdateTicking.Raise(new UnvalidatedUpdateTickingEventArgs(this.TicksElapsed));
@@ -1649,14 +1686,6 @@ namespace StardewModdingAPI.Framework
/****
** Methods
****/
- /// <summary>Perform any cleanup needed when a save is unloaded.</summary>
- private void MarkWorldNotReady()
- {
- Context.IsWorldReady = false;
- this.AfterLoadTimer.Reset();
- this.RaisedLoadedEvent = false;
- }
-
#if !SMAPI_3_0_STRICT
/// <summary>Raise the <see cref="GraphicsEvents.OnPostRenderEvent"/> if there are any listeners.</summary>
/// <param name="needsNewBatch">Whether to create a new sprite batch.</param>