using System;
using System.Diagnostics.CodeAnalysis;
using HarmonyLib;
using StardewModdingAPI.Enums;
using StardewModdingAPI.Framework.Patching;
using StardewModdingAPI.Framework.Reflection;
using StardewValley;
using StardewValley.Menus;
using StardewValley.Minigames;
namespace StardewModdingAPI.Patches
{
/// Harmony patches which notify SMAPI for save creation 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 LoadContextPatch : IHarmonyPatch
{
/*********
** Fields
*********/
/// Simplifies access to private code.
private static Reflector Reflection;
/// A callback to invoke when the load stage changes.
private static Action OnStageChanged;
/// 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 LoadContextPatch(Reflector reflection, Action onStageChanged)
{
LoadContextPatch.Reflection = reflection;
LoadContextPatch.OnStageChanged = onStageChanged;
}
///
public void Apply(Harmony harmony)
{
// detect CreatedBasicInfo
harmony.Patch(
original: AccessTools.Method(typeof(TitleMenu), nameof(TitleMenu.createdNewCharacter)),
prefix: new HarmonyMethod(this.GetType(), nameof(LoadContextPatch.Before_TitleMenu_CreatedNewCharacter))
);
// detect CreatedInitialLocations and SaveAddedLocations
harmony.Patch(
original: AccessTools.Method(typeof(Game1), nameof(Game1.AddModNPCs)),
prefix: new HarmonyMethod(this.GetType(), nameof(LoadContextPatch.Before_Game1_AddModNPCs))
);
// detect CreatedLocations, and track IsInLoadForNewGame
harmony.Patch(
original: AccessTools.Method(typeof(Game1), nameof(Game1.loadForNewGame)),
prefix: new HarmonyMethod(this.GetType(), nameof(LoadContextPatch.Before_Game1_LoadForNewGame)),
postfix: new HarmonyMethod(this.GetType(), nameof(LoadContextPatch.After_Game1_LoadForNewGame))
);
// detect ReturningToTitle
harmony.Patch(
original: AccessTools.Method(typeof(Game1), nameof(Game1.CleanupReturningToTitle)),
prefix: new HarmonyMethod(this.GetType(), nameof(LoadContextPatch.Before_Game1_CleanupReturningToTitle))
);
}
/*********
** Private methods
*********/
/// Called 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_TitleMenu_CreatedNewCharacter()
{
LoadContextPatch.OnStageChanged(LoadStage.CreatedBasicInfo);
return true;
}
/// Called 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_Game1_AddModNPCs()
{
// When this method is called from Game1.loadForNewGame, it happens right after adding the vanilla
// locations but before initializing them.
if (LoadContextPatch.IsInLoadForNewGame)
{
LoadContextPatch.OnStageChanged(LoadContextPatch.IsCreating()
? LoadStage.CreatedInitialLocations
: LoadStage.SaveAddedLocations
);
}
return true;
}
/// Called 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_Game1_CleanupReturningToTitle()
{
LoadContextPatch.OnStageChanged(LoadStage.ReturningToTitle);
return true;
}
/// Called 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_Game1_LoadForNewGame()
{
LoadContextPatch.IsInLoadForNewGame = true;
return true;
}
/// Called after .
/// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.
private static void After_Game1_LoadForNewGame()
{
LoadContextPatch.IsInLoadForNewGame = false;
if (LoadContextPatch.IsCreating())
LoadContextPatch.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 && LoadContextPatch.Reflection.GetField(menu, "transitioningCharacterCreationMenu").GetValue()); // creating save, skipped intro
}
}
}