From 0e304e4d51857e3c7dc9cd18141176e44934755c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 7 May 2017 01:56:39 -0400 Subject: added basic context logging to simplify troubleshooting --- src/StardewModdingAPI/Framework/SGame.cs | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index 1f2bf3ac..dbc257e1 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -1118,6 +1118,8 @@ namespace StardewModdingAPI.Framework { if (this.AfterLoadTimer == 0) { + this.Monitor.Log($"Context: loaded saved game '{Constants.SaveFolderName}'.", LogLevel.Trace); + SaveEvents.InvokeAfterLoad(this.Monitor); PlayerEvents.InvokeLoadedGame(this.Monitor, new EventArgsLoadedGameChanged(Game1.hasLoadedGame)); TimeEvents.InvokeAfterDayStarted(this.Monitor); @@ -1132,6 +1134,7 @@ namespace StardewModdingAPI.Framework // after exit to title if (this.IsWorldReady && this.IsExiting && Game1.activeClickableMenu is TitleMenu) { + this.Monitor.Log("Context: returned to title", LogLevel.Trace); SaveEvents.InvokeAfterReturnToTitle(this.Monitor); this.AfterLoadTimer = 5; this.IsExiting = false; @@ -1199,9 +1202,13 @@ namespace StardewModdingAPI.Framework // raise save events // (saving is performed by SaveGameMenu; on days when the player shipping something, ShippingMenu wraps SaveGameMenu) if (newMenu is SaveGameMenu || newMenu is ShippingMenu) + { + this.Monitor.Log("Context: before save.", LogLevel.Trace); SaveEvents.InvokeBeforeSave(this.Monitor); + } else if (previousMenu is SaveGameMenu || previousMenu is ShippingMenu) { + this.Monitor.Log("Context: after save, starting new day.", LogLevel.Trace); SaveEvents.InvokeAfterSave(this.Monitor); TimeEvents.InvokeAfterDayStarted(this.Monitor); } -- cgit From 8963793bf854bee368b5cd08a3e0eb410ee8e1a9 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 7 May 2017 02:50:36 -0400 Subject: exit game after many consecutive unrecoverable draw errors (#283) --- src/StardewModdingAPI/Framework/SGame.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index dbc257e1..f8226529 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -34,6 +34,12 @@ namespace StardewModdingAPI.Framework /// Skipping a few frames ensures the game finishes initialising the world before mods try to change it. private int AfterLoadTimer = 5; + /// The maximum number of consecutive attempts SMAPI should make to recover from a draw error. + private readonly int MaxFailedDraws = 120; // roughly two seconds + + /// The number of consecutive failed draws. + private int FailedDraws = 0; + /// Whether the player has loaded a save and the world has finished initialising. private bool IsWorldReady => this.AfterLoadTimer < 0; @@ -944,9 +950,20 @@ namespace StardewModdingAPI.Framework } } } + + // reset failed draw count + this.FailedDraws = 0; } catch (Exception ex) { + // exit if irrecoverable + if (this.FailedDraws >= this.MaxFailedDraws) + { + this.Monitor.ExitGameImmediately("the game crashed when drawing, and SMAPI was unable to recover the game."); + return; + } + this.FailedDraws++; + // log error this.Monitor.Log($"An error occured in the overridden draw loop: {ex.GetLogSummary()}", LogLevel.Error); -- cgit From 624840efe5f3d4135dafeb2939b182cfeb4ec6c3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 7 May 2017 13:09:32 -0400 Subject: use more robust sprite batch recovery logic (#283) --- .../Framework/InternalExtensions.cs | 22 ++++++++++++++++++ src/StardewModdingAPI/Framework/SGame.cs | 27 +++++++++++----------- 2 files changed, 36 insertions(+), 13 deletions(-) (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/InternalExtensions.cs b/src/StardewModdingAPI/Framework/InternalExtensions.cs index 5199c72d..cadf6598 100644 --- a/src/StardewModdingAPI/Framework/InternalExtensions.cs +++ b/src/StardewModdingAPI/Framework/InternalExtensions.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using Microsoft.Xna.Framework.Graphics; +using StardewValley; namespace StardewModdingAPI.Framework { @@ -128,5 +130,25 @@ namespace StardewModdingAPI.Framework deprecationManager.Warn(modName, nounPhrase, version, severity); } } + + /**** + ** Sprite batch + ****/ + /// Get whether the sprite batch is between a begin and end pair. + /// The sprite batch to check. + /// The reflection helper with which to access private fields. + public static bool IsOpen(this SpriteBatch spriteBatch, IReflectionHelper reflection) + { + // get field name + const string fieldName = +#if SMAPI_FOR_WINDOWS + "inBeginEndPair"; +#else + "_beginCalled"; +#endif + + // get result + return reflection.GetPrivateValue(Game1.spriteBatch, fieldName); + } } } diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index f8226529..7dae937b 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -38,7 +38,7 @@ namespace StardewModdingAPI.Framework private readonly int MaxFailedDraws = 120; // roughly two seconds /// The number of consecutive failed draws. - private int FailedDraws = 0; + private int FailedDraws; /// Whether the player has loaded a save and the world has finished initialising. private bool IsWorldReady => this.AfterLoadTimer < 0; @@ -956,6 +956,9 @@ namespace StardewModdingAPI.Framework } catch (Exception ex) { + // log error + this.Monitor.Log($"An error occured in the overridden draw loop: {ex.GetLogSummary()}", LogLevel.Error); + // exit if irrecoverable if (this.FailedDraws >= this.MaxFailedDraws) { @@ -964,22 +967,20 @@ namespace StardewModdingAPI.Framework } this.FailedDraws++; - // log error - this.Monitor.Log($"An error occured in the overridden draw loop: {ex.GetLogSummary()}", LogLevel.Error); - - // fix sprite batch + // recover sprite batch try { - bool isSpriteBatchOpen = -#if SMAPI_FOR_WINDOWS - SGame.Reflection.GetPrivateValue(Game1.spriteBatch, "inBeginEndPair"); -#else - SGame.Reflection.GetPrivateValue(Game1.spriteBatch, "_beginCalled"); -#endif - if (isSpriteBatchOpen) + if (Game1.spriteBatch.IsOpen(SGame.Reflection)) { this.Monitor.Log("Recovering sprite batch from error...", LogLevel.Trace); - Game1.spriteBatch.End(); + try + { + Game1.spriteBatch.End(); + } + catch + { + Game1.spriteBatch = new SpriteBatch(this.GraphicsDevice); // sprite batch is broken, try replacing it + } } } catch (Exception innerEx) -- cgit From 72a0b4fc6d268c67c3fba171793d5864f9710276 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 8 May 2017 01:57:07 -0400 Subject: detect unrecoverable draw errors (#283) --- src/StardewModdingAPI/Framework/SGame.cs | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index 7dae937b..6932af2a 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -967,6 +967,13 @@ namespace StardewModdingAPI.Framework } this.FailedDraws++; + // abort in known unrecoverable cases + if (Game1.toolSpriteSheet?.IsDisposed == true) + { + this.Monitor.ExitGameImmediately("the game unexpectedly disposed the tool spritesheet, so it crashed trying to draw a tool. This is a known bug in Stardew Valley 1.2.29, and there's no way to recover from it."); + return; + } + // recover sprite batch try { -- cgit From 85f609dc6c2f02d89b9fccaacfe837f8822d6b7c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 8 May 2017 02:18:58 -0400 Subject: add optional verbose context logging --- src/StardewModdingAPI/Framework/Models/SConfig.cs | 3 +++ src/StardewModdingAPI/Framework/SGame.cs | 32 +++++++++++++++++++---- 2 files changed, 30 insertions(+), 5 deletions(-) (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/Models/SConfig.cs b/src/StardewModdingAPI/Framework/Models/SConfig.cs index 0de96297..c3f0816e 100644 --- a/src/StardewModdingAPI/Framework/Models/SConfig.cs +++ b/src/StardewModdingAPI/Framework/Models/SConfig.cs @@ -12,6 +12,9 @@ /// Whether to check if a newer version of SMAPI is available on startup. public bool CheckForUpdates { get; set; } = true; + /// Whether SMAPI should log more information about the game context. + public bool VerboseLogging { get; set; } = false; + /// A list of mod versions which should be considered compatible or incompatible regardless of whether SMAPI detects incompatible code. public ModCompatibility[] ModCompatibility { get; set; } } diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index 6932af2a..d248c3ca 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -30,13 +30,13 @@ namespace StardewModdingAPI.Framework /**** ** SMAPI state ****/ + /// The maximum number of consecutive attempts SMAPI should make to recover from a draw error. + private readonly int MaxFailedDraws = 120; // roughly two seconds + /// The number of ticks until SMAPI should notify mods that the game has loaded. /// Skipping a few frames ensures the game finishes initialising the world before mods try to change it. private int AfterLoadTimer = 5; - /// The maximum number of consecutive attempts SMAPI should make to recover from a draw error. - private readonly int MaxFailedDraws = 120; // roughly two seconds - /// The number of consecutive failed draws. private int FailedDraws; @@ -176,6 +176,12 @@ namespace StardewModdingAPI.Framework private readonly Action renderScreenBuffer = () => SGame.Reflection.GetPrivateMethod(SGame.Instance, nameof(renderScreenBuffer)).Invoke(new object[0]); // ReSharper restore ArrangeStaticMemberQualifier, ArrangeThisQualifier, InconsistentNaming + /********* + ** Accessors + *********/ + /// Whether SMAPI should log more information about the game context. + public bool VerboseLogging { get; set; } + /********* ** Protected methods @@ -1133,6 +1139,9 @@ namespace StardewModdingAPI.Framework var oldValue = this.PreviousLocale; var newValue = LocalizedContentManager.CurrentLanguageCode; + if (this.VerboseLogging) + this.Monitor.Log($"Context: locale set to {newValue}.", LogLevel.Trace); + if (oldValue != null) ContentEvents.InvokeAfterLocaleChanged(this.Monitor, oldValue.ToString(), newValue.ToString()); this.PreviousLocale = newValue; @@ -1143,7 +1152,7 @@ namespace StardewModdingAPI.Framework { if (this.AfterLoadTimer == 0) { - this.Monitor.Log($"Context: loaded saved game '{Constants.SaveFolderName}'.", LogLevel.Trace); + this.Monitor.Log($"Context: loaded saved game '{Constants.SaveFolderName}', starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace); SaveEvents.InvokeAfterLoad(this.Monitor); PlayerEvents.InvokeLoadedGame(this.Monitor, new EventArgsLoadedGameChanged(Game1.hasLoadedGame)); @@ -1224,6 +1233,17 @@ namespace StardewModdingAPI.Framework IClickableMenu previousMenu = this.PreviousActiveMenu; IClickableMenu newMenu = Game1.activeClickableMenu; + // log context + if (this.VerboseLogging) + { + if (previousMenu == null) + this.Monitor.Log($"Context: opened menu {newMenu?.GetType().FullName ?? "(none)"}.", LogLevel.Trace); + else if (newMenu == null) + this.Monitor.Log($"Context: closed menu {previousMenu.GetType().FullName}.", LogLevel.Trace); + else + this.Monitor.Log($"Context: changed menu from {previousMenu.GetType().FullName} to {newMenu.GetType().FullName}.", LogLevel.Trace); + } + // raise save events // (saving is performed by SaveGameMenu; on days when the player shipping something, ShippingMenu wraps SaveGameMenu) if (newMenu is SaveGameMenu || newMenu is ShippingMenu) @@ -1233,7 +1253,7 @@ namespace StardewModdingAPI.Framework } else if (previousMenu is SaveGameMenu || previousMenu is ShippingMenu) { - this.Monitor.Log("Context: after save, starting new day.", LogLevel.Trace); + this.Monitor.Log($"Context: after save, starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace); SaveEvents.InvokeAfterSave(this.Monitor); TimeEvents.InvokeAfterDayStarted(this.Monitor); } @@ -1262,6 +1282,8 @@ namespace StardewModdingAPI.Framework // raise current location changed if (Game1.currentLocation != this.PreviousGameLocation) { + if (this.VerboseLogging) + this.Monitor.Log($"Context: set location to {Game1.currentLocation?.Name ?? "(none)"}.", LogLevel.Trace); LocationEvents.InvokeCurrentLocationChanged(this.Monitor, this.PreviousGameLocation, Game1.currentLocation); this.PreviousGameLocation = Game1.currentLocation; } -- cgit From 486ac29796586e09540723dcae8070cf3e60285b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 9 May 2017 00:11:39 -0400 Subject: use shared reflection helper --- src/StardewModdingAPI/Framework/ModHelper.cs | 7 ++++--- src/StardewModdingAPI/Framework/SGame.cs | 10 +++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/ModHelper.cs b/src/StardewModdingAPI/Framework/ModHelper.cs index 09297a65..7810148c 100644 --- a/src/StardewModdingAPI/Framework/ModHelper.cs +++ b/src/StardewModdingAPI/Framework/ModHelper.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Serialisation; namespace StardewModdingAPI.Framework @@ -25,7 +24,7 @@ namespace StardewModdingAPI.Framework public IContentHelper Content { get; } /// Simplifies access to private game code. - public IReflectionHelper Reflection { get; } = new ReflectionHelper(); + public IReflectionHelper Reflection { get; } /// Metadata about loaded mods. public IModRegistry ModRegistry { get; } @@ -44,9 +43,10 @@ namespace StardewModdingAPI.Framework /// Metadata about loaded mods. /// Manages console commands. /// The content manager which loads content assets. + /// Simplifies access to private game code. /// An argument is null or empty. /// The path does not exist on disk. - public ModHelper(IManifest manifest, string modDirectory, JsonHelper jsonHelper, IModRegistry modRegistry, CommandManager commandManager, SContentManager contentManager) + public ModHelper(IManifest manifest, string modDirectory, JsonHelper jsonHelper, IModRegistry modRegistry, CommandManager commandManager, SContentManager contentManager, IReflectionHelper reflection) { // validate if (string.IsNullOrWhiteSpace(modDirectory)) @@ -64,6 +64,7 @@ namespace StardewModdingAPI.Framework this.Content = new ContentHelper(contentManager, modDirectory, manifest.Name); this.ModRegistry = modRegistry; this.ConsoleCommands = new CommandHelper(manifest.Name, commandManager); + this.Reflection = reflection; } /**** diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index d248c3ca..8786010e 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -9,7 +9,6 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using StardewModdingAPI.Events; -using StardewModdingAPI.Framework.Reflection; using StardewValley; using StardewValley.BellsAndWhistles; using StardewValley.Locations; @@ -157,9 +156,11 @@ namespace StardewModdingAPI.Framework /**** ** Private wrappers ****/ + /// Simplifies access to private game code. + private static IReflectionHelper Reflection; + // ReSharper disable ArrangeStaticMemberQualifier, ArrangeThisQualifier, InconsistentNaming /// Used to access private fields and methods. - private static readonly IReflectionHelper Reflection = new ReflectionHelper(); private static List _fpsList => SGame.Reflection.GetPrivateField>(typeof(Game1), nameof(_fpsList)).GetValue(); private static Stopwatch _fpsStopwatch => SGame.Reflection.GetPrivateField(typeof(Game1), nameof(SGame._fpsStopwatch)).GetValue(); private static float _fps @@ -176,6 +177,7 @@ namespace StardewModdingAPI.Framework private readonly Action renderScreenBuffer = () => SGame.Reflection.GetPrivateMethod(SGame.Instance, nameof(renderScreenBuffer)).Invoke(new object[0]); // ReSharper restore ArrangeStaticMemberQualifier, ArrangeThisQualifier, InconsistentNaming + /********* ** Accessors *********/ @@ -188,11 +190,13 @@ namespace StardewModdingAPI.Framework *********/ /// Construct an instance. /// Encapsulates monitoring and logging. - internal SGame(IMonitor monitor) + /// Simplifies access to private game code. + internal SGame(IMonitor monitor, IReflectionHelper reflection) { this.Monitor = monitor; this.FirstUpdate = true; SGame.Instance = this; + SGame.Reflection = reflection; Game1.graphics.GraphicsProfile = GraphicsProfile.HiDef; // required by Stardew Valley } -- cgit From fa729fa70021106bfb8541695f5fb7c5654e31b7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 9 May 2017 00:14:59 -0400 Subject: don't try to recover from a completely broken sprite batch, which can cause a whole new set of problems (#283) --- src/StardewModdingAPI/Framework/SGame.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index 8786010e..b1ac7c58 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -990,14 +990,7 @@ namespace StardewModdingAPI.Framework if (Game1.spriteBatch.IsOpen(SGame.Reflection)) { this.Monitor.Log("Recovering sprite batch from error...", LogLevel.Trace); - try - { - Game1.spriteBatch.End(); - } - catch - { - Game1.spriteBatch = new SpriteBatch(this.GraphicsDevice); // sprite batch is broken, try replacing it - } + Game1.spriteBatch.End(); } } catch (Exception innerEx) -- cgit From 467d4a27ee565433559c9dc374f5c95107938498 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 9 May 2017 00:16:04 -0400 Subject: reduce max consecutive draw crashes (#283) --- src/StardewModdingAPI/Framework/SGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index b1ac7c58..81dae754 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -30,7 +30,7 @@ namespace StardewModdingAPI.Framework ** SMAPI state ****/ /// The maximum number of consecutive attempts SMAPI should make to recover from a draw error. - private readonly int MaxFailedDraws = 120; // roughly two seconds + private readonly int MaxFailedDraws = 60; // roughly one second /// The number of ticks until SMAPI should notify mods that the game has loaded. /// Skipping a few frames ensures the game finishes initialising the world before mods try to change it. -- cgit From b4584afda897b695c5fc306db9facc0690b5ab75 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 9 May 2017 22:37:53 -0400 Subject: trace locale changes as non-verbose context --- src/StardewModdingAPI/Framework/SGame.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index 81dae754..f0450306 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -1136,8 +1136,7 @@ namespace StardewModdingAPI.Framework var oldValue = this.PreviousLocale; var newValue = LocalizedContentManager.CurrentLanguageCode; - if (this.VerboseLogging) - this.Monitor.Log($"Context: locale set to {newValue}.", LogLevel.Trace); + this.Monitor.Log($"Context: locale set to {newValue}.", LogLevel.Trace); if (oldValue != null) ContentEvents.InvokeAfterLocaleChanged(this.Monitor, oldValue.ToString(), newValue.ToString()); -- cgit From 86c60c971ae533321b6550f140af4f01351cb2e3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 10 May 2017 23:49:58 -0400 Subject: merge SGame::UpdateEventCalls into Update The method was misleadingly named (since only some of the events were in the method), and unnecessarily limited the possible flows. --- src/StardewModdingAPI/Framework/SGame.cs | 501 +++++++++++++++---------------- 1 file changed, 247 insertions(+), 254 deletions(-) (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index f0450306..ed1ff647 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -257,8 +257,253 @@ namespace StardewModdingAPI.Framework GameEvents.InvokeGameLoaded(this.Monitor); } - // update SMAPI events - this.UpdateEventCalls(); + // content locale changed event + if (this.PreviousLocale != LocalizedContentManager.CurrentLanguageCode) + { + var oldValue = this.PreviousLocale; + var newValue = LocalizedContentManager.CurrentLanguageCode; + + this.Monitor.Log($"Context: locale set to {newValue}.", LogLevel.Trace); + + if (oldValue != null) + ContentEvents.InvokeAfterLocaleChanged(this.Monitor, oldValue.ToString(), newValue.ToString()); + this.PreviousLocale = newValue; + } + + // save loaded event + if (Context.IsSaveLoaded && !SaveGame.IsProcessing/*still loading save*/ && this.AfterLoadTimer >= 0) + { + if (this.AfterLoadTimer == 0) + { + this.Monitor.Log($"Context: loaded saved game '{Constants.SaveFolderName}', starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace); + + SaveEvents.InvokeAfterLoad(this.Monitor); + PlayerEvents.InvokeLoadedGame(this.Monitor, new EventArgsLoadedGameChanged(Game1.hasLoadedGame)); + TimeEvents.InvokeAfterDayStarted(this.Monitor); + } + this.AfterLoadTimer--; + } + + // before exit to title + if (Game1.exitToTitle) + this.IsExiting = true; + + // after exit to title + if (this.IsWorldReady && this.IsExiting && Game1.activeClickableMenu is TitleMenu) + { + this.Monitor.Log("Context: returned to title", LogLevel.Trace); + SaveEvents.InvokeAfterReturnToTitle(this.Monitor); + this.AfterLoadTimer = 5; + this.IsExiting = false; + } + + // input events + { + // get latest state + this.KStateNow = Keyboard.GetState(); + this.MStateNow = Mouse.GetState(); + this.MPositionNow = new Point(Game1.getMouseX(), Game1.getMouseY()); + + // raise key pressed + foreach (var key in this.FramePressedKeys) + ControlEvents.InvokeKeyPressed(this.Monitor, key); + + // raise key released + foreach (var key in this.FrameReleasedKeys) + ControlEvents.InvokeKeyReleased(this.Monitor, key); + + // raise controller button pressed + for (var i = PlayerIndex.One; i <= PlayerIndex.Four; i++) + { + var buttons = this.GetFramePressedButtons(i); + foreach (var button in buttons) + { + if (button == Buttons.LeftTrigger || button == Buttons.RightTrigger) + ControlEvents.InvokeTriggerPressed(this.Monitor, i, button, button == Buttons.LeftTrigger ? GamePad.GetState(i).Triggers.Left : GamePad.GetState(i).Triggers.Right); + else + ControlEvents.InvokeButtonPressed(this.Monitor, i, button); + } + } + + // raise controller button released + for (var i = PlayerIndex.One; i <= PlayerIndex.Four; i++) + { + foreach (var button in this.GetFrameReleasedButtons(i)) + { + if (button == Buttons.LeftTrigger || button == Buttons.RightTrigger) + ControlEvents.InvokeTriggerReleased(this.Monitor, i, button, button == Buttons.LeftTrigger ? GamePad.GetState(i).Triggers.Left : GamePad.GetState(i).Triggers.Right); + else + ControlEvents.InvokeButtonReleased(this.Monitor, i, button); + } + } + + // raise keyboard state changed + if (this.KStateNow != this.KStatePrior) + ControlEvents.InvokeKeyboardChanged(this.Monitor, this.KStatePrior, this.KStateNow); + + // raise mouse state changed + if (this.MStateNow != this.MStatePrior) + { + ControlEvents.InvokeMouseChanged(this.Monitor, this.MStatePrior, this.MStateNow, this.MPositionPrior, this.MPositionNow); + this.MStatePrior = this.MStateNow; + this.MPositionPrior = this.MPositionNow; + } + } + + // menu events + if (Game1.activeClickableMenu != this.PreviousActiveMenu) + { + IClickableMenu previousMenu = this.PreviousActiveMenu; + IClickableMenu newMenu = Game1.activeClickableMenu; + + // log context + if (this.VerboseLogging) + { + if (previousMenu == null) + this.Monitor.Log($"Context: opened menu {newMenu?.GetType().FullName ?? "(none)"}.", LogLevel.Trace); + else if (newMenu == null) + this.Monitor.Log($"Context: closed menu {previousMenu.GetType().FullName}.", LogLevel.Trace); + else + this.Monitor.Log($"Context: changed menu from {previousMenu.GetType().FullName} to {newMenu.GetType().FullName}.", LogLevel.Trace); + } + + // raise save events + // (saving is performed by SaveGameMenu; on days when the player shipping something, ShippingMenu wraps SaveGameMenu) + if (newMenu is SaveGameMenu || newMenu is ShippingMenu) + { + this.Monitor.Log("Context: before save.", LogLevel.Trace); + SaveEvents.InvokeBeforeSave(this.Monitor); + } + else if (previousMenu is SaveGameMenu || previousMenu is ShippingMenu) + { + this.Monitor.Log($"Context: after save, starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace); + SaveEvents.InvokeAfterSave(this.Monitor); + TimeEvents.InvokeAfterDayStarted(this.Monitor); + } + + // raise menu events + if (newMenu != null) + MenuEvents.InvokeMenuChanged(this.Monitor, previousMenu, newMenu); + else + MenuEvents.InvokeMenuClosed(this.Monitor, previousMenu); + + // update previous menu + // (if the menu was changed in one of the handlers, deliberately defer detection until the next update so mods can be notified of the new menu change) + this.PreviousActiveMenu = newMenu; + } + + // world & player events + if (this.IsWorldReady) + { + // raise location list changed + if (this.GetHash(Game1.locations) != this.PreviousGameLocations) + { + LocationEvents.InvokeLocationsChanged(this.Monitor, Game1.locations); + this.PreviousGameLocations = this.GetHash(Game1.locations); + } + + // raise current location changed + if (Game1.currentLocation != this.PreviousGameLocation) + { + if (this.VerboseLogging) + this.Monitor.Log($"Context: set location to {Game1.currentLocation?.Name ?? "(none)"}.", LogLevel.Trace); + LocationEvents.InvokeCurrentLocationChanged(this.Monitor, this.PreviousGameLocation, Game1.currentLocation); + this.PreviousGameLocation = Game1.currentLocation; + } + + // raise player changed + if (Game1.player != this.PreviousFarmer) + { + PlayerEvents.InvokeFarmerChanged(this.Monitor, this.PreviousFarmer, Game1.player); + this.PreviousFarmer = Game1.player; + } + + // raise player leveled up a skill + if (Game1.player.combatLevel != this.PreviousCombatLevel) + { + PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Combat, Game1.player.combatLevel); + this.PreviousCombatLevel = Game1.player.combatLevel; + } + if (Game1.player.farmingLevel != this.PreviousFarmingLevel) + { + PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Farming, Game1.player.farmingLevel); + this.PreviousFarmingLevel = Game1.player.farmingLevel; + } + if (Game1.player.fishingLevel != this.PreviousFishingLevel) + { + PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Fishing, Game1.player.fishingLevel); + this.PreviousFishingLevel = Game1.player.fishingLevel; + } + if (Game1.player.foragingLevel != this.PreviousForagingLevel) + { + PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Foraging, Game1.player.foragingLevel); + this.PreviousForagingLevel = Game1.player.foragingLevel; + } + if (Game1.player.miningLevel != this.PreviousMiningLevel) + { + PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Mining, Game1.player.miningLevel); + this.PreviousMiningLevel = Game1.player.miningLevel; + } + if (Game1.player.luckLevel != this.PreviousLuckLevel) + { + PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Luck, Game1.player.luckLevel); + this.PreviousLuckLevel = Game1.player.luckLevel; + } + + // raise player inventory changed + ItemStackChange[] changedItems = this.GetInventoryChanges(Game1.player.items, this.PreviousItems).ToArray(); + if (changedItems.Any()) + { + PlayerEvents.InvokeInventoryChanged(this.Monitor, Game1.player.items, changedItems); + this.PreviousItems = Game1.player.items.Where(n => n != null).ToDictionary(n => n, n => n.Stack); + } + + // raise current location's object list changed + { + int? objectHash = Game1.currentLocation?.objects != null ? this.GetHash(Game1.currentLocation.objects) : (int?)null; + if (objectHash != null && this.PreviousLocationObjects != objectHash) + { + LocationEvents.InvokeOnNewLocationObject(this.Monitor, Game1.currentLocation.objects); + this.PreviousLocationObjects = objectHash.Value; + } + } + + // raise time changed + if (Game1.timeOfDay != this.PreviousTime) + { + TimeEvents.InvokeTimeOfDayChanged(this.Monitor, this.PreviousTime, Game1.timeOfDay); + this.PreviousTime = Game1.timeOfDay; + } + if (Game1.dayOfMonth != this.PreviousDay) + { + TimeEvents.InvokeDayOfMonthChanged(this.Monitor, this.PreviousDay, Game1.dayOfMonth); + this.PreviousDay = Game1.dayOfMonth; + } + if (Game1.currentSeason != this.PreviousSeason) + { + TimeEvents.InvokeSeasonOfYearChanged(this.Monitor, this.PreviousSeason, Game1.currentSeason); + this.PreviousSeason = Game1.currentSeason; + } + if (Game1.year != this.PreviousYear) + { + TimeEvents.InvokeYearOfGameChanged(this.Monitor, this.PreviousYear, Game1.year); + this.PreviousYear = Game1.year; + } + + // raise mine level changed + if (Game1.mine != null && Game1.mine.mineLevel != this.PreviousMineLevel) + { + MineEvents.InvokeMineLevelChanged(this.Monitor, this.PreviousMineLevel, Game1.mine.mineLevel); + this.PreviousMineLevel = Game1.mine.mineLevel; + } + } + + // raise game day transition event (obsolete) + if (Game1.newDay != this.PreviousIsNewDay) + { + TimeEvents.InvokeOnNewDay(this.Monitor, this.PreviousDay, Game1.dayOfMonth, Game1.newDay); + this.PreviousIsNewDay = Game1.newDay; + } // let game update try @@ -1127,258 +1372,6 @@ namespace StardewModdingAPI.Framework return this.WasButtonJustReleased(button, value > 0.2f ? ButtonState.Pressed : ButtonState.Released, stateIndex); } - /// Detect changes since the last update ticket and trigger mod events. - private void UpdateEventCalls() - { - // content locale changed event - if (this.PreviousLocale != LocalizedContentManager.CurrentLanguageCode) - { - var oldValue = this.PreviousLocale; - var newValue = LocalizedContentManager.CurrentLanguageCode; - - this.Monitor.Log($"Context: locale set to {newValue}.", LogLevel.Trace); - - if (oldValue != null) - ContentEvents.InvokeAfterLocaleChanged(this.Monitor, oldValue.ToString(), newValue.ToString()); - this.PreviousLocale = newValue; - } - - // save loaded event - if (Context.IsSaveLoaded && !SaveGame.IsProcessing/*still loading save*/ && this.AfterLoadTimer >= 0) - { - if (this.AfterLoadTimer == 0) - { - this.Monitor.Log($"Context: loaded saved game '{Constants.SaveFolderName}', starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace); - - SaveEvents.InvokeAfterLoad(this.Monitor); - PlayerEvents.InvokeLoadedGame(this.Monitor, new EventArgsLoadedGameChanged(Game1.hasLoadedGame)); - TimeEvents.InvokeAfterDayStarted(this.Monitor); - } - this.AfterLoadTimer--; - } - - // before exit to title - if (Game1.exitToTitle) - this.IsExiting = true; - - // after exit to title - if (this.IsWorldReady && this.IsExiting && Game1.activeClickableMenu is TitleMenu) - { - this.Monitor.Log("Context: returned to title", LogLevel.Trace); - SaveEvents.InvokeAfterReturnToTitle(this.Monitor); - this.AfterLoadTimer = 5; - this.IsExiting = false; - } - - // input events - { - // get latest state - this.KStateNow = Keyboard.GetState(); - this.MStateNow = Mouse.GetState(); - this.MPositionNow = new Point(Game1.getMouseX(), Game1.getMouseY()); - - // raise key pressed - foreach (var key in this.FramePressedKeys) - ControlEvents.InvokeKeyPressed(this.Monitor, key); - - // raise key released - foreach (var key in this.FrameReleasedKeys) - ControlEvents.InvokeKeyReleased(this.Monitor, key); - - // raise controller button pressed - for (var i = PlayerIndex.One; i <= PlayerIndex.Four; i++) - { - var buttons = this.GetFramePressedButtons(i); - foreach (var button in buttons) - { - if (button == Buttons.LeftTrigger || button == Buttons.RightTrigger) - ControlEvents.InvokeTriggerPressed(this.Monitor, i, button, button == Buttons.LeftTrigger ? GamePad.GetState(i).Triggers.Left : GamePad.GetState(i).Triggers.Right); - else - ControlEvents.InvokeButtonPressed(this.Monitor, i, button); - } - } - - // raise controller button released - for (var i = PlayerIndex.One; i <= PlayerIndex.Four; i++) - { - foreach (var button in this.GetFrameReleasedButtons(i)) - { - if (button == Buttons.LeftTrigger || button == Buttons.RightTrigger) - ControlEvents.InvokeTriggerReleased(this.Monitor, i, button, button == Buttons.LeftTrigger ? GamePad.GetState(i).Triggers.Left : GamePad.GetState(i).Triggers.Right); - else - ControlEvents.InvokeButtonReleased(this.Monitor, i, button); - } - } - - // raise keyboard state changed - if (this.KStateNow != this.KStatePrior) - ControlEvents.InvokeKeyboardChanged(this.Monitor, this.KStatePrior, this.KStateNow); - - // raise mouse state changed - if (this.MStateNow != this.MStatePrior) - { - ControlEvents.InvokeMouseChanged(this.Monitor, this.MStatePrior, this.MStateNow, this.MPositionPrior, this.MPositionNow); - this.MStatePrior = this.MStateNow; - this.MPositionPrior = this.MPositionNow; - } - } - - // menu events - if (Game1.activeClickableMenu != this.PreviousActiveMenu) - { - IClickableMenu previousMenu = this.PreviousActiveMenu; - IClickableMenu newMenu = Game1.activeClickableMenu; - - // log context - if (this.VerboseLogging) - { - if (previousMenu == null) - this.Monitor.Log($"Context: opened menu {newMenu?.GetType().FullName ?? "(none)"}.", LogLevel.Trace); - else if (newMenu == null) - this.Monitor.Log($"Context: closed menu {previousMenu.GetType().FullName}.", LogLevel.Trace); - else - this.Monitor.Log($"Context: changed menu from {previousMenu.GetType().FullName} to {newMenu.GetType().FullName}.", LogLevel.Trace); - } - - // raise save events - // (saving is performed by SaveGameMenu; on days when the player shipping something, ShippingMenu wraps SaveGameMenu) - if (newMenu is SaveGameMenu || newMenu is ShippingMenu) - { - this.Monitor.Log("Context: before save.", LogLevel.Trace); - SaveEvents.InvokeBeforeSave(this.Monitor); - } - else if (previousMenu is SaveGameMenu || previousMenu is ShippingMenu) - { - this.Monitor.Log($"Context: after save, starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace); - SaveEvents.InvokeAfterSave(this.Monitor); - TimeEvents.InvokeAfterDayStarted(this.Monitor); - } - - // raise menu events - if (newMenu != null) - MenuEvents.InvokeMenuChanged(this.Monitor, previousMenu, newMenu); - else - MenuEvents.InvokeMenuClosed(this.Monitor, previousMenu); - - // update previous menu - // (if the menu was changed in one of the handlers, deliberately defer detection until the next update so mods can be notified of the new menu change) - this.PreviousActiveMenu = newMenu; - } - - // world & player events - if (this.IsWorldReady) - { - // raise location list changed - if (this.GetHash(Game1.locations) != this.PreviousGameLocations) - { - LocationEvents.InvokeLocationsChanged(this.Monitor, Game1.locations); - this.PreviousGameLocations = this.GetHash(Game1.locations); - } - - // raise current location changed - if (Game1.currentLocation != this.PreviousGameLocation) - { - if (this.VerboseLogging) - this.Monitor.Log($"Context: set location to {Game1.currentLocation?.Name ?? "(none)"}.", LogLevel.Trace); - LocationEvents.InvokeCurrentLocationChanged(this.Monitor, this.PreviousGameLocation, Game1.currentLocation); - this.PreviousGameLocation = Game1.currentLocation; - } - - // raise player changed - if (Game1.player != this.PreviousFarmer) - { - PlayerEvents.InvokeFarmerChanged(this.Monitor, this.PreviousFarmer, Game1.player); - this.PreviousFarmer = Game1.player; - } - - // raise player leveled up a skill - if (Game1.player.combatLevel != this.PreviousCombatLevel) - { - PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Combat, Game1.player.combatLevel); - this.PreviousCombatLevel = Game1.player.combatLevel; - } - if (Game1.player.farmingLevel != this.PreviousFarmingLevel) - { - PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Farming, Game1.player.farmingLevel); - this.PreviousFarmingLevel = Game1.player.farmingLevel; - } - if (Game1.player.fishingLevel != this.PreviousFishingLevel) - { - PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Fishing, Game1.player.fishingLevel); - this.PreviousFishingLevel = Game1.player.fishingLevel; - } - if (Game1.player.foragingLevel != this.PreviousForagingLevel) - { - PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Foraging, Game1.player.foragingLevel); - this.PreviousForagingLevel = Game1.player.foragingLevel; - } - if (Game1.player.miningLevel != this.PreviousMiningLevel) - { - PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Mining, Game1.player.miningLevel); - this.PreviousMiningLevel = Game1.player.miningLevel; - } - if (Game1.player.luckLevel != this.PreviousLuckLevel) - { - PlayerEvents.InvokeLeveledUp(this.Monitor, EventArgsLevelUp.LevelType.Luck, Game1.player.luckLevel); - this.PreviousLuckLevel = Game1.player.luckLevel; - } - - // raise player inventory changed - ItemStackChange[] changedItems = this.GetInventoryChanges(Game1.player.items, this.PreviousItems).ToArray(); - if (changedItems.Any()) - { - PlayerEvents.InvokeInventoryChanged(this.Monitor, Game1.player.items, changedItems); - this.PreviousItems = Game1.player.items.Where(n => n != null).ToDictionary(n => n, n => n.Stack); - } - - // raise current location's object list changed - { - int? objectHash = Game1.currentLocation?.objects != null ? this.GetHash(Game1.currentLocation.objects) : (int?)null; - if (objectHash != null && this.PreviousLocationObjects != objectHash) - { - LocationEvents.InvokeOnNewLocationObject(this.Monitor, Game1.currentLocation.objects); - this.PreviousLocationObjects = objectHash.Value; - } - } - - // raise time changed - if (Game1.timeOfDay != this.PreviousTime) - { - TimeEvents.InvokeTimeOfDayChanged(this.Monitor, this.PreviousTime, Game1.timeOfDay); - this.PreviousTime = Game1.timeOfDay; - } - if (Game1.dayOfMonth != this.PreviousDay) - { - TimeEvents.InvokeDayOfMonthChanged(this.Monitor, this.PreviousDay, Game1.dayOfMonth); - this.PreviousDay = Game1.dayOfMonth; - } - if (Game1.currentSeason != this.PreviousSeason) - { - TimeEvents.InvokeSeasonOfYearChanged(this.Monitor, this.PreviousSeason, Game1.currentSeason); - this.PreviousSeason = Game1.currentSeason; - } - if (Game1.year != this.PreviousYear) - { - TimeEvents.InvokeYearOfGameChanged(this.Monitor, this.PreviousYear, Game1.year); - this.PreviousYear = Game1.year; - } - - // raise mine level changed - if (Game1.mine != null && Game1.mine.mineLevel != this.PreviousMineLevel) - { - MineEvents.InvokeMineLevelChanged(this.Monitor, this.PreviousMineLevel, Game1.mine.mineLevel); - this.PreviousMineLevel = Game1.mine.mineLevel; - } - } - - // raise game day transition event (obsolete) - if (Game1.newDay != this.PreviousIsNewDay) - { - TimeEvents.InvokeOnNewDay(this.Monitor, this.PreviousDay, Game1.dayOfMonth, Game1.newDay); - this.PreviousIsNewDay = Game1.newDay; - } - } - /// Get the player inventory changes between two states. /// The player's current inventory. /// The player's previous inventory. -- cgit From 48c5c9e36794ed3dae0cf6114194b0dc80dd4725 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 11 May 2017 00:34:01 -0400 Subject: overhaul save handling to fix save events not triggering on Linux/Mac (#284) --- src/StardewModdingAPI/Framework/SGame.cs | 89 +++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 29 deletions(-) (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/SGame.cs b/src/StardewModdingAPI/Framework/SGame.cs index ed1ff647..f2098dbd 100644 --- a/src/StardewModdingAPI/Framework/SGame.cs +++ b/src/StardewModdingAPI/Framework/SGame.cs @@ -45,6 +45,9 @@ namespace StardewModdingAPI.Framework /// Whether the game is returning to the menu. private bool IsExiting; + /// Whether the game is saving and SMAPI has already raised . + private bool IsBetweenSaveEvents; + /// Whether the game's zoom level is at 100% (i.e. nothing should be scaled). public bool ZoomLevelIsOne => Game1.options.zoomLevel.Equals(1.0f); @@ -216,6 +219,9 @@ namespace StardewModdingAPI.Framework /// A snapshot of the game timing state. protected override void Update(GameTime gameTime) { + /********* + ** Skip conditions + *********/ // SMAPI exiting, stop processing game updates if (this.Monitor.IsExiting) { @@ -242,14 +248,34 @@ namespace StardewModdingAPI.Framework // 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 // during enumeration errors). To avoid problems, events are not invoked while a save - // is in progress. + // is in progress. It's safe to raise SaveEvents.BeforeSave as soon as the menu is + // opened (since the save hasn't started yet), but all other events should be suppressed. if (Context.IsSaving) { + // raise before-save + if (!this.IsBetweenSaveEvents) + { + this.IsBetweenSaveEvents = true; + this.Monitor.Log("Context: before save.", LogLevel.Trace); + SaveEvents.InvokeBeforeSave(this.Monitor); + } + + // suppress non-save events base.Update(gameTime); return; } + 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); + SaveEvents.InvokeAfterSave(this.Monitor); + TimeEvents.InvokeAfterDayStarted(this.Monitor); + } - // raise game loaded + /********* + ** Game loaded events + *********/ if (this.FirstUpdate) { GameEvents.InvokeInitialize(this.Monitor); @@ -257,7 +283,9 @@ namespace StardewModdingAPI.Framework GameEvents.InvokeGameLoaded(this.Monitor); } - // content locale changed event + /********* + ** Locale changed events + *********/ if (this.PreviousLocale != LocalizedContentManager.CurrentLanguageCode) { var oldValue = this.PreviousLocale; @@ -270,7 +298,9 @@ namespace StardewModdingAPI.Framework this.PreviousLocale = newValue; } - // save loaded event + /********* + ** After load events + *********/ if (Context.IsSaveLoaded && !SaveGame.IsProcessing/*still loading save*/ && this.AfterLoadTimer >= 0) { if (this.AfterLoadTimer == 0) @@ -284,6 +314,9 @@ namespace StardewModdingAPI.Framework this.AfterLoadTimer--; } + /********* + ** Exit to title events + *********/ // before exit to title if (Game1.exitToTitle) this.IsExiting = true; @@ -297,7 +330,9 @@ namespace StardewModdingAPI.Framework this.IsExiting = false; } - // input events + /********* + ** Input events + *********/ { // get latest state this.KStateNow = Keyboard.GetState(); @@ -350,7 +385,9 @@ namespace StardewModdingAPI.Framework } } - // menu events + /********* + ** Menu events + *********/ if (Game1.activeClickableMenu != this.PreviousActiveMenu) { IClickableMenu previousMenu = this.PreviousActiveMenu; @@ -367,20 +404,6 @@ namespace StardewModdingAPI.Framework this.Monitor.Log($"Context: changed menu from {previousMenu.GetType().FullName} to {newMenu.GetType().FullName}.", LogLevel.Trace); } - // raise save events - // (saving is performed by SaveGameMenu; on days when the player shipping something, ShippingMenu wraps SaveGameMenu) - if (newMenu is SaveGameMenu || newMenu is ShippingMenu) - { - this.Monitor.Log("Context: before save.", LogLevel.Trace); - SaveEvents.InvokeBeforeSave(this.Monitor); - } - else if (previousMenu is SaveGameMenu || previousMenu is ShippingMenu) - { - this.Monitor.Log($"Context: after save, starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace); - SaveEvents.InvokeAfterSave(this.Monitor); - TimeEvents.InvokeAfterDayStarted(this.Monitor); - } - // raise menu events if (newMenu != null) MenuEvents.InvokeMenuChanged(this.Monitor, previousMenu, newMenu); @@ -392,7 +415,9 @@ namespace StardewModdingAPI.Framework this.PreviousActiveMenu = newMenu; } - // world & player events + /********* + ** World & player events + *********/ if (this.IsWorldReady) { // raise location list changed @@ -498,14 +523,18 @@ namespace StardewModdingAPI.Framework } } - // raise game day transition event (obsolete) + /********* + ** Game day transition event (obsolete) + *********/ if (Game1.newDay != this.PreviousIsNewDay) { TimeEvents.InvokeOnNewDay(this.Monitor, this.PreviousDay, Game1.dayOfMonth, Game1.newDay); this.PreviousIsNewDay = Game1.newDay; } - // let game update + /********* + ** Game update + *********/ try { base.Update(gameTime); @@ -514,8 +543,10 @@ namespace StardewModdingAPI.Framework { this.Monitor.Log($"An error occured in the base update loop: {ex.GetLogSummary()}", LogLevel.Error); } - - // raise update events + + /********* + ** Update events + *********/ GameEvents.InvokeUpdateTick(this.Monitor); if (this.FirstUpdate) { @@ -538,11 +569,11 @@ namespace StardewModdingAPI.Framework if (this.CurrentUpdateTick >= 60) this.CurrentUpdateTick = 0; - // track keyboard state + /********* + ** Update input state + *********/ this.KStatePrior = this.KStateNow; - - // track controller button state - for (var i = PlayerIndex.One; i <= PlayerIndex.Four; i++) + for (PlayerIndex i = PlayerIndex.One; i <= PlayerIndex.Four; i++) this.PreviouslyPressedButtons[(int)i] = this.GetButtonsDown(i); } -- cgit From 03876153f4fbb13b8a260c529513a306319f9e05 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 11 May 2017 22:25:45 -0400 Subject: decouple mod metadata vs assembly loading to enable upcoming mod dependencies (#285) --- src/StardewModdingAPI/Framework/ModMetadata.cs | 40 ++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/StardewModdingAPI/Framework/ModMetadata.cs (limited to 'src/StardewModdingAPI/Framework') diff --git a/src/StardewModdingAPI/Framework/ModMetadata.cs b/src/StardewModdingAPI/Framework/ModMetadata.cs new file mode 100644 index 00000000..aeb9261a --- /dev/null +++ b/src/StardewModdingAPI/Framework/ModMetadata.cs @@ -0,0 +1,40 @@ +using StardewModdingAPI.Framework.Models; + +namespace StardewModdingAPI.Framework +{ + /// Metadata for a mod. + internal class ModMetadata + { + /********* + ** Accessors + *********/ + /// The mod's display name. + public string DisplayName { get; } + + /// The mod's full directory path. + public string DirectoryPath { get; } + + /// The mod manifest. + public IManifest Manifest { get; } + + /// Optional metadata about a mod version that SMAPI should assume is compatible or broken, regardless of whether it detects incompatible code. + public ModCompatibility Compatibility { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The mod's display name. + /// The mod's full directory path. + /// The mod manifest. + /// Optional metadata about a mod version that SMAPI should assume is compatible or broken, regardless of whether it detects incompatible code. + public ModMetadata(string displayName, string directoryPath, IManifest manifest, ModCompatibility compatibility) + { + this.DisplayName = displayName; + this.DirectoryPath = directoryPath; + this.Manifest = manifest; + this.Compatibility = compatibility; + } + } +} -- cgit From bb165f2079e33d02c0e673db73ac5b336272a3fa Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 11 May 2017 23:21:02 -0400 Subject: organise a few framework classes --- .../Framework/AssemblyDefinitionResolver.cs | 61 ----- src/StardewModdingAPI/Framework/AssemblyLoader.cs | 292 --------------------- .../Framework/AssemblyParseResult.cs | 31 --- src/StardewModdingAPI/Framework/Manifest.cs | 45 ---- .../ModLoading/AssemblyDefinitionResolver.cs | 61 +++++ .../Framework/ModLoading/AssemblyLoader.cs | 292 +++++++++++++++++++++ .../Framework/ModLoading/AssemblyParseResult.cs | 31 +++ .../Framework/ModLoading/ModMetadata.cs | 40 +++ src/StardewModdingAPI/Framework/ModMetadata.cs | 40 --- src/StardewModdingAPI/Framework/Models/Manifest.cs | 44 ++++ 10 files changed, 468 insertions(+), 469 deletions(-) delete mode 100644 src/StardewModdingAPI/Framework/AssemblyDefinitionResolver.cs delete mode 100644 src/StardewModdingAPI/Framework/AssemblyLoader.cs delete mode 100644 src/StardewModdingAPI/Framework/AssemblyParseResult.cs delete mode 100644 src/StardewModdingAPI/Framework/Manifest.cs create mode 100644 src/StardewModdingAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs create mode 100644 src/StardewModdingAPI/Framework/ModLoading/AssemblyLoader.cs create mode 100644 src/StardewModdingAPI/Framework/ModLoading/