From 65997c1243a60ae15cc0b832ebcd41d96c3ea06a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 1 Oct 2019 21:41:15 -0400 Subject: auto-fix save data when a custom location mod is removed --- src/SMAPI/Framework/SCore.cs | 2 +- src/SMAPI/Framework/SGame.cs | 19 +++++++++++++++++++ src/SMAPI/Patches/LoadErrorPatch.cs | 28 +++++++++++++++++++++++++++- src/SMAPI/i18n/default.json | 2 +- 4 files changed, 48 insertions(+), 3 deletions(-) (limited to 'src') 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 /// Skipping a few frames ensures the game finishes initializing the world before mods try to change it. private readonly Countdown AfterLoadTimer = new Countdown(5); + /// Whether custom content was removed from the save data to avoid a crash. + private bool IsSaveContentRemoved; + /// Whether the game is saving and SMAPI has already raised . 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)); } + /// A callback invoked when custom content is removed from the save data to avoid a crash. + internal void OnSaveContentRemoved() + { + this.IsSaveContentRemoved = true; + } + /// A callback invoked when the game's low-level load stage changes. /// The new load stage. 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 *********/ 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 /// Writes messages to the console and log file. private static IMonitor Monitor; + /// A callback invoked when custom content is removed from the save data to avoid a crash. + private static Action OnContentRemoved; + /********* ** Accessors @@ -33,9 +37,11 @@ namespace StardewModdingAPI.Patches *********/ /// Construct an instance. /// Writes messages to the console and log file. - public LoadErrorPatch(IMonitor monitor) + /// A callback invoked when custom content is removed from the save data to avoid a crash. + public LoadErrorPatch(IMonitor monitor, Action onContentRemoved) { LoadErrorPatch.Monitor = monitor; + LoadErrorPatch.OnContentRemoved = onContentRemoved; } @@ -58,6 +64,22 @@ namespace StardewModdingAPI.Patches /// Returns whether to execute the original method. private static bool Before_SaveGame_LoadDataToLocations(List 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)." } -- cgit