auto-fix save data when a custom NPC mod is removed
This commit is contained in:
parent
1b5055dfaa
commit
9461494a35
|
@ -20,10 +20,13 @@ doesn't change any of your game files. It serves eight main purposes:
|
|||
many mods, and rewrites the mod so it's compatible._
|
||||
|
||||
5. **Intercept errors.**
|
||||
_SMAPI intercepts errors that happen in the game, displays the error details in the console
|
||||
window, and in most cases automatically recovers the game. This prevents mods from accidentally
|
||||
crashing the game, and makes it possible to troubleshoot errors in the game itself that would
|
||||
otherwise show a generic 'program has stopped working' type of message._
|
||||
_SMAPI intercepts errors, shows the error info in the SMAPI console, and in most cases
|
||||
automatically recovers the game. That prevents mods from crashing the game, and makes it
|
||||
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._
|
||||
|
||||
6. **Provide update checks.**
|
||||
_SMAPI automatically checks for new versions of your installed mods, and notifies you when any
|
||||
|
|
|
@ -8,12 +8,16 @@ These changes have not been released yet.
|
|||
For players:
|
||||
* **Updated for Stardew Valley 1.4.**
|
||||
SMAPI 3.0 adds compatibility with the latest game version, and improves mod APIs using changes in the game code.
|
||||
|
||||
* **Improved performance.**
|
||||
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.
|
||||
SMAPI now detects and prevents more crashes due to game or mod bugs, or due to removing some mods which add custom content.
|
||||
|
||||
* **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.
|
||||
|
||||
* **Fixed many bugs and edge cases.**
|
||||
|
||||
For modders:
|
||||
|
@ -39,11 +43,12 @@ For modders:
|
|||
* Changes:
|
||||
* Updated for Stardew Valley 1.4.
|
||||
* Improved performance.
|
||||
* Rewrote launch script on Linux to improve compatibility (thanks to kurumushi and toastal!).
|
||||
* 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.)
|
||||
* 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.
|
||||
* The installer now recognises custom game paths stored in `stardewvalley.targets`.
|
||||
* Duplicate-mod errors now show the mod version in each folder.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
/*********
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue