using System; using System.Diagnostics.CodeAnalysis; #if HARMONY_2 using HarmonyLib; #else using Harmony; #endif 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; /********* ** Accessors *********/ /// public string Name => nameof(LoadContextPatch); /********* ** 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; } /// #if HARMONY_2 public void Apply(Harmony harmony) #else public void Apply(HarmonyInstance harmony) #endif { // 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)) ); } /********* ** 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_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 } } }