using System;
using System.Diagnostics.CodeAnalysis;
using HarmonyLib;
using StardewModdingAPI.Enums;
using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Internal.Patching;
using StardewValley;
using StardewValley.Menus;
using StardewValley.Minigames;
namespace StardewModdingAPI.Patches
{
/// Harmony patches for which notify SMAPI for save load stages.
/// Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.
[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 Game1Patcher : BasePatcher
{
/*********
** Fields
*********/
/// Simplifies access to private code.
private static Reflector Reflection = null!; // initialized in constructor
/// A callback to invoke when the load stage changes.
private static Action OnStageChanged = null!; // initialized in constructor
/// Whether the game is running running the code in .
private static bool IsInLoadForNewGame;
/*********
** Public methods
*********/
/// Construct an instance.
/// Simplifies access to private code.
/// A callback to invoke when the load stage changes.
public Game1Patcher(Reflector reflection, Action onStageChanged)
{
Game1Patcher.Reflection = reflection;
Game1Patcher.OnStageChanged = onStageChanged;
}
///
public override void Apply(Harmony harmony, IMonitor monitor)
{
// detect CreatedInitialLocations and SaveAddedLocations
harmony.Patch(
original: this.RequireMethod(nameof(Game1.AddModNPCs)),
prefix: this.GetHarmonyMethod(nameof(Game1Patcher.Before_AddModNpcs))
);
// detect CreatedLocations, and track IsInLoadForNewGame
harmony.Patch(
original: this.RequireMethod(nameof(Game1.loadForNewGame)),
prefix: this.GetHarmonyMethod(nameof(Game1Patcher.Before_LoadForNewGame)),
postfix: this.GetHarmonyMethod(nameof(Game1Patcher.After_LoadForNewGame))
);
// detect ReturningToTitle
harmony.Patch(
original: this.RequireMethod(nameof(Game1.CleanupReturningToTitle)),
prefix: this.GetHarmonyMethod(nameof(Game1Patcher.Before_CleanupReturningToTitle))
);
}
/*********
** Private methods
*********/
/// The method to call before .
/// Returns whether to execute the original method.
/// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.
private static bool Before_AddModNpcs()
{
// When this method is called from Game1.loadForNewGame, it happens right after adding the vanilla
// locations but before initializing them.
if (Game1Patcher.IsInLoadForNewGame)
{
Game1Patcher.OnStageChanged(Game1Patcher.IsCreating()
? LoadStage.CreatedInitialLocations
: LoadStage.SaveAddedLocations
);
}
return true;
}
/// The method to call before .
/// Returns whether to execute the original method.
/// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.
private static bool Before_CleanupReturningToTitle()
{
Game1Patcher.OnStageChanged(LoadStage.ReturningToTitle);
return true;
}
/// The method to call before .
/// Returns whether to execute the original method.
/// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.
private static bool Before_LoadForNewGame()
{
Game1Patcher.IsInLoadForNewGame = true;
return true;
}
/// The method to call after .
/// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.
private static void After_LoadForNewGame()
{
Game1Patcher.IsInLoadForNewGame = false;
if (Game1Patcher.IsCreating())
Game1Patcher.OnStageChanged(LoadStage.CreatedLocations);
}
/// Get whether the save file is currently being created.
private static bool IsCreating()
{
return
(Game1.currentMinigame is Intro) // creating save with intro
|| (Game1.activeClickableMenu is TitleMenu menu && Game1Patcher.Reflection.GetField(menu, "transitioningCharacterCreationMenu").GetValue()); // creating save, skipped intro
}
}
}