auto-fix save data when a custom location mod is removed

This commit is contained in:
Jesse Plamondon-Willard 2019-10-01 21:41:15 -04:00
parent 845deb43d6
commit 65997c1243
No known key found for this signature in database
GPG Key ID: CF8B1456B3E29F49
6 changed files with 53 additions and 8 deletions

View File

@ -25,8 +25,8 @@ doesn't change any of your game files. It serves eight main purposes:
possible to troubleshoot errors in the game itself that would otherwise show a generic 'program
has stopped working' type of message._
_That also includes automatically fixing save data when a load would crash, e.g. due to a custom
NPC mod the player removed._
_SMAPI also automatically fixes save data in some cases when a load would crash, e.g. due to a
custom location or NPC mod that was removed._
6. **Provide update checks.**
_SMAPI automatically checks for new versions of your installed mods, and notifies you when any

View File

@ -13,7 +13,7 @@ For players:
SMAPI should have less impact on game performance and startup time for some players.
* **Added more error recovery.**
SMAPI now detects and prevents more crashes due to game or mod bugs, or due to removing some mods which add custom content.
SMAPI now detects and prevents more crashes due to game/mod bugs, or due to removing mods which add custom locations or NPCs.
* **Improved mod scanning.**
SMAPI now supports some non-standard mod structures automatically, improves compatibility with the Vortex mod manager, and improves various error/skip messages related to mod loading.
@ -46,7 +46,7 @@ For modders:
* Improved mod scanning:
* Now ignores metadata files and folders (like `__MACOSX` and `__folder_managed_by_vortex`) and content files (like `.txt` or `.png`), which avoids missing-manifest errors in some common cases.
* Now detects XNB mods more accurately, and consolidates multi-folder XNB mods in logged messages.
* SMAPI now automatically fixes your save if you remove a custom NPC mod. (Invalid NPCs are now removed on load, with a warning in the console.)
* SMAPI now automatically removes invalid content when loading a save to prevent crashes. A warning is shown in-game when this happens. This applies for locations and NPCs.
* Added support for configuring console colors via `smapi-internal/config.json` (intended for players with unusual consoles).
* Improved launch script compatibility on Linux (thanks to kurumushi and toastal!).
* Save Backup now works in the background, to avoid affecting startup time for players with a large number of saves.

View File

@ -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

View File

@ -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)
@ -457,6 +466,16 @@ namespace StardewModdingAPI.Framework
this.Watchers.Reset();
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
*********/

View File

@ -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;
}
}

View File

@ -1,3 +1,3 @@
{
"warn.invalid-content-removed": "Invalid content was removed to prevent a crash (see the SMAPI console for info)."
}