From fdf221addee883f4b1ddcd42f92876f7a816c7a1 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 13 Jun 2019 23:49:05 -0400 Subject: reorganise update loop a bit (#648) --- src/SMAPI/Framework/SGame.cs | 667 ++++++++++++++++++++++--------------------- 1 file changed, 340 insertions(+), 327 deletions(-) (limited to 'src/SMAPI') diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 2a3f16d4..1145207f 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -28,7 +28,6 @@ using StardewValley.Menus; using StardewValley.TerrainFeatures; using StardewValley.Tools; using xTile.Dimensions; -using xTile.Layers; using SObject = StardewValley.Object; namespace StardewModdingAPI.Framework @@ -270,15 +269,27 @@ namespace StardewModdingAPI.Framework this.DeprecationManager.PrintQueued(); /********* - ** Special cases + ** First-tick initialisation *********/ - // Perform first-tick initialisation. if (!this.IsInitialised) { this.IsInitialised = true; this.InitialiseAfterGameStarted(); } + /********* + ** Update input + *********/ + // This should *always* run, even when suppressing mod events, since the game uses + // this too. For example, doing this after mod event suppression would prevent the + // user from doing anything on the overnight shipping screen. + SInputState inputState = this.Input; + if (this.IsActive) + inputState.TrueUpdate(); + + /********* + ** Special cases + *********/ // Abort if SMAPI is exiting. if (this.CancellationToken.IsCancellationRequested) { @@ -345,6 +356,38 @@ namespace StardewModdingAPI.Framework return; } + // Raise minimal events while saving. + // 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. 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-create + if (!Context.IsWorldReady && !this.IsBetweenCreateEvents) + { + this.IsBetweenCreateEvents = true; + this.Monitor.Log("Context: before save creation.", LogLevel.Trace); + events.SaveCreating.RaiseEmpty(); + } + + // raise before-save + if (Context.IsWorldReady && !this.IsBetweenSaveEvents) + { + this.IsBetweenSaveEvents = true; + this.Monitor.Log("Context: before save.", LogLevel.Trace); + events.Saving.RaiseEmpty(); + } + + // suppress non-save events + events.UnvalidatedUpdateTicking.RaiseEmpty(); + SGame.TicksElapsed++; + base.Update(gameTime); + events.UnvalidatedUpdateTicked.RaiseEmpty(); + return; + } + /********* ** Execute commands *********/ @@ -382,66 +425,6 @@ namespace StardewModdingAPI.Framework } } - /********* - ** Update input - *********/ - // This should *always* run, even when suppressing mod events, since the game uses - // this too. For example, doing this after mod event suppression would prevent the - // user from doing anything on the overnight shipping screen. - SInputState inputState = this.Input; - if (this.IsActive) - inputState.TrueUpdate(); - - /********* - ** Save events + suppress events during save - *********/ - // 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. 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-create - if (!Context.IsWorldReady && !this.IsBetweenCreateEvents) - { - this.IsBetweenCreateEvents = true; - this.Monitor.Log("Context: before save creation.", LogLevel.Trace); - events.SaveCreating.RaiseEmpty(); - } - - // raise before-save - if (Context.IsWorldReady && !this.IsBetweenSaveEvents) - { - this.IsBetweenSaveEvents = true; - this.Monitor.Log("Context: before save.", LogLevel.Trace); - events.Saving.RaiseEmpty(); - } - - // suppress non-save events - events.UnvalidatedUpdateTicking.RaiseEmpty(); - SGame.TicksElapsed++; - base.Update(gameTime); - events.UnvalidatedUpdateTicked.RaiseEmpty(); - 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.OnLoadStageChanged(LoadStage.CreatedSaveFile); - events.SaveCreated.RaiseEmpty(); - } - 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); - events.Saved.RaiseEmpty(); - events.DayStarted.RaiseEmpty(); - } - /********* ** Update context *********/ @@ -464,340 +447,370 @@ namespace StardewModdingAPI.Framework this.Watchers.Update(); /********* - ** Locale changed events + ** Pre-update events *********/ - if (this.Watchers.LocaleWatcher.IsChanged) { - this.Monitor.Log($"Context: locale set to {this.Watchers.LocaleWatcher.CurrentValue}.", LogLevel.Trace); - - this.Watchers.LocaleWatcher.Reset(); - } - - /********* - ** Load / return-to-title events - *********/ - if (wasWorldReady && !Context.IsWorldReady) - this.OnLoadStageChanged(LoadStage.None); - else if (Context.IsWorldReady && Context.LoadStage != LoadStage.Ready) - { - // print context - string context = $"Context: loaded save '{Constants.SaveFolderName}', starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}, locale set to {this.ContentCore.Language}."; - if (Context.IsMultiplayer) + /********* + ** Save created/loaded events + *********/ + if (this.IsBetweenCreateEvents) { - int onlineCount = Game1.getOnlineFarmers().Count(); - context += $" {(Context.IsMainPlayer ? "Main player" : "Farmhand")} with {onlineCount} {(onlineCount == 1 ? "player" : "players")} online."; + // raise after-create + this.IsBetweenCreateEvents = false; + this.Monitor.Log($"Context: after save creation, starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace); + this.OnLoadStageChanged(LoadStage.CreatedSaveFile); + events.SaveCreated.RaiseEmpty(); + } + 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); + events.Saved.RaiseEmpty(); + events.DayStarted.RaiseEmpty(); } - else - context += " Single-player."; - this.Monitor.Log(context, LogLevel.Trace); - // raise events - this.OnLoadStageChanged(LoadStage.Ready); - events.SaveLoaded.RaiseEmpty(); - events.DayStarted.RaiseEmpty(); - } + /********* + ** Locale changed events + *********/ + if (this.Watchers.LocaleWatcher.IsChanged) + { + this.Monitor.Log($"Context: locale set to {this.Watchers.LocaleWatcher.CurrentValue}.", LogLevel.Trace); - /********* - ** Window events - *********/ - // Here we depend on the game's viewport instead of listening to the Window.Resize - // event because we need to notify mods after the game handles the resize, so the - // game's metadata (like Game1.viewport) are updated. That's a bit complicated - // since the game adds & removes its own handler on the fly. - if (this.Watchers.WindowSizeWatcher.IsChanged) - { - if (this.Monitor.IsVerbose) - this.Monitor.Log($"Events: window size changed to {this.Watchers.WindowSizeWatcher.CurrentValue}.", LogLevel.Trace); + this.Watchers.LocaleWatcher.Reset(); + } - Point oldSize = this.Watchers.WindowSizeWatcher.PreviousValue; - Point newSize = this.Watchers.WindowSizeWatcher.CurrentValue; + /********* + ** Load / return-to-title events + *********/ + if (wasWorldReady && !Context.IsWorldReady) + this.OnLoadStageChanged(LoadStage.None); + else if (Context.IsWorldReady && Context.LoadStage != LoadStage.Ready) + { + // print context + string context = $"Context: loaded save '{Constants.SaveFolderName}', starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}, locale set to {this.ContentCore.Language}."; + if (Context.IsMultiplayer) + { + int onlineCount = Game1.getOnlineFarmers().Count(); + context += $" {(Context.IsMainPlayer ? "Main player" : "Farmhand")} with {onlineCount} {(onlineCount == 1 ? "player" : "players")} online."; + } + else + context += " Single-player."; + this.Monitor.Log(context, LogLevel.Trace); - events.WindowResized.Raise(new WindowResizedEventArgs(oldSize, newSize)); - this.Watchers.WindowSizeWatcher.Reset(); - } + // raise events + this.OnLoadStageChanged(LoadStage.Ready); + events.SaveLoaded.RaiseEmpty(); + events.DayStarted.RaiseEmpty(); + } - /********* - ** Input events (if window has focus) - *********/ - if (this.IsActive) - { - // raise events - bool isChatInput = Game1.IsChatting || (Context.IsMultiplayer && Context.IsWorldReady && Game1.activeClickableMenu == null && Game1.currentMinigame == null && inputState.IsAnyDown(Game1.options.chatButton)); - if (!isChatInput) + /********* + ** Window events + *********/ + // Here we depend on the game's viewport instead of listening to the Window.Resize + // event because we need to notify mods after the game handles the resize, so the + // game's metadata (like Game1.viewport) are updated. That's a bit complicated + // since the game adds & removes its own handler on the fly. + if (this.Watchers.WindowSizeWatcher.IsChanged) { - ICursorPosition cursor = this.Input.CursorPosition; + if (this.Monitor.IsVerbose) + this.Monitor.Log($"Events: window size changed to {this.Watchers.WindowSizeWatcher.CurrentValue}.", LogLevel.Trace); - // raise cursor moved event - if (this.Watchers.CursorWatcher.IsChanged) - { - if (events.CursorMoved.HasListeners()) - { - ICursorPosition was = this.Watchers.CursorWatcher.PreviousValue; - ICursorPosition now = this.Watchers.CursorWatcher.CurrentValue; - this.Watchers.CursorWatcher.Reset(); + Point oldSize = this.Watchers.WindowSizeWatcher.PreviousValue; + Point newSize = this.Watchers.WindowSizeWatcher.CurrentValue; - events.CursorMoved.Raise(new CursorMovedEventArgs(was, now)); - } - else - this.Watchers.CursorWatcher.Reset(); - } + events.WindowResized.Raise(new WindowResizedEventArgs(oldSize, newSize)); + this.Watchers.WindowSizeWatcher.Reset(); + } - // raise mouse wheel scrolled - if (this.Watchers.MouseWheelScrollWatcher.IsChanged) + /********* + ** Input events (if window has focus) + *********/ + if (this.IsActive) + { + // raise events + bool isChatInput = Game1.IsChatting || (Context.IsMultiplayer && Context.IsWorldReady && Game1.activeClickableMenu == null && Game1.currentMinigame == null && inputState.IsAnyDown(Game1.options.chatButton)); + if (!isChatInput) { - if (events.MouseWheelScrolled.HasListeners() || this.Monitor.IsVerbose) + ICursorPosition cursor = this.Input.CursorPosition; + + // raise cursor moved event + if (this.Watchers.CursorWatcher.IsChanged) { - int was = this.Watchers.MouseWheelScrollWatcher.PreviousValue; - int now = this.Watchers.MouseWheelScrollWatcher.CurrentValue; - this.Watchers.MouseWheelScrollWatcher.Reset(); + if (events.CursorMoved.HasListeners()) + { + ICursorPosition was = this.Watchers.CursorWatcher.PreviousValue; + ICursorPosition now = this.Watchers.CursorWatcher.CurrentValue; + this.Watchers.CursorWatcher.Reset(); - if (this.Monitor.IsVerbose) - this.Monitor.Log($"Events: mouse wheel scrolled to {now}.", LogLevel.Trace); - events.MouseWheelScrolled.Raise(new MouseWheelScrolledEventArgs(cursor, was, now)); + events.CursorMoved.Raise(new CursorMovedEventArgs(was, now)); + } + else + this.Watchers.CursorWatcher.Reset(); } - else - this.Watchers.MouseWheelScrollWatcher.Reset(); - } - - // raise input button events - foreach (var pair in inputState.ActiveButtons) - { - SButton button = pair.Key; - InputStatus status = pair.Value; - if (status == InputStatus.Pressed) + // raise mouse wheel scrolled + if (this.Watchers.MouseWheelScrollWatcher.IsChanged) { - if (this.Monitor.IsVerbose) - this.Monitor.Log($"Events: button {button} pressed.", LogLevel.Trace); + if (events.MouseWheelScrolled.HasListeners() || this.Monitor.IsVerbose) + { + int was = this.Watchers.MouseWheelScrollWatcher.PreviousValue; + int now = this.Watchers.MouseWheelScrollWatcher.CurrentValue; + this.Watchers.MouseWheelScrollWatcher.Reset(); - events.ButtonPressed.Raise(new ButtonPressedEventArgs(button, cursor, inputState)); + if (this.Monitor.IsVerbose) + this.Monitor.Log($"Events: mouse wheel scrolled to {now}.", LogLevel.Trace); + events.MouseWheelScrolled.Raise(new MouseWheelScrolledEventArgs(cursor, was, now)); + } + else + this.Watchers.MouseWheelScrollWatcher.Reset(); } - else if (status == InputStatus.Released) + + // raise input button events + foreach (var pair in inputState.ActiveButtons) { - if (this.Monitor.IsVerbose) - this.Monitor.Log($"Events: button {button} released.", LogLevel.Trace); + SButton button = pair.Key; + InputStatus status = pair.Value; + + if (status == InputStatus.Pressed) + { + if (this.Monitor.IsVerbose) + this.Monitor.Log($"Events: button {button} pressed.", LogLevel.Trace); - events.ButtonReleased.Raise(new ButtonReleasedEventArgs(button, cursor, inputState)); + events.ButtonPressed.Raise(new ButtonPressedEventArgs(button, cursor, inputState)); + } + else if (status == InputStatus.Released) + { + if (this.Monitor.IsVerbose) + this.Monitor.Log($"Events: button {button} released.", LogLevel.Trace); + + events.ButtonReleased.Raise(new ButtonReleasedEventArgs(button, cursor, inputState)); + } } } } - } - - /********* - ** Menu events - *********/ - if (this.Watchers.ActiveMenuWatcher.IsChanged) - { - IClickableMenu was = this.Watchers.ActiveMenuWatcher.PreviousValue; - IClickableMenu now = this.Watchers.ActiveMenuWatcher.CurrentValue; - this.Watchers.ActiveMenuWatcher.Reset(); // reset here so a mod changing the menu will be raised as a new event afterwards - if (this.Monitor.IsVerbose) - this.Monitor.Log($"Context: menu changed from {was?.GetType().FullName ?? "none"} to {now?.GetType().FullName ?? "none"}.", LogLevel.Trace); + /********* + ** Menu events + *********/ + if (this.Watchers.ActiveMenuWatcher.IsChanged) + { + IClickableMenu was = this.Watchers.ActiveMenuWatcher.PreviousValue; + IClickableMenu now = this.Watchers.ActiveMenuWatcher.CurrentValue; + this.Watchers.ActiveMenuWatcher.Reset(); // reset here so a mod changing the menu will be raised as a new event afterwards - // raise menu events - events.MenuChanged.Raise(new MenuChangedEventArgs(was, now)); - } + if (this.Monitor.IsVerbose) + this.Monitor.Log($"Context: menu changed from {was?.GetType().FullName ?? "none"} to {now?.GetType().FullName ?? "none"}.", LogLevel.Trace); - /********* - ** World & player events - *********/ - if (Context.IsWorldReady) - { - bool raiseWorldEvents = !this.Watchers.SaveIdWatcher.IsChanged; // don't report changes from unloaded => loaded + // raise menu events + events.MenuChanged.Raise(new MenuChangedEventArgs(was, now)); + } - // raise location changes - if (this.Watchers.LocationsWatcher.IsChanged) + /********* + ** World & player events + *********/ + if (Context.IsWorldReady) { - // location list changes - if (this.Watchers.LocationsWatcher.IsLocationListChanged) - { - GameLocation[] added = this.Watchers.LocationsWatcher.Added.ToArray(); - GameLocation[] removed = this.Watchers.LocationsWatcher.Removed.ToArray(); - this.Watchers.LocationsWatcher.ResetLocationList(); + bool raiseWorldEvents = !this.Watchers.SaveIdWatcher.IsChanged; // don't report changes from unloaded => loaded - if (this.Monitor.IsVerbose) + // raise location changes + if (this.Watchers.LocationsWatcher.IsChanged) + { + // location list changes + if (this.Watchers.LocationsWatcher.IsLocationListChanged) { - string addedText = added.Any() ? string.Join(", ", added.Select(p => p.Name)) : "none"; - string removedText = removed.Any() ? string.Join(", ", removed.Select(p => p.Name)) : "none"; - this.Monitor.Log($"Context: location list changed (added {addedText}; removed {removedText}).", LogLevel.Trace); - } + GameLocation[] added = this.Watchers.LocationsWatcher.Added.ToArray(); + GameLocation[] removed = this.Watchers.LocationsWatcher.Removed.ToArray(); + this.Watchers.LocationsWatcher.ResetLocationList(); - events.LocationListChanged.Raise(new LocationListChangedEventArgs(added, removed)); - } + if (this.Monitor.IsVerbose) + { + string addedText = added.Any() ? string.Join(", ", added.Select(p => p.Name)) : "none"; + string removedText = removed.Any() ? string.Join(", ", removed.Select(p => p.Name)) : "none"; + this.Monitor.Log($"Context: location list changed (added {addedText}; removed {removedText}).", LogLevel.Trace); + } - // raise location contents changed - if (raiseWorldEvents) - { - foreach (LocationTracker watcher in this.Watchers.LocationsWatcher.Locations) + events.LocationListChanged.Raise(new LocationListChangedEventArgs(added, removed)); + } + + // raise location contents changed + if (raiseWorldEvents) { - // buildings changed - if (watcher.BuildingsWatcher.IsChanged) + foreach (LocationTracker watcher in this.Watchers.LocationsWatcher.Locations) { - GameLocation location = watcher.Location; - Building[] added = watcher.BuildingsWatcher.Added.ToArray(); - Building[] removed = watcher.BuildingsWatcher.Removed.ToArray(); - watcher.BuildingsWatcher.Reset(); + // buildings changed + if (watcher.BuildingsWatcher.IsChanged) + { + GameLocation location = watcher.Location; + Building[] added = watcher.BuildingsWatcher.Added.ToArray(); + Building[] removed = watcher.BuildingsWatcher.Removed.ToArray(); + watcher.BuildingsWatcher.Reset(); - events.BuildingListChanged.Raise(new BuildingListChangedEventArgs(location, added, removed)); - } + events.BuildingListChanged.Raise(new BuildingListChangedEventArgs(location, added, removed)); + } - // debris changed - if (watcher.DebrisWatcher.IsChanged) - { - GameLocation location = watcher.Location; - Debris[] added = watcher.DebrisWatcher.Added.ToArray(); - Debris[] removed = watcher.DebrisWatcher.Removed.ToArray(); - watcher.DebrisWatcher.Reset(); + // debris changed + if (watcher.DebrisWatcher.IsChanged) + { + GameLocation location = watcher.Location; + Debris[] added = watcher.DebrisWatcher.Added.ToArray(); + Debris[] removed = watcher.DebrisWatcher.Removed.ToArray(); + watcher.DebrisWatcher.Reset(); - events.DebrisListChanged.Raise(new DebrisListChangedEventArgs(location, added, removed)); - } + events.DebrisListChanged.Raise(new DebrisListChangedEventArgs(location, added, removed)); + } - // large terrain features changed - if (watcher.LargeTerrainFeaturesWatcher.IsChanged) - { - GameLocation location = watcher.Location; - LargeTerrainFeature[] added = watcher.LargeTerrainFeaturesWatcher.Added.ToArray(); - LargeTerrainFeature[] removed = watcher.LargeTerrainFeaturesWatcher.Removed.ToArray(); - watcher.LargeTerrainFeaturesWatcher.Reset(); + // large terrain features changed + if (watcher.LargeTerrainFeaturesWatcher.IsChanged) + { + GameLocation location = watcher.Location; + LargeTerrainFeature[] added = watcher.LargeTerrainFeaturesWatcher.Added.ToArray(); + LargeTerrainFeature[] removed = watcher.LargeTerrainFeaturesWatcher.Removed.ToArray(); + watcher.LargeTerrainFeaturesWatcher.Reset(); - events.LargeTerrainFeatureListChanged.Raise(new LargeTerrainFeatureListChangedEventArgs(location, added, removed)); - } + events.LargeTerrainFeatureListChanged.Raise(new LargeTerrainFeatureListChangedEventArgs(location, added, removed)); + } - // NPCs changed - if (watcher.NpcsWatcher.IsChanged) - { - GameLocation location = watcher.Location; - NPC[] added = watcher.NpcsWatcher.Added.ToArray(); - NPC[] removed = watcher.NpcsWatcher.Removed.ToArray(); - watcher.NpcsWatcher.Reset(); + // NPCs changed + if (watcher.NpcsWatcher.IsChanged) + { + GameLocation location = watcher.Location; + NPC[] added = watcher.NpcsWatcher.Added.ToArray(); + NPC[] removed = watcher.NpcsWatcher.Removed.ToArray(); + watcher.NpcsWatcher.Reset(); - events.NpcListChanged.Raise(new NpcListChangedEventArgs(location, added, removed)); - } + events.NpcListChanged.Raise(new NpcListChangedEventArgs(location, added, removed)); + } - // objects changed - if (watcher.ObjectsWatcher.IsChanged) - { - GameLocation location = watcher.Location; - KeyValuePair[] added = watcher.ObjectsWatcher.Added.ToArray(); - KeyValuePair[] removed = watcher.ObjectsWatcher.Removed.ToArray(); - watcher.ObjectsWatcher.Reset(); + // objects changed + if (watcher.ObjectsWatcher.IsChanged) + { + GameLocation location = watcher.Location; + KeyValuePair[] added = watcher.ObjectsWatcher.Added.ToArray(); + KeyValuePair[] removed = watcher.ObjectsWatcher.Removed.ToArray(); + watcher.ObjectsWatcher.Reset(); - events.ObjectListChanged.Raise(new ObjectListChangedEventArgs(location, added, removed)); - } + events.ObjectListChanged.Raise(new ObjectListChangedEventArgs(location, added, removed)); + } - // terrain features changed - if (watcher.TerrainFeaturesWatcher.IsChanged) - { - GameLocation location = watcher.Location; - KeyValuePair[] added = watcher.TerrainFeaturesWatcher.Added.ToArray(); - KeyValuePair[] removed = watcher.TerrainFeaturesWatcher.Removed.ToArray(); - watcher.TerrainFeaturesWatcher.Reset(); + // terrain features changed + if (watcher.TerrainFeaturesWatcher.IsChanged) + { + GameLocation location = watcher.Location; + KeyValuePair[] added = watcher.TerrainFeaturesWatcher.Added.ToArray(); + KeyValuePair[] removed = watcher.TerrainFeaturesWatcher.Removed.ToArray(); + watcher.TerrainFeaturesWatcher.Reset(); - events.TerrainFeatureListChanged.Raise(new TerrainFeatureListChangedEventArgs(location, added, removed)); + events.TerrainFeatureListChanged.Raise(new TerrainFeatureListChangedEventArgs(location, added, removed)); + } } } + else + this.Watchers.LocationsWatcher.Reset(); } - else - this.Watchers.LocationsWatcher.Reset(); - } - - // raise time changed - if (raiseWorldEvents && this.Watchers.TimeWatcher.IsChanged) - { - int was = this.Watchers.TimeWatcher.PreviousValue; - int now = this.Watchers.TimeWatcher.CurrentValue; - this.Watchers.TimeWatcher.Reset(); - if (this.Monitor.IsVerbose) - this.Monitor.Log($"Events: time changed from {was} to {now}.", LogLevel.Trace); - - events.TimeChanged.Raise(new TimeChangedEventArgs(was, now)); - } - else - this.Watchers.TimeWatcher.Reset(); - - // raise player events - if (raiseWorldEvents) - { - PlayerTracker playerTracker = this.Watchers.CurrentPlayerTracker; - - // raise current location changed - if (playerTracker.TryGetNewLocation(out GameLocation newLocation)) + // raise time changed + if (raiseWorldEvents && this.Watchers.TimeWatcher.IsChanged) { + int was = this.Watchers.TimeWatcher.PreviousValue; + int now = this.Watchers.TimeWatcher.CurrentValue; + this.Watchers.TimeWatcher.Reset(); + if (this.Monitor.IsVerbose) - this.Monitor.Log($"Context: set location to {newLocation.Name}.", LogLevel.Trace); + this.Monitor.Log($"Events: time changed from {was} to {now}.", LogLevel.Trace); - GameLocation oldLocation = playerTracker.LocationWatcher.PreviousValue; - events.Warped.Raise(new WarpedEventArgs(playerTracker.Player, oldLocation, newLocation)); + events.TimeChanged.Raise(new TimeChangedEventArgs(was, now)); } + else + this.Watchers.TimeWatcher.Reset(); - // raise player leveled up a skill - foreach (KeyValuePair> pair in playerTracker.GetChangedSkills()) + // raise player events + if (raiseWorldEvents) { - if (this.Monitor.IsVerbose) - this.Monitor.Log($"Events: player skill '{pair.Key}' changed from {pair.Value.PreviousValue} to {pair.Value.CurrentValue}.", LogLevel.Trace); + PlayerTracker playerTracker = this.Watchers.CurrentPlayerTracker; - events.LevelChanged.Raise(new LevelChangedEventArgs(playerTracker.Player, pair.Key, pair.Value.PreviousValue, pair.Value.CurrentValue)); - } + // raise current location changed + if (playerTracker.TryGetNewLocation(out GameLocation newLocation)) + { + if (this.Monitor.IsVerbose) + this.Monitor.Log($"Context: set location to {newLocation.Name}.", LogLevel.Trace); - // raise player inventory changed - ItemStackChange[] changedItems = playerTracker.GetInventoryChanges().ToArray(); - if (changedItems.Any()) - { - if (this.Monitor.IsVerbose) - this.Monitor.Log("Events: player inventory changed.", LogLevel.Trace); - events.InventoryChanged.Raise(new InventoryChangedEventArgs(playerTracker.Player, changedItems)); - } + GameLocation oldLocation = playerTracker.LocationWatcher.PreviousValue; + events.Warped.Raise(new WarpedEventArgs(playerTracker.Player, oldLocation, newLocation)); + } - // raise mine level changed - if (playerTracker.TryGetNewMineLevel(out int mineLevel)) - { - if (this.Monitor.IsVerbose) - this.Monitor.Log($"Context: mine level changed to {mineLevel}.", LogLevel.Trace); + // raise player leveled up a skill + foreach (KeyValuePair> pair in playerTracker.GetChangedSkills()) + { + if (this.Monitor.IsVerbose) + this.Monitor.Log($"Events: player skill '{pair.Key}' changed from {pair.Value.PreviousValue} to {pair.Value.CurrentValue}.", LogLevel.Trace); + + events.LevelChanged.Raise(new LevelChangedEventArgs(playerTracker.Player, pair.Key, pair.Value.PreviousValue, pair.Value.CurrentValue)); + } + + // raise player inventory changed + ItemStackChange[] changedItems = playerTracker.GetInventoryChanges().ToArray(); + if (changedItems.Any()) + { + if (this.Monitor.IsVerbose) + this.Monitor.Log("Events: player inventory changed.", LogLevel.Trace); + events.InventoryChanged.Raise(new InventoryChangedEventArgs(playerTracker.Player, changedItems)); + } + + // raise mine level changed + if (playerTracker.TryGetNewMineLevel(out int mineLevel)) + { + if (this.Monitor.IsVerbose) + this.Monitor.Log($"Context: mine level changed to {mineLevel}.", LogLevel.Trace); + } } + this.Watchers.CurrentPlayerTracker?.Reset(); + + // update save ID watcher + this.Watchers.SaveIdWatcher.Reset(); + } + + /********* + ** Game update + *********/ + // game launched + bool isFirstTick = SGame.TicksElapsed == 0; + if (isFirstTick) + { + Context.IsGameLaunched = true; + events.GameLaunched.Raise(new GameLaunchedEventArgs()); } - this.Watchers.CurrentPlayerTracker?.Reset(); - } - // update save ID watcher - this.Watchers.SaveIdWatcher.Reset(); + // preloaded + if (Context.IsSaveLoaded && Context.LoadStage != LoadStage.Loaded && Context.LoadStage != LoadStage.Ready && Game1.dayOfMonth != 0) + this.OnLoadStageChanged(LoadStage.Loaded); + } /********* - ** Game update + ** Game update tick *********/ - // game launched - bool isFirstTick = SGame.TicksElapsed == 0; - if (isFirstTick) { - Context.IsGameLaunched = true; - events.GameLaunched.Raise(new GameLaunchedEventArgs()); - } - - // preloaded - if (Context.IsSaveLoaded && Context.LoadStage != LoadStage.Loaded && Context.LoadStage != LoadStage.Ready && Game1.dayOfMonth != 0) - this.OnLoadStageChanged(LoadStage.Loaded); + bool isOneSecond = SGame.TicksElapsed % 60 == 0; + events.UnvalidatedUpdateTicking.RaiseEmpty(); + events.UpdateTicking.RaiseEmpty(); + if (isOneSecond) + events.OneSecondUpdateTicking.RaiseEmpty(); + try + { + this.Input.UpdateSuppression(); + SGame.TicksElapsed++; + base.Update(gameTime); + } + catch (Exception ex) + { + this.MonitorForGame.Log($"An error occured in the base update loop: {ex.GetLogSummary()}", LogLevel.Error); + } - // update tick - bool isOneSecond = SGame.TicksElapsed % 60 == 0; - events.UnvalidatedUpdateTicking.RaiseEmpty(); - events.UpdateTicking.RaiseEmpty(); - if (isOneSecond) - events.OneSecondUpdateTicking.RaiseEmpty(); - try - { - this.Input.UpdateSuppression(); - SGame.TicksElapsed++; - base.Update(gameTime); - } - catch (Exception ex) - { - this.MonitorForGame.Log($"An error occured in the base update loop: {ex.GetLogSummary()}", LogLevel.Error); + events.UnvalidatedUpdateTicked.RaiseEmpty(); + events.UpdateTicked.RaiseEmpty(); + if (isOneSecond) + events.OneSecondUpdateTicked.RaiseEmpty(); } - events.UnvalidatedUpdateTicked.RaiseEmpty(); - events.UpdateTicked.RaiseEmpty(); - if (isOneSecond) - events.OneSecondUpdateTicked.RaiseEmpty(); /********* ** Update events -- cgit