Compare commits
2 Commits
develop
...
experiment
Author | SHA1 | Date |
---|---|---|
Jesse Plamondon-Willard | e16584527c | |
Jesse Plamondon-Willard | 90ca3d6ba1 |
|
@ -11,6 +11,7 @@
|
||||||
* Added `IsLocalPlayer` to new player events.
|
* Added `IsLocalPlayer` to new player events.
|
||||||
* Reloading a map asset will now update affected locations.
|
* Reloading a map asset will now update affected locations.
|
||||||
* Reloading the `Data\NPCDispositions` asset will now update affected NPCs.
|
* Reloading the `Data\NPCDispositions` asset will now update affected NPCs.
|
||||||
|
* Improved how SMAPI events work at the end of day. Events are now raised correctly while end-of-day menus like the shipping menu are shown, and `BeforeSave` is now raised immediately before save instead of when the shipping menu is opened.
|
||||||
* Fixed some map tilesheets not editable if not playing in English.
|
* Fixed some map tilesheets not editable if not playing in English.
|
||||||
* Fixed newlines in most manifest fields not being ignored.
|
* Fixed newlines in most manifest fields not being ignored.
|
||||||
* Fixed `Display.RenderedWorld` event not invoked before overlays are rendered.
|
* Fixed `Display.RenderedWorld` event not invoked before overlays are rendered.
|
||||||
|
|
|
@ -166,11 +166,6 @@ namespace StardewModdingAPI.Framework
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// apply game patches
|
|
||||||
new GamePatcher(this.Monitor).Apply(
|
|
||||||
new DialogueErrorPatch(this.MonitorForGame, this.Reflection)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Launch SMAPI.</summary>
|
/// <summary>Launch SMAPI.</summary>
|
||||||
|
@ -284,6 +279,12 @@ namespace StardewModdingAPI.Framework
|
||||||
File.Delete(Constants.FatalCrashMarker);
|
File.Delete(Constants.FatalCrashMarker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// apply game patches
|
||||||
|
new GamePatcher(this.Monitor).Apply(
|
||||||
|
new DialogueErrorPatch(this.MonitorForGame, this.Reflection),
|
||||||
|
new SaveGamePatch(onSaving: this.GameInstance.OnSaving)
|
||||||
|
);
|
||||||
|
|
||||||
// start game
|
// start game
|
||||||
this.Monitor.Log("Starting game...", LogLevel.Debug);
|
this.Monitor.Log("Starting game...", LogLevel.Debug);
|
||||||
try
|
try
|
||||||
|
|
|
@ -199,11 +199,61 @@ namespace StardewModdingAPI.Framework
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>A callback invoked before <see cref="Game1.newDayAfterFade"/> runs.</summary>
|
/// <summary>A callback invoked before <see cref="Game1.newDayAfterFade"/> runs.</summary>
|
||||||
protected void OnNewDayAfterFade()
|
private void OnNewDayAfterFade()
|
||||||
{
|
{
|
||||||
this.Events.DayEnding.RaiseEmpty();
|
this.Events.DayEnding.RaiseEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>A callback invoked before <see cref="SaveGame.Save"/> runs.</summary>
|
||||||
|
internal void OnSaving()
|
||||||
|
{
|
||||||
|
// raise events
|
||||||
|
if (!Context.IsWorldReady)
|
||||||
|
{
|
||||||
|
this.IsBetweenCreateEvents = true;
|
||||||
|
this.Monitor.Log("Context: before save creation.", LogLevel.Trace);
|
||||||
|
this.Events.SaveCreating.RaiseEmpty();
|
||||||
|
#if !SMAPI_3_0_STRICT
|
||||||
|
this.Events.Legacy_BeforeCreateSave.Raise();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.IsBetweenSaveEvents = true;
|
||||||
|
this.Monitor.Log("Context: before save.", LogLevel.Trace);
|
||||||
|
this.Events.Saving.RaiseEmpty();
|
||||||
|
#if !SMAPI_3_0_STRICT
|
||||||
|
this.Events.Legacy_BeforeSave.Raise();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>A callback invoked after <see cref="SaveGame.Save"/> runs.</summary>
|
||||||
|
private void OnSaved()
|
||||||
|
{
|
||||||
|
// reset flags
|
||||||
|
this.IsBetweenCreateEvents = false;
|
||||||
|
this.IsBetweenSaveEvents = false;
|
||||||
|
|
||||||
|
// raise events
|
||||||
|
if (!Context.IsWorldReady)
|
||||||
|
{
|
||||||
|
this.Monitor.Log($"Context: after save creation, starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace);
|
||||||
|
this.Events.SaveCreated.RaiseEmpty();
|
||||||
|
#if !SMAPI_3_0_STRICT
|
||||||
|
this.Events.Legacy_AfterCreateSave.Raise();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.Monitor.Log($"Context: after save, starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace);
|
||||||
|
this.Events.Saved.RaiseEmpty();
|
||||||
|
#if !SMAPI_3_0_STRICT
|
||||||
|
this.Events.Legacy_AfterSave.Raise();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>A callback invoked when a mod message is received.</summary>
|
/// <summary>A callback invoked when a mod message is received.</summary>
|
||||||
/// <param name="message">The message to deliver to applicable mods.</param>
|
/// <param name="message">The message to deliver to applicable mods.</param>
|
||||||
private void OnModMessageReceived(ModMessageModel message)
|
private void OnModMessageReceived(ModMessageModel message)
|
||||||
|
@ -362,67 +412,26 @@ namespace StardewModdingAPI.Framework
|
||||||
inputState.TrueUpdate();
|
inputState.TrueUpdate();
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
** Save events + suppress events during save
|
** Suppress events during save
|
||||||
*********/
|
*********/
|
||||||
// While the game is writing to the save file in the background, mods can unexpectedly
|
// While the game is writing to the save file in the background, mods can unexpectedly
|
||||||
// fail since they don't have exclusive access to resources (e.g. collection changed
|
// fail since they don't have exclusive access to resources (e.g. collection changed
|
||||||
// during enumeration errors). To avoid problems, events are not invoked while a save
|
// during enumeration errors). To avoid problems, events are not invoked while a save
|
||||||
// is in progress. It's safe to raise SaveEvents.BeforeSave as soon as the menu is
|
// is in progress.
|
||||||
// opened (since the save hasn't started yet), but all other events should be suppressed.
|
if (this.IsBetweenCreateEvents || this.IsBetweenSaveEvents)
|
||||||
if (Context.IsSaving)
|
|
||||||
{
|
{
|
||||||
// raise before-create
|
if (!Context.IsSaving)
|
||||||
if (!Context.IsWorldReady && !this.IsBetweenCreateEvents)
|
this.OnSaved();
|
||||||
|
else
|
||||||
{
|
{
|
||||||
this.IsBetweenCreateEvents = true;
|
this.Events.UnvalidatedUpdateTicking.Raise(new UnvalidatedUpdateTickingEventArgs(this.TicksElapsed));
|
||||||
this.Monitor.Log("Context: before save creation.", LogLevel.Trace);
|
base.Update(gameTime);
|
||||||
this.Events.SaveCreating.RaiseEmpty();
|
this.Events.UnvalidatedUpdateTicked.Raise(new UnvalidatedUpdateTickedEventArgs(this.TicksElapsed));
|
||||||
#if !SMAPI_3_0_STRICT
|
#if !SMAPI_3_0_STRICT
|
||||||
this.Events.Legacy_BeforeCreateSave.Raise();
|
this.Events.Legacy_BeforeCreateSave.Raise();
|
||||||
#endif
|
#endif
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// raise before-save
|
|
||||||
if (Context.IsWorldReady && !this.IsBetweenSaveEvents)
|
|
||||||
{
|
|
||||||
this.IsBetweenSaveEvents = true;
|
|
||||||
this.Monitor.Log("Context: before save.", LogLevel.Trace);
|
|
||||||
this.Events.Saving.RaiseEmpty();
|
|
||||||
#if !SMAPI_3_0_STRICT
|
|
||||||
this.Events.Legacy_BeforeSave.Raise();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// suppress non-save events
|
|
||||||
this.Events.UnvalidatedUpdateTicking.Raise(new UnvalidatedUpdateTickingEventArgs(this.TicksElapsed));
|
|
||||||
base.Update(gameTime);
|
|
||||||
this.Events.UnvalidatedUpdateTicked.Raise(new UnvalidatedUpdateTickedEventArgs(this.TicksElapsed));
|
|
||||||
#if !SMAPI_3_0_STRICT
|
|
||||||
this.Events.Legacy_UnvalidatedUpdateTick.Raise();
|
|
||||||
#endif
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.IsBetweenCreateEvents)
|
|
||||||
{
|
|
||||||
// raise after-create
|
|
||||||
this.IsBetweenCreateEvents = false;
|
|
||||||
this.Monitor.Log($"Context: after save creation, starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace);
|
|
||||||
this.Events.SaveCreated.RaiseEmpty();
|
|
||||||
#if !SMAPI_3_0_STRICT
|
|
||||||
this.Events.Legacy_AfterCreateSave.Raise();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
if (this.IsBetweenSaveEvents)
|
|
||||||
{
|
|
||||||
// raise after-save
|
|
||||||
this.IsBetweenSaveEvents = false;
|
|
||||||
this.Monitor.Log($"Context: after save, starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace);
|
|
||||||
this.Events.Saved.RaiseEmpty();
|
|
||||||
this.Events.DayStarted.RaiseEmpty();
|
|
||||||
#if !SMAPI_3_0_STRICT
|
|
||||||
this.Events.Legacy_AfterSave.Raise();
|
|
||||||
this.Events.Legacy_AfterDayStarted.Raise();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Reflection;
|
||||||
|
using Harmony;
|
||||||
|
using StardewModdingAPI.Framework.Patching;
|
||||||
|
using StardewValley;
|
||||||
|
|
||||||
|
namespace StardewModdingAPI.Patches
|
||||||
|
{
|
||||||
|
/// <summary>A Harmony patch for <see cref="SaveGame.Save"/> to detect when the game is saving.</summary>
|
||||||
|
internal class SaveGamePatch : IHarmonyPatch
|
||||||
|
{
|
||||||
|
/*********
|
||||||
|
** Private methods
|
||||||
|
*********/
|
||||||
|
/// <summary>A callback to invoke before <see cref="SaveGame.Save"/> runs.</summary>
|
||||||
|
private static Action OnSaving;
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Accessors
|
||||||
|
*********/
|
||||||
|
/// <summary>A unique name for this patch.</summary>
|
||||||
|
public string Name => $"{nameof(SaveGamePatch)}";
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Public methods
|
||||||
|
*********/
|
||||||
|
/// <summary>Construct an instance.</summary>
|
||||||
|
/// <param name="onSaving">A callback to invoke before <see cref="SaveGame.Save"/> runs.</param>
|
||||||
|
public SaveGamePatch(Action onSaving)
|
||||||
|
{
|
||||||
|
SaveGamePatch.OnSaving = onSaving;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>Apply the Harmony patch.</summary>
|
||||||
|
/// <param name="harmony">The Harmony instance.</param>
|
||||||
|
public void Apply(HarmonyInstance harmony)
|
||||||
|
{
|
||||||
|
MethodInfo method = AccessTools.Method(typeof(SaveGame), nameof(SaveGame.Save));
|
||||||
|
MethodInfo prefix = AccessTools.Method(this.GetType(), nameof(SaveGamePatch.Prefix));
|
||||||
|
|
||||||
|
harmony.Patch(method, prefix: new HarmonyMethod(prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
** Private methods
|
||||||
|
*********/
|
||||||
|
/// <summary>The method to call before <see cref="SaveGame.Save"/>.</summary>
|
||||||
|
/// <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 Prefix()
|
||||||
|
{
|
||||||
|
SaveGamePatch.OnSaving();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -323,6 +323,7 @@
|
||||||
<Compile Include="Framework\Monitor.cs" />
|
<Compile Include="Framework\Monitor.cs" />
|
||||||
<Compile Include="Metadata\InstructionMetadata.cs" />
|
<Compile Include="Metadata\InstructionMetadata.cs" />
|
||||||
<Compile Include="Mod.cs" />
|
<Compile Include="Mod.cs" />
|
||||||
|
<Compile Include="Patches\SaveGamePatch.cs" />
|
||||||
<Compile Include="Patches\DialogueErrorPatch.cs" />
|
<Compile Include="Patches\DialogueErrorPatch.cs" />
|
||||||
<Compile Include="PatchMode.cs" />
|
<Compile Include="PatchMode.cs" />
|
||||||
<Compile Include="GamePlatform.cs" />
|
<Compile Include="GamePlatform.cs" />
|
||||||
|
|
Loading…
Reference in New Issue