diff options
author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2019-09-26 19:48:01 -0400 |
---|---|---|
committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2019-09-26 19:48:01 -0400 |
commit | 9461494a35b789c679a799fc9c5db2321d19d803 (patch) | |
tree | de8fd404dd81c0d6c57626b2cd8331a6c38c5f77 /src/SMAPI | |
parent | 1b5055dfaafc6dcf77b5262b8290e8ca2c8179ed (diff) | |
download | SMAPI-9461494a35b789c679a799fc9c5db2321d19d803.tar.gz SMAPI-9461494a35b789c679a799fc9c5db2321d19d803.tar.bz2 SMAPI-9461494a35b789c679a799fc9c5db2321d19d803.zip |
auto-fix save data when a custom NPC mod is removed
Diffstat (limited to 'src/SMAPI')
-rw-r--r-- | src/SMAPI/Framework/SCore.cs | 3 | ||||
-rw-r--r-- | src/SMAPI/Patches/DialogueErrorPatch.cs | 9 | ||||
-rw-r--r-- | src/SMAPI/Patches/EventErrorPatch.cs | 7 | ||||
-rw-r--r-- | src/SMAPI/Patches/LoadContextPatch.cs | 7 | ||||
-rw-r--r-- | src/SMAPI/Patches/LoadErrorPatch.cs | 94 | ||||
-rw-r--r-- | src/SMAPI/Patches/ObjectErrorPatch.cs | 9 |
6 files changed, 113 insertions, 16 deletions
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index e293cefd..bc893abc 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -239,7 +239,8 @@ namespace StardewModdingAPI.Framework new EventErrorPatch(this.MonitorForGame), new DialogueErrorPatch(this.MonitorForGame, this.Reflection), new ObjectErrorPatch(), - new LoadContextPatch(this.Reflection, this.GameInstance.OnLoadStageChanged) + new LoadContextPatch(this.Reflection, this.GameInstance.OnLoadStageChanged), + new LoadErrorPatch(this.Monitor) ); // add exit handler diff --git a/src/SMAPI/Patches/DialogueErrorPatch.cs b/src/SMAPI/Patches/DialogueErrorPatch.cs index f1c25c05..24f97259 100644 --- a/src/SMAPI/Patches/DialogueErrorPatch.cs +++ b/src/SMAPI/Patches/DialogueErrorPatch.cs @@ -10,6 +10,9 @@ using StardewValley; namespace StardewModdingAPI.Patches { /// <summary>A Harmony patch for the <see cref="Dialogue"/> constructor which intercepts invalid dialogue lines and logs an error instead of crashing.</summary> + /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks> + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")] + [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")] internal class DialogueErrorPatch : IHarmonyPatch { /********* @@ -29,7 +32,7 @@ namespace StardewModdingAPI.Patches ** Accessors *********/ /// <summary>A unique name for this patch.</summary> - public string Name => $"{nameof(DialogueErrorPatch)}"; + public string Name => nameof(DialogueErrorPatch); /********* @@ -68,8 +71,6 @@ namespace StardewModdingAPI.Patches /// <param name="masterDialogue">The dialogue being parsed.</param> /// <param name="speaker">The NPC for which the dialogue is being parsed.</param> /// <returns>Returns whether to execute the original method.</returns> - /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks> - [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")] private static bool Before_Dialogue_Constructor(Dialogue __instance, string masterDialogue, NPC speaker) { // get private members @@ -109,8 +110,6 @@ namespace StardewModdingAPI.Patches /// <param name="__result">The return value of the original method.</param> /// <param name="__originalMethod">The method being wrapped.</param> /// <returns>Returns whether to execute the original method.</returns> - /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks> - [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")] private static bool Before_NPC_CurrentDialogue(NPC __instance, ref Stack<Dialogue> __result, MethodInfo __originalMethod) { if (DialogueErrorPatch.IsInterceptingCurrentDialogue) diff --git a/src/SMAPI/Patches/EventErrorPatch.cs b/src/SMAPI/Patches/EventErrorPatch.cs index cd530616..1dc7e8c3 100644 --- a/src/SMAPI/Patches/EventErrorPatch.cs +++ b/src/SMAPI/Patches/EventErrorPatch.cs @@ -7,6 +7,9 @@ using StardewValley; namespace StardewModdingAPI.Patches { /// <summary>A Harmony patch for the <see cref="Dialogue"/> constructor which intercepts invalid dialogue lines and logs an error instead of crashing.</summary> + /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks> + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")] + [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")] internal class EventErrorPatch : IHarmonyPatch { /********* @@ -23,7 +26,7 @@ namespace StardewModdingAPI.Patches ** Accessors *********/ /// <summary>A unique name for this patch.</summary> - public string Name => $"{nameof(EventErrorPatch)}"; + public string Name => nameof(EventErrorPatch); /********* @@ -56,8 +59,6 @@ namespace StardewModdingAPI.Patches /// <param name="precondition">The precondition to be parsed.</param> /// <param name="__originalMethod">The method being wrapped.</param> /// <returns>Returns whether to execute the original method.</returns> - /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks> - [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")] private static bool Before_GameLocation_CheckEventPrecondition(GameLocation __instance, ref int __result, string precondition, MethodInfo __originalMethod) { if (EventErrorPatch.IsIntercepted) diff --git a/src/SMAPI/Patches/LoadContextPatch.cs b/src/SMAPI/Patches/LoadContextPatch.cs index 93a059aa..0cc8c8eb 100644 --- a/src/SMAPI/Patches/LoadContextPatch.cs +++ b/src/SMAPI/Patches/LoadContextPatch.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using Harmony; using StardewModdingAPI.Enums; using StardewModdingAPI.Framework.Patching; @@ -10,7 +11,9 @@ using StardewValley.Minigames; namespace StardewModdingAPI.Patches { /// <summary>Harmony patches which notify SMAPI for save creation load stages.</summary> - /// <remarks>This patch hooks into <see cref="Game1.loadForNewGame"/>, checks if <c>TitleMenu.transitioningCharacterCreationMenu</c> is true (which means the player is creating a new save file), then raises <see cref="LoadStage.CreatedBasicInfo"/> after the location list is cleared twice (the second clear happens right before locations are created), and <see cref="LoadStage.CreatedLocations"/> when the method ends.</remarks> + /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks> + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")] + [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")] internal class LoadContextPatch : IHarmonyPatch { /********* @@ -27,7 +30,7 @@ namespace StardewModdingAPI.Patches ** Accessors *********/ /// <summary>A unique name for this patch.</summary> - public string Name => $"{nameof(LoadContextPatch)}"; + public string Name => nameof(LoadContextPatch); /********* diff --git a/src/SMAPI/Patches/LoadErrorPatch.cs b/src/SMAPI/Patches/LoadErrorPatch.cs new file mode 100644 index 00000000..87e8ee14 --- /dev/null +++ b/src/SMAPI/Patches/LoadErrorPatch.cs @@ -0,0 +1,94 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Harmony; +using StardewModdingAPI.Framework.Patching; +using StardewValley; +using StardewValley.Locations; + +namespace StardewModdingAPI.Patches +{ + /// <summary>A Harmony patch for <see cref="SaveGame"/> which prevents some errors due to broken save data.</summary> + /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks> + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")] + [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")] + internal class LoadErrorPatch : IHarmonyPatch + { + /********* + ** Fields + *********/ + /// <summary>Writes messages to the console and log file.</summary> + private static IMonitor Monitor; + + + /********* + ** Accessors + *********/ + /// <summary>A unique name for this patch.</summary> + public string Name => nameof(LoadErrorPatch); + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="monitor">Writes messages to the console and log file.</param> + public LoadErrorPatch(IMonitor monitor) + { + LoadErrorPatch.Monitor = monitor; + } + + + /// <summary>Apply the Harmony patch.</summary> + /// <param name="harmony">The Harmony instance.</param> + public void Apply(HarmonyInstance harmony) + { + harmony.Patch( + original: AccessTools.Method(typeof(SaveGame), nameof(SaveGame.loadDataToLocations)), + prefix: new HarmonyMethod(this.GetType(), nameof(LoadErrorPatch.Before_SaveGame_LoadDataToLocations)) + ); + } + + + /********* + ** Private methods + *********/ + /// <summary>The method to call instead of <see cref="SaveGame.loadDataToLocations"/>.</summary> + /// <param name="gamelocations">The game locations being loaded.</param> + /// <returns>Returns whether to execute the original method.</returns> + private static bool Before_SaveGame_LoadDataToLocations(List<GameLocation> gamelocations) + { + // get building interiors + var interiors = + ( + from location in gamelocations.OfType<BuildableGameLocation>() + from building in location.buildings + where building.indoors.Value != null + select building.indoors.Value + ); + + // remove custom NPCs which no longer exist + IDictionary<string, string> data = Game1.content.Load<Dictionary<string, string>>("Data\\NPCDispositions"); + foreach (GameLocation location in gamelocations.Concat(interiors)) + { + foreach (NPC npc in location.characters.ToArray()) + { + if (npc.isVillager() && !data.ContainsKey(npc.Name)) + { + try + { + npc.reloadSprite(); // this won't crash for special villagers like Bouncer + } + catch + { + 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); + } + } + } + } + + return true; + } + } +} diff --git a/src/SMAPI/Patches/ObjectErrorPatch.cs b/src/SMAPI/Patches/ObjectErrorPatch.cs index 5b918d39..d716b29b 100644 --- a/src/SMAPI/Patches/ObjectErrorPatch.cs +++ b/src/SMAPI/Patches/ObjectErrorPatch.cs @@ -8,13 +8,16 @@ using SObject = StardewValley.Object; namespace StardewModdingAPI.Patches { /// <summary>A Harmony patch for <see cref="SObject.getDescription"/> which intercepts crashes due to the item no longer existing.</summary> + /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks> + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")] + [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")] internal class ObjectErrorPatch : IHarmonyPatch { /********* ** Accessors *********/ /// <summary>A unique name for this patch.</summary> - public string Name => $"{nameof(ObjectErrorPatch)}"; + public string Name => nameof(ObjectErrorPatch); /********* @@ -45,8 +48,6 @@ namespace StardewModdingAPI.Patches /// <param name="__instance">The instance being patched.</param> /// <param name="__result">The patched method's return value.</param> /// <returns>Returns whether to execute the original method.</returns> - /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks> - [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")] private static bool Before_Object_GetDescription(SObject __instance, ref string __result) { // invalid bigcraftables crash instead of showing '???' like invalid non-bigcraftables @@ -63,8 +64,6 @@ namespace StardewModdingAPI.Patches /// <param name="__instance">The instance being patched.</param> /// <param name="hoveredItem">The item for which to draw a tooltip.</param> /// <returns>Returns whether to execute the original method.</returns> - /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks> - [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony.")] private static bool Before_IClickableMenu_DrawTooltip(IClickableMenu __instance, Item hoveredItem) { // invalid edible item cause crash when drawing tooltips |