summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SMAPI/Framework/SCore.cs2
-rw-r--r--src/SMAPI/Framework/SGame.cs19
-rw-r--r--src/SMAPI/Patches/LoadErrorPatch.cs28
-rw-r--r--src/SMAPI/i18n/default.json2
4 files changed, 48 insertions, 3 deletions
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index bd131762..bfdf1c51 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -245,7 +245,7 @@ namespace StardewModdingAPI.Framework
new DialogueErrorPatch(this.MonitorForGame, this.Reflection),
new ObjectErrorPatch(),
new LoadContextPatch(this.Reflection, this.GameInstance.OnLoadStageChanged),
- new LoadErrorPatch(this.Monitor)
+ new LoadErrorPatch(this.Monitor, this.GameInstance.OnSaveContentRemoved)
);
// add exit handler
diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs
index 89705352..13858fc5 100644
--- a/src/SMAPI/Framework/SGame.cs
+++ b/src/SMAPI/Framework/SGame.cs
@@ -65,6 +65,9 @@ namespace StardewModdingAPI.Framework
/// <remarks>Skipping a few frames ensures the game finishes initializing the world before mods try to change it.</remarks>
private readonly Countdown AfterLoadTimer = new Countdown(5);
+ /// <summary>Whether custom content was removed from the save data to avoid a crash.</summary>
+ private bool IsSaveContentRemoved;
+
/// <summary>Whether the game is saving and SMAPI has already raised <see cref="IGameLoopEvents.Saving"/>.</summary>
private bool IsBetweenSaveEvents;
@@ -216,6 +219,12 @@ namespace StardewModdingAPI.Framework
this.Events.ModMessageReceived.RaiseForMods(new ModMessageReceivedEventArgs(message), mod => mod != null && modIDs.Contains(mod.Manifest.UniqueID));
}
+ /// <summary>A callback invoked when custom content is removed from the save data to avoid a crash.</summary>
+ internal void OnSaveContentRemoved()
+ {
+ this.IsSaveContentRemoved = true;
+ }
+
/// <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)
@@ -458,6 +467,16 @@ namespace StardewModdingAPI.Framework
WatcherSnapshot state = this.WatcherSnapshot;
/*********
+ ** Display in-game warnings
+ *********/
+ // save content removed
+ if (this.IsSaveContentRemoved && Context.IsWorldReady)
+ {
+ this.IsSaveContentRemoved = false;
+ Game1.addHUDMessage(new HUDMessage(this.Translator.Get("warn.invalid-content-removed"), HUDMessage.error_type));
+ }
+
+ /*********
** Pre-update events
*********/
{
diff --git a/src/SMAPI/Patches/LoadErrorPatch.cs b/src/SMAPI/Patches/LoadErrorPatch.cs
index 87e8ee14..eedb4164 100644
--- a/src/SMAPI/Patches/LoadErrorPatch.cs
+++ b/src/SMAPI/Patches/LoadErrorPatch.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -20,6 +21,9 @@ namespace StardewModdingAPI.Patches
/// <summary>Writes messages to the console and log file.</summary>
private static IMonitor Monitor;
+ /// <summary>A callback invoked when custom content is removed from the save data to avoid a crash.</summary>
+ private static Action OnContentRemoved;
+
/*********
** Accessors
@@ -33,9 +37,11 @@ namespace StardewModdingAPI.Patches
*********/
/// <summary>Construct an instance.</summary>
/// <param name="monitor">Writes messages to the console and log file.</param>
- public LoadErrorPatch(IMonitor monitor)
+ /// <param name="onContentRemoved">A callback invoked when custom content is removed from the save data to avoid a crash.</param>
+ public LoadErrorPatch(IMonitor monitor, Action onContentRemoved)
{
LoadErrorPatch.Monitor = monitor;
+ LoadErrorPatch.OnContentRemoved = onContentRemoved;
}
@@ -58,6 +64,22 @@ namespace StardewModdingAPI.Patches
/// <returns>Returns whether to execute the original method.</returns>
private static bool Before_SaveGame_LoadDataToLocations(List<GameLocation> gamelocations)
{
+ bool removedAny = false;
+
+ // remove invalid locations
+ foreach (GameLocation location in gamelocations.ToArray())
+ {
+ if (location is Cellar)
+ continue; // missing cellars will be added by the game code
+
+ if (Game1.getLocationFromName(location.name) == null)
+ {
+ LoadErrorPatch.Monitor.Log($"Removed invalid location '{location.Name}' to avoid a crash when loading save '{Constants.SaveFolderName}'. (Did you remove a custom location mod?)", LogLevel.Warn);
+ gamelocations.Remove(location);
+ removedAny = true;
+ }
+ }
+
// get building interiors
var interiors =
(
@@ -83,11 +105,15 @@ namespace StardewModdingAPI.Patches
{
LoadErrorPatch.Monitor.Log($"Removed invalid villager '{npc.Name}' to avoid a crash when loading save '{Constants.SaveFolderName}'. (Did you remove a custom NPC mod?)", LogLevel.Warn);
location.characters.Remove(npc);
+ removedAny = true;
}
}
}
}
+ if (removedAny)
+ LoadErrorPatch.OnContentRemoved();
+
return true;
}
}
diff --git a/src/SMAPI/i18n/default.json b/src/SMAPI/i18n/default.json
index 0db3279e..5a3e4a6e 100644
--- a/src/SMAPI/i18n/default.json
+++ b/src/SMAPI/i18n/default.json
@@ -1,3 +1,3 @@
{
-
+ "warn.invalid-content-removed": "Invalid content was removed to prevent a crash (see the SMAPI console for info)."
}