summaryrefslogtreecommitdiff
path: root/src/SMAPI
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2019-09-26 19:48:01 -0400
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2019-09-26 19:48:01 -0400
commit9461494a35b789c679a799fc9c5db2321d19d803 (patch)
treede8fd404dd81c0d6c57626b2cd8331a6c38c5f77 /src/SMAPI
parent1b5055dfaafc6dcf77b5262b8290e8ca2c8179ed (diff)
downloadSMAPI-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.cs3
-rw-r--r--src/SMAPI/Patches/DialogueErrorPatch.cs9
-rw-r--r--src/SMAPI/Patches/EventErrorPatch.cs7
-rw-r--r--src/SMAPI/Patches/LoadContextPatch.cs7
-rw-r--r--src/SMAPI/Patches/LoadErrorPatch.cs94
-rw-r--r--src/SMAPI/Patches/ObjectErrorPatch.cs9
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