From 270d436a176904ab39fc0ce97da2027dd6ac1114 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 18 Dec 2018 20:15:39 -0500 Subject: remove shell code in Windows installer to reduce antivirus false positives --- docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 813f40a7..41a6cd83 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,4 +1,8 @@ # Release notes +## Upcoming release +* For players: + * Tweaked installer to reduce antivirus false positives. + ## 2.9.3 * For players: * Fixed errors hovering items in some cases with SMAPI 2.9.2. -- cgit From 7294cb3cc5aeed2849827b192c54db2059fe6a5f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 22 Dec 2018 16:08:52 -0500 Subject: add world_clear console command --- docs/release-notes.md | 1 + .../Framework/Commands/World/ClearCommand.cs | 246 +++++++++++++++++++++ .../StardewModdingAPI.Mods.ConsoleCommands.csproj | 1 + 3 files changed, 248 insertions(+) create mode 100644 src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 41a6cd83..ca47a687 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,6 +1,7 @@ # Release notes ## Upcoming release * For players: + * Added `world_clear` console command to remove spawned or placed entities. * Tweaked installer to reduce antivirus false positives. ## 2.9.3 diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs new file mode 100644 index 00000000..9b5f07de --- /dev/null +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using StardewValley; +using StardewValley.Locations; +using StardewValley.Objects; +using StardewValley.TerrainFeatures; +using SObject = StardewValley.Object; + +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World +{ + /// A command which clears in-game objects. + internal class ClearCommand : TrainerCommand + { + /********* + ** Properties + *********/ + /// The valid types that can be cleared. + private readonly string[] ValidTypes = { "debris", "fruit-trees", "grass", "trees", "everything" }; + + /// The resource clump IDs to consider debris. + private readonly int[] DebrisClumps = { ResourceClump.stumpIndex, ResourceClump.hollowLogIndex, ResourceClump.meteoriteIndex, ResourceClump.boulderIndex }; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + public ClearCommand() + : base( + name: "world_clear", + description: "Clears in-game entities in a given location.\n\n" + + "Usage: world_clear \n" + + "- location: the location name for which to clear objects (like Farm), or 'current' for the current location.\n" + + " - object type: the type of object clear. You can specify 'debris' (stones/twigs/weeds and dead crops), 'grass', and 'trees' / 'fruit-trees'. You can also specify 'everything', which includes things not removed by the other types (like furniture or resource clumps)." + ) + { } + + /// Handle the command. + /// Writes messages to the console and log file. + /// The command name. + /// The command arguments. + public override void Handle(IMonitor monitor, string command, ArgumentParser args) + { + // check context + if (!Context.IsWorldReady) + { + monitor.Log("You need to load a save to use this command.", LogLevel.Error); + return; + } + + // parse arguments + if (!args.TryGet(0, "location", out string locationName, required: true)) + return; + if (!args.TryGet(1, "object type", out string type, required: true, oneOf: this.ValidTypes)) + return; + + // get target location + GameLocation location = Game1.locations.FirstOrDefault(p => p.Name != null && p.Name.Equals(locationName, StringComparison.InvariantCultureIgnoreCase)); + if (location == null && locationName == "current") + location = Game1.currentLocation; + if (location == null) + { + string[] locationNames = (from loc in Game1.locations where !string.IsNullOrWhiteSpace(loc.Name) orderby loc.Name select loc.Name).ToArray(); + monitor.Log($"Could not find a location with that name. Must be one of [{string.Join(", ", locationNames)}].", LogLevel.Error); + return; + } + + // apply + switch (type) + { + case "debris": + { + int removed = 0; + foreach (var pair in location.terrainFeatures.Pairs.ToArray()) + { + TerrainFeature feature = pair.Value; + if (feature is HoeDirt dirt && dirt.crop?.dead == true) + { + dirt.crop = null; + removed++; + } + } + + removed += + this.RemoveObjects(location, obj => obj.Name.ToLower().Contains("weed") || obj.Name == "Twig" || obj.Name == "Stone") + + this.RemoveResourceClumps(location, clump => this.DebrisClumps.Contains(clump.parentSheetIndex.Value)); + + monitor.Log($"Done! Removed {removed} entities from {location.Name}.", LogLevel.Info); + break; + } + + case "fruit-trees": + { + int removed = this.RemoveTerrainFeatures(location, feature => feature is FruitTree); + monitor.Log($"Done! Removed {removed} entities from {location.Name}.", LogLevel.Info); + break; + } + + case "grass": + { + int removed = this.RemoveTerrainFeatures(location, feature => feature is Grass); + monitor.Log($"Done! Removed {removed} entities from {location.Name}.", LogLevel.Info); + break; + } + + case "trees": + { + int removed = this.RemoveTerrainFeatures(location, feature => feature is Tree); + monitor.Log($"Done! Removed {removed} entities from {location.Name}.", LogLevel.Info); + break; + } + + case "everything": + { + int removed = + this.RemoveFurniture(location, p => true) + + this.RemoveObjects(location, p => true) + + this.RemoveTerrainFeatures(location, p => true) + + this.RemoveLargeTerrainFeatures(location, p => true) + + this.RemoveResourceClumps(location, p => true); + monitor.Log($"Done! Removed {removed} entities from {location.Name}.", LogLevel.Info); + break; + } + + default: + monitor.Log($"Unknown type '{type}'. Must be one [{string.Join(", ", this.ValidTypes)}].", LogLevel.Error); + break; + } + } + + + /********* + ** Private methods + *********/ + /// Remove objects from a location matching a lambda. + /// The location to search. + /// Whether an entity should be removed. + /// Returns the number of removed entities. + private int RemoveObjects(GameLocation location, Func shouldRemove) + { + int removed = 0; + + foreach (var pair in location.Objects.Pairs.ToArray()) + { + if (shouldRemove(pair.Value)) + { + location.Objects.Remove(pair.Key); + removed++; + } + } + + return removed; + } + + /// Remove terrain features from a location matching a lambda. + /// The location to search. + /// Whether an entity should be removed. + /// Returns the number of removed entities. + private int RemoveTerrainFeatures(GameLocation location, Func shouldRemove) + { + int removed = 0; + + foreach (var pair in location.terrainFeatures.Pairs.ToArray()) + { + if (shouldRemove(pair.Value)) + { + location.terrainFeatures.Remove(pair.Key); + removed++; + } + } + + return removed; + } + + /// Remove large terrain features from a location matching a lambda. + /// The location to search. + /// Whether an entity should be removed. + /// Returns the number of removed entities. + private int RemoveLargeTerrainFeatures(GameLocation location, Func shouldRemove) + { + int removed = 0; + + foreach (LargeTerrainFeature feature in location.largeTerrainFeatures.ToArray()) + { + if (shouldRemove(feature)) + { + location.largeTerrainFeatures.Remove(feature); + removed++; + } + } + + return removed; + } + + /// Remove resource clumps from a location matching a lambda. + /// The location to search. + /// Whether an entity should be removed. + /// Returns the number of removed entities. + private int RemoveResourceClumps(GameLocation location, Func shouldRemove) + { + int removed = 0; + + // get resource clumps + IList resourceClumps = + (location as Farm)?.resourceClumps + ?? (IList)(location as Woods)?.stumps + ?? new List(); + + // remove matching clumps + foreach (var clump in resourceClumps.ToArray()) + { + if (shouldRemove(clump)) + { + resourceClumps.Remove(clump); + removed++; + } + } + + return removed; + } + + /// Remove furniture from a location matching a lambda. + /// The location to search. + /// Whether an entity should be removed. + /// Returns the number of removed entities. + private int RemoveFurniture(GameLocation location, Func shouldRemove) + { + int removed = 0; + + if (location is DecoratableLocation decoratableLocation) + { + foreach (Furniture furniture in decoratableLocation.furniture.ToArray()) + { + if (shouldRemove(furniture)) + { + decoratableLocation.furniture.Remove(furniture); + removed++; + } + } + } + + return removed; + } + } +} diff --git a/src/SMAPI.Mods.ConsoleCommands/StardewModdingAPI.Mods.ConsoleCommands.csproj b/src/SMAPI.Mods.ConsoleCommands/StardewModdingAPI.Mods.ConsoleCommands.csproj index d1f16e41..a3237a3d 100644 --- a/src/SMAPI.Mods.ConsoleCommands/StardewModdingAPI.Mods.ConsoleCommands.csproj +++ b/src/SMAPI.Mods.ConsoleCommands/StardewModdingAPI.Mods.ConsoleCommands.csproj @@ -62,6 +62,7 @@ + -- cgit From 4b325f61b370b24403fa10616178dceefa773420 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 23 Dec 2018 16:51:38 -0500 Subject: allow Read/WriteSaveFile as soon as the save is loaded --- docs/release-notes.md | 3 +++ src/SMAPI/Framework/ModHelpers/DataHelper.cs | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index ca47a687..eacf0955 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,9 @@ * Added `world_clear` console command to remove spawned or placed entities. * Tweaked installer to reduce antivirus false positives. +* For modders: + * You can now use `ReadSaveData` or `WriteSaveData` immediately after the save is loaded, before the in-game world is initialised. + ## 2.9.3 * For players: * Fixed errors hovering items in some cases with SMAPI 2.9.2. diff --git a/src/SMAPI/Framework/ModHelpers/DataHelper.cs b/src/SMAPI/Framework/ModHelpers/DataHelper.cs index e5100aed..242b8ab1 100644 --- a/src/SMAPI/Framework/ModHelpers/DataHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/DataHelper.cs @@ -77,9 +77,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The player hasn't loaded a save file yet or isn't the main player. public TModel ReadSaveData(string key) where TModel : class { - if (!Context.IsSaveLoaded) + if (!Game1.hasLoadedGame) throw new InvalidOperationException($"Can't use {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.ReadSaveData)} when a save file isn't loaded."); - if (!Context.IsMainPlayer) + if (!Game1.IsMasterGame) throw new InvalidOperationException($"Can't use {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.ReadSaveData)} because this isn't the main player. (Save files are stored on the main player's computer.)"); return Game1.CustomData.TryGetValue(this.GetSaveFileKey(key), out string value) @@ -94,9 +94,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The player hasn't loaded a save file yet or isn't the main player. public void WriteSaveData(string key, TModel data) where TModel : class { - if (!Context.IsSaveLoaded) + if (!Game1.hasLoadedGame) throw new InvalidOperationException($"Can't use {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.WriteSaveData)} when a save file isn't loaded."); - if (!Context.IsMainPlayer) + if (!Game1.IsMasterGame) throw new InvalidOperationException($"Can't use {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.ReadSaveData)} because this isn't the main player. (Save files are stored on the main player's computer.)"); string internalKey = this.GetSaveFileKey(key); -- cgit From 041bd2d6ba726eeea88afed3be307343a6f9286b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 23 Dec 2018 19:26:02 -0500 Subject: add Specialised.SavePreloaded event --- docs/release-notes.md | 3 ++- src/SMAPI/Events/IGameLoopEvents.cs | 2 +- src/SMAPI/Events/ISpecialisedEvents.cs | 3 +++ src/SMAPI/Events/SavePreloadedEventArgs.cs | 7 +++++++ src/SMAPI/Framework/Events/EventManager.cs | 6 +++++- src/SMAPI/Framework/Events/ModGameLoopEvents.cs | 2 +- src/SMAPI/Framework/Events/ModSpecialisedEvents.cs | 7 +++++++ src/SMAPI/Framework/SGame.cs | 23 ++++++++++++++++++---- src/SMAPI/StardewModdingAPI.csproj | 1 + 9 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 src/SMAPI/Events/SavePreloadedEventArgs.cs (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index eacf0955..3daca07f 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -5,7 +5,8 @@ * Tweaked installer to reduce antivirus false positives. * For modders: - * You can now use `ReadSaveData` or `WriteSaveData` immediately after the save is loaded, before the in-game world is initialised. + * Added `Specialised.SavePreloaded` event, which is raised immediately after a save is loaded but before the in-game world is fully initialised. + * You can now use read/write save data as soon as the save is loaded (instead of once the world is initialised). ## 2.9.3 * For players: diff --git a/src/SMAPI/Events/IGameLoopEvents.cs b/src/SMAPI/Events/IGameLoopEvents.cs index e1900f79..ea79aa74 100644 --- a/src/SMAPI/Events/IGameLoopEvents.cs +++ b/src/SMAPI/Events/IGameLoopEvents.cs @@ -26,7 +26,7 @@ namespace StardewModdingAPI.Events /// Raised after the game finishes writing data to the save file (except the initial save creation). event EventHandler Saved; - /// Raised after the player loads a save slot. + /// Raised after the player loads a save slot and the world is initialised. event EventHandler SaveLoaded; /// Raised after the game begins a new day (including when the player loads a save). diff --git a/src/SMAPI/Events/ISpecialisedEvents.cs b/src/SMAPI/Events/ISpecialisedEvents.cs index 928cd05d..2a19113c 100644 --- a/src/SMAPI/Events/ISpecialisedEvents.cs +++ b/src/SMAPI/Events/ISpecialisedEvents.cs @@ -5,6 +5,9 @@ namespace StardewModdingAPI.Events /// Events serving specialised edge cases that shouldn't be used by most mods. public interface ISpecialisedEvents { + /// Raised immediately after the player loads a save slot, but before the world is fully initialised. The save and game data are available at this point, but some in-game content (like location maps) haven't been initialised yet. + event EventHandler SavePreloaded; + /// Raised before the game state is updated (≈60 times per second), regardless of normal SMAPI validation. This event is not thread-safe and may be invoked while game logic is running asynchronously. Changes to game state in this method may crash the game or corrupt an in-progress save. Do not use this event unless you're fully aware of the context in which your code will be run. Mods using this event will trigger a stability warning in the SMAPI console. event EventHandler UnvalidatedUpdateTicking; diff --git a/src/SMAPI/Events/SavePreloadedEventArgs.cs b/src/SMAPI/Events/SavePreloadedEventArgs.cs new file mode 100644 index 00000000..03990f5a --- /dev/null +++ b/src/SMAPI/Events/SavePreloadedEventArgs.cs @@ -0,0 +1,7 @@ +using System; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for an event. + public class SavePreloadedEventArgs : EventArgs { } +} diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs index 0ad85adf..bd862046 100644 --- a/src/SMAPI/Framework/Events/EventManager.cs +++ b/src/SMAPI/Framework/Events/EventManager.cs @@ -70,7 +70,7 @@ namespace StardewModdingAPI.Framework.Events /// Raised after the game finishes writing data to the save file (except the initial save creation). public readonly ManagedEvent Saved; - /// Raised after the player loads a save slot. + /// Raised after the player loads a save slot and the world is initialised. public readonly ManagedEvent SaveLoaded; /// Raised after the game begins a new day, including when loading a save. @@ -151,6 +151,9 @@ namespace StardewModdingAPI.Framework.Events /**** ** Specialised ****/ + /// Raised immediately after the player loads a save slot, but before the world is fully initialised. + public readonly ManagedEvent SavePreloaded; + /// Raised before the game performs its overall update tick (≈60 times per second). See notes on . public readonly ManagedEvent UnvalidatedUpdateTicking; @@ -408,6 +411,7 @@ namespace StardewModdingAPI.Framework.Events this.ObjectListChanged = ManageEventOf(nameof(IModEvents.World), nameof(IWorldEvents.ObjectListChanged)); this.TerrainFeatureListChanged = ManageEventOf(nameof(IModEvents.World), nameof(IWorldEvents.TerrainFeatureListChanged)); + this.SavePreloaded = ManageEventOf(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.SavePreloaded)); this.UnvalidatedUpdateTicking = ManageEventOf(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.UnvalidatedUpdateTicking)); this.UnvalidatedUpdateTicked = ManageEventOf(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.UnvalidatedUpdateTicked)); diff --git a/src/SMAPI/Framework/Events/ModGameLoopEvents.cs b/src/SMAPI/Framework/Events/ModGameLoopEvents.cs index a5beac99..3a764ab0 100644 --- a/src/SMAPI/Framework/Events/ModGameLoopEvents.cs +++ b/src/SMAPI/Framework/Events/ModGameLoopEvents.cs @@ -58,7 +58,7 @@ namespace StardewModdingAPI.Framework.Events remove => this.EventManager.Saved.Remove(value); } - /// Raised after the player loads a save slot. + /// Raised after the player loads a save slot and the world is initialised. public event EventHandler SaveLoaded { add => this.EventManager.SaveLoaded.Add(value); diff --git a/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs b/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs index 17c32bb8..83e349cf 100644 --- a/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs +++ b/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs @@ -9,6 +9,13 @@ namespace StardewModdingAPI.Framework.Events /********* ** Accessors *********/ + /// Raised immediately after the player loads a save slot, but before the world is fully initialised. The save and game data are available at this point, but some in-game content (like location maps) haven't been initialised yet. + public event EventHandler SavePreloaded + { + add => this.EventManager.SavePreloaded.Add(value); + remove => this.EventManager.SavePreloaded.Remove(value); + } + /// Raised before the game state is updated (≈60 times per second), regardless of normal SMAPI validation. This event is not thread-safe and may be invoked while game logic is running asynchronously. Changes to game state in this method may crash the game or corrupt an in-progress save. Do not use this event unless you're fully aware of the context in which your code will be run. Mods using this event will trigger a stability warning in the SMAPI console. public event EventHandler UnvalidatedUpdateTicking { diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index d515d3ad..befd9cef 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -69,8 +69,11 @@ namespace StardewModdingAPI.Framework /// Skipping a few frames ensures the game finishes initialising the world before mods try to change it. private readonly Countdown AfterLoadTimer = new Countdown(5); + /// Whether was raised for this session. + private bool RaisedPreloadedEvent; + /// Whether the after-load events were raised for this session. - private bool RaisedAfterLoadEvent; + private bool RaisedLoadedEvent; /// Whether the game is saving and SMAPI has already raised . private bool IsBetweenSaveEvents; @@ -217,6 +220,7 @@ namespace StardewModdingAPI.Framework private void OnReturnedToTitle() { this.Monitor.Log("Context: returned to title", LogLevel.Trace); + this.RaisedPreloadedEvent = false; this.Multiplayer.CleanupOnMultiplayerExit(); this.Events.ReturnedToTitle.RaiseEmpty(); #if !SMAPI_3_0_STRICT @@ -466,7 +470,7 @@ namespace StardewModdingAPI.Framework *********/ if (wasWorldReady && !Context.IsWorldReady) this.OnReturnedToTitle(); - else if (!this.RaisedAfterLoadEvent && Context.IsWorldReady) + else if (!this.RaisedLoadedEvent && Context.IsWorldReady) { // print context string context = $"Context: loaded saved game '{Constants.SaveFolderName}', starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}."; @@ -480,7 +484,7 @@ namespace StardewModdingAPI.Framework this.Monitor.Log(context, LogLevel.Trace); // raise events - this.RaisedAfterLoadEvent = true; + this.RaisedLoadedEvent = true; this.Events.SaveLoaded.RaiseEmpty(); this.Events.DayStarted.RaiseEmpty(); #if !SMAPI_3_0_STRICT @@ -824,8 +828,19 @@ namespace StardewModdingAPI.Framework ** Game update *********/ this.TicksElapsed++; + + // game launched if (this.TicksElapsed == 1) this.Events.GameLaunched.Raise(new GameLaunchedEventArgs()); + + // preloaded + if (Context.IsSaveLoaded && !this.RaisedPreloadedEvent) + { + this.RaisedPreloadedEvent = true; + this.Events.SavePreloaded.RaiseEmpty(); + } + + // update tick this.Events.UnvalidatedUpdateTicking.Raise(new UnvalidatedUpdateTickingEventArgs(this.TicksElapsed)); this.Events.UpdateTicking.Raise(new UpdateTickingEventArgs(this.TicksElapsed)); try @@ -1639,7 +1654,7 @@ namespace StardewModdingAPI.Framework { Context.IsWorldReady = false; this.AfterLoadTimer.Reset(); - this.RaisedAfterLoadEvent = false; + this.RaisedLoadedEvent = false; } #if !SMAPI_3_0_STRICT diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj index 9b00e777..36fa7e0b 100644 --- a/src/SMAPI/StardewModdingAPI.csproj +++ b/src/SMAPI/StardewModdingAPI.csproj @@ -150,6 +150,7 @@ + -- cgit From 6ad52d607c49b16c6933060375086830edd9a1f9 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 24 Dec 2018 17:28:58 -0500 Subject: add Specialised.LoadStageChanged event --- docs/release-notes.md | 2 +- src/SMAPI/Enums/LoadStage.cs | 36 +++++++ src/SMAPI/Events/ISpecialisedEvents.cs | 4 +- src/SMAPI/Events/LoadStageChangedEventArgs.cs | 31 ++++++ src/SMAPI/Events/SavePreloadedEventArgs.cs | 7 -- src/SMAPI/Framework/Events/EventManager.cs | 6 +- src/SMAPI/Framework/Events/ModSpecialisedEvents.cs | 8 +- src/SMAPI/Framework/SCore.cs | 13 +-- src/SMAPI/Framework/SGame.cs | 89 +++++++++++------ src/SMAPI/Patches/LoadForNewGamePatch.cs | 109 +++++++++++++++++++++ src/SMAPI/StardewModdingAPI.csproj | 4 +- 11 files changed, 255 insertions(+), 54 deletions(-) create mode 100644 src/SMAPI/Enums/LoadStage.cs create mode 100644 src/SMAPI/Events/LoadStageChangedEventArgs.cs delete mode 100644 src/SMAPI/Events/SavePreloadedEventArgs.cs create mode 100644 src/SMAPI/Patches/LoadForNewGamePatch.cs (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 3daca07f..15747488 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -5,7 +5,7 @@ * Tweaked installer to reduce antivirus false positives. * For modders: - * Added `Specialised.SavePreloaded` event, which is raised immediately after a save is loaded but before the in-game world is fully initialised. + * Added `Specialised.LoadStageChanged` for mods which need to do something at a specific point in the game's save loading process. * You can now use read/write save data as soon as the save is loaded (instead of once the world is initialised). ## 2.9.3 diff --git a/src/SMAPI/Enums/LoadStage.cs b/src/SMAPI/Enums/LoadStage.cs new file mode 100644 index 00000000..6ff7de4f --- /dev/null +++ b/src/SMAPI/Enums/LoadStage.cs @@ -0,0 +1,36 @@ +namespace StardewModdingAPI.Enums +{ + /// A low-level stage in the game's loading process. + public enum LoadStage + { + /// A save is not loaded or loading. + None, + + /// The game is creating a new save slot, and has initialised the basic save info. + CreatedBasicInfo, + + /// The game is creating a new save slot, and has initialised the in-game locations. + CreatedLocations, + + /// The game is creating a new save slot, and has created the physical save files. + CreatedSaveFile, + + /// The game is loading a save slot, and has read the raw save data into . Not applicable when connecting to a multiplayer host. This is equivalent to value 20. + SaveParsed, + + /// The game is loading a save slot, and has applied the basic save info (including player data). Not applicable when connecting to a multiplayer host. Note that some basic info (like daily luck) is not initialised at this point. This is equivalent to value 36. + SaveLoadedBasicInfo, + + /// The game is loading a save slot, and has applied the in-game location data. Not applicable when connecting to a multiplayer host. This is equivalent to value 50. + SaveLoadedLocations, + + /// The final metadata has been loaded from the save file. This happens before the game applies problem fixes, checks for achievements, starts music, etc. Not applicable when connecting to a multiplayer host. + Preloaded, + + /// The save is fully loaded, but the world may not be fully initialised yet. + Loaded, + + /// The save is fully loaded, the world has been initialised, and is now true. + Ready + } +} diff --git a/src/SMAPI/Events/ISpecialisedEvents.cs b/src/SMAPI/Events/ISpecialisedEvents.cs index 2a19113c..ecb109e6 100644 --- a/src/SMAPI/Events/ISpecialisedEvents.cs +++ b/src/SMAPI/Events/ISpecialisedEvents.cs @@ -5,8 +5,8 @@ namespace StardewModdingAPI.Events /// Events serving specialised edge cases that shouldn't be used by most mods. public interface ISpecialisedEvents { - /// Raised immediately after the player loads a save slot, but before the world is fully initialised. The save and game data are available at this point, but some in-game content (like location maps) haven't been initialised yet. - event EventHandler SavePreloaded; + /// Raised when the low-level stage in the game's loading process has changed. This is an advanced event for mods which need to run code at specific points in the loading process. The available stages or when they happen might change without warning in future versions (e.g. due to changes in the game's load process), so mods using this event are more likely to break or have bugs. Most mods should use instead. + event EventHandler LoadStageChanged; /// Raised before the game state is updated (≈60 times per second), regardless of normal SMAPI validation. This event is not thread-safe and may be invoked while game logic is running asynchronously. Changes to game state in this method may crash the game or corrupt an in-progress save. Do not use this event unless you're fully aware of the context in which your code will be run. Mods using this event will trigger a stability warning in the SMAPI console. event EventHandler UnvalidatedUpdateTicking; diff --git a/src/SMAPI/Events/LoadStageChangedEventArgs.cs b/src/SMAPI/Events/LoadStageChangedEventArgs.cs new file mode 100644 index 00000000..e837a5f1 --- /dev/null +++ b/src/SMAPI/Events/LoadStageChangedEventArgs.cs @@ -0,0 +1,31 @@ +using System; +using StardewModdingAPI.Enums; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for an event. + public class LoadStageChangedEventArgs : EventArgs + { + /********* + ** Accessors + *********/ + /// The previous load stage. + public LoadStage OldStage { get; } + + /// The new load stage. + public LoadStage NewStage { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The previous load stage. + /// The new load stage. + public LoadStageChangedEventArgs(LoadStage old, LoadStage current) + { + this.OldStage = old; + this.NewStage = current; + } + } +} diff --git a/src/SMAPI/Events/SavePreloadedEventArgs.cs b/src/SMAPI/Events/SavePreloadedEventArgs.cs deleted file mode 100644 index 03990f5a..00000000 --- a/src/SMAPI/Events/SavePreloadedEventArgs.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System; - -namespace StardewModdingAPI.Events -{ - /// Event arguments for an event. - public class SavePreloadedEventArgs : EventArgs { } -} diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs index bd862046..b7f00f52 100644 --- a/src/SMAPI/Framework/Events/EventManager.cs +++ b/src/SMAPI/Framework/Events/EventManager.cs @@ -151,8 +151,8 @@ namespace StardewModdingAPI.Framework.Events /**** ** Specialised ****/ - /// Raised immediately after the player loads a save slot, but before the world is fully initialised. - public readonly ManagedEvent SavePreloaded; + /// Raised when the low-level stage in the game's loading process has changed. See notes on . + public readonly ManagedEvent LoadStageChanged; /// Raised before the game performs its overall update tick (≈60 times per second). See notes on . public readonly ManagedEvent UnvalidatedUpdateTicking; @@ -411,7 +411,7 @@ namespace StardewModdingAPI.Framework.Events this.ObjectListChanged = ManageEventOf(nameof(IModEvents.World), nameof(IWorldEvents.ObjectListChanged)); this.TerrainFeatureListChanged = ManageEventOf(nameof(IModEvents.World), nameof(IWorldEvents.TerrainFeatureListChanged)); - this.SavePreloaded = ManageEventOf(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.SavePreloaded)); + this.LoadStageChanged = ManageEventOf(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.LoadStageChanged)); this.UnvalidatedUpdateTicking = ManageEventOf(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.UnvalidatedUpdateTicking)); this.UnvalidatedUpdateTicked = ManageEventOf(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.UnvalidatedUpdateTicked)); diff --git a/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs b/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs index 83e349cf..7c3e9dee 100644 --- a/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs +++ b/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs @@ -9,11 +9,11 @@ namespace StardewModdingAPI.Framework.Events /********* ** Accessors *********/ - /// Raised immediately after the player loads a save slot, but before the world is fully initialised. The save and game data are available at this point, but some in-game content (like location maps) haven't been initialised yet. - public event EventHandler SavePreloaded + /// Raised when the low-level stage in the game's loading process has changed. This is an advanced event for mods which need to run code at specific points in the loading process. The available stages or when they happen might change without warning in future versions (e.g. due to changes in the game's load process), so mods using this event are more likely to break or have bugs. Most mods should use instead. + public event EventHandler LoadStageChanged { - add => this.EventManager.SavePreloaded.Add(value); - remove => this.EventManager.SavePreloaded.Remove(value); + add => this.EventManager.LoadStageChanged.Add(value); + remove => this.EventManager.LoadStageChanged.Remove(value); } /// Raised before the game state is updated (≈60 times per second), regardless of normal SMAPI validation. This event is not thread-safe and may be invoked while game logic is running asynchronously. Changes to game state in this method may crash the game or corrupt an in-progress save. Do not use this event unless you're fully aware of the context in which your code will be run. Mods using this event will trigger a stability warning in the SMAPI console. diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 679838ba..00801b72 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -181,12 +181,6 @@ namespace StardewModdingAPI.Framework return; } #endif - - // apply game patches - new GamePatcher(this.Monitor).Apply( - new DialogueErrorPatch(this.MonitorForGame, this.Reflection), - new ObjectErrorPatch() - ); } /// Launch SMAPI. @@ -237,6 +231,13 @@ namespace StardewModdingAPI.Framework this.GameInstance = new SGame(this.Monitor, this.MonitorForGame, this.Reflection, this.EventManager, this.Toolkit.JsonHelper, this.ModRegistry, SCore.DeprecationManager, this.OnLocaleChanged, this.InitialiseAfterGameStart, this.Dispose); StardewValley.Program.gamePtr = this.GameInstance; + // apply game patches + new GamePatcher(this.Monitor).Apply( + new DialogueErrorPatch(this.MonitorForGame, this.Reflection), + new ObjectErrorPatch(), + new LoadForNewGamePatch(this.Reflection, this.GameInstance.OnLoadStageChanged) + ); + // add exit handler new Thread(() => { diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index befd9cef..cb62de2a 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -69,11 +69,8 @@ namespace StardewModdingAPI.Framework /// Skipping a few frames ensures the game finishes initialising the world before mods try to change it. private readonly Countdown AfterLoadTimer = new Countdown(5); - /// Whether was raised for this session. - private bool RaisedPreloadedEvent; - - /// Whether the after-load events were raised for this session. - private bool RaisedLoadedEvent; + /// The current stage in the game's loading process. + private LoadStage LoadStage = LoadStage.None; /// Whether the game is saving and SMAPI has already raised . private bool IsBetweenSaveEvents; @@ -216,16 +213,33 @@ namespace StardewModdingAPI.Framework this.Events.ModMessageReceived.RaiseForMods(new ModMessageReceivedEventArgs(message), mod => mod != null && modIDs.Contains(mod.Manifest.UniqueID)); } - /// A callback raised when the player quits a save and returns to the title screen. - private void OnReturnedToTitle() + /// A callback invoked when the game's low-level load stage changes. + /// The new load stage. + internal void OnLoadStageChanged(LoadStage newStage) { - this.Monitor.Log("Context: returned to title", LogLevel.Trace); - this.RaisedPreloadedEvent = false; - this.Multiplayer.CleanupOnMultiplayerExit(); - this.Events.ReturnedToTitle.RaiseEmpty(); + // nothing to do + if (newStage == this.LoadStage) + return; + + // update data + LoadStage oldStage = this.LoadStage; + this.LoadStage = newStage; + if (newStage == LoadStage.None) + { + this.Monitor.Log("Context: returned to title", LogLevel.Trace); + this.Multiplayer.CleanupOnMultiplayerExit(); + } + this.Monitor.VerboseLog($"Context: load stage changed to {newStage}"); + + // raise events + this.Events.LoadStageChanged.Raise(new LoadStageChangedEventArgs(oldStage, newStage)); + if (newStage == LoadStage.None) + { + this.Events.ReturnedToTitle.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_AfterReturnToTitle.Raise(); + this.Events.Legacy_AfterReturnToTitle.Raise(); #endif + } } /// Constructor a content manager to read XNB files. @@ -284,7 +298,29 @@ namespace StardewModdingAPI.Framework { this.Monitor.Log("Game loader synchronising...", LogLevel.Trace); while (Game1.currentLoader?.MoveNext() == true) - ; + { + // raise load stage changed + switch (Game1.currentLoader.Current) + { + case 20: + this.OnLoadStageChanged(LoadStage.SaveParsed); + break; + + case 36: + this.OnLoadStageChanged(LoadStage.SaveLoadedBasicInfo); + break; + + case 50: + this.OnLoadStageChanged(LoadStage.SaveLoadedLocations); + break; + + default: + if (Game1.gameMode == Game1.playingGameMode) + this.OnLoadStageChanged(LoadStage.Preloaded); + break; + } + } + Game1.currentLoader = null; this.Monitor.Log("Game loader done.", LogLevel.Trace); } @@ -411,6 +447,7 @@ namespace StardewModdingAPI.Framework // 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); this.Events.SaveCreated.RaiseEmpty(); #if !SMAPI_3_0_STRICT this.Events.Legacy_AfterCreateSave.Raise(); @@ -434,7 +471,10 @@ namespace StardewModdingAPI.Framework *********/ bool wasWorldReady = Context.IsWorldReady; if ((Context.IsWorldReady && !Context.IsSaveLoaded) || Game1.exitToTitle) - this.MarkWorldNotReady(); + { + Context.IsWorldReady = false; + this.AfterLoadTimer.Reset(); + } else if (Context.IsSaveLoaded && this.AfterLoadTimer.Current > 0 && Game1.currentLocation != null) { if (Game1.dayOfMonth != 0) // wait until new-game intro finishes (world not fully initialised yet) @@ -469,8 +509,8 @@ namespace StardewModdingAPI.Framework ** Load / return-to-title events *********/ if (wasWorldReady && !Context.IsWorldReady) - this.OnReturnedToTitle(); - else if (!this.RaisedLoadedEvent && Context.IsWorldReady) + this.OnLoadStageChanged(LoadStage.None); + else if (Context.IsWorldReady && this.LoadStage != LoadStage.Ready) { // print context string context = $"Context: loaded saved game '{Constants.SaveFolderName}', starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}."; @@ -484,7 +524,7 @@ namespace StardewModdingAPI.Framework this.Monitor.Log(context, LogLevel.Trace); // raise events - this.RaisedLoadedEvent = true; + this.OnLoadStageChanged(LoadStage.Ready); this.Events.SaveLoaded.RaiseEmpty(); this.Events.DayStarted.RaiseEmpty(); #if !SMAPI_3_0_STRICT @@ -834,11 +874,8 @@ namespace StardewModdingAPI.Framework this.Events.GameLaunched.Raise(new GameLaunchedEventArgs()); // preloaded - if (Context.IsSaveLoaded && !this.RaisedPreloadedEvent) - { - this.RaisedPreloadedEvent = true; - this.Events.SavePreloaded.RaiseEmpty(); - } + if (Context.IsSaveLoaded && this.LoadStage != LoadStage.Loaded && this.LoadStage != LoadStage.Ready) + this.OnLoadStageChanged(LoadStage.Loaded); // update tick this.Events.UnvalidatedUpdateTicking.Raise(new UnvalidatedUpdateTickingEventArgs(this.TicksElapsed)); @@ -1649,14 +1686,6 @@ namespace StardewModdingAPI.Framework /**** ** Methods ****/ - /// Perform any cleanup needed when a save is unloaded. - private void MarkWorldNotReady() - { - Context.IsWorldReady = false; - this.AfterLoadTimer.Reset(); - this.RaisedLoadedEvent = false; - } - #if !SMAPI_3_0_STRICT /// Raise the if there are any listeners. /// Whether to create a new sprite batch. diff --git a/src/SMAPI/Patches/LoadForNewGamePatch.cs b/src/SMAPI/Patches/LoadForNewGamePatch.cs new file mode 100644 index 00000000..9e788e84 --- /dev/null +++ b/src/SMAPI/Patches/LoadForNewGamePatch.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Reflection; +using Harmony; +using StardewModdingAPI.Enums; +using StardewModdingAPI.Framework.Patching; +using StardewModdingAPI.Framework.Reflection; +using StardewValley; +using StardewValley.Menus; + +namespace StardewModdingAPI.Patches +{ + /// A Harmony patch for which notifies SMAPI for save creation load stages. + /// This patch hooks into , checks if TitleMenu.transitioningCharacterCreationMenu is true (which means the player is creating a new save file), then raises after the location list is cleared twice (the second clear happens right before locations are created), and when the method ends. + internal class LoadForNewGamePatch : IHarmonyPatch + { + /********* + ** Accessors + *********/ + /// Simplifies access to private code. + private static Reflector Reflection; + + /// A callback to invoke when the load stage changes. + private static Action OnStageChanged; + + /// Whether was called as part of save creation. + private static bool IsCreating; + + /// The number of times that has been cleared since started. + private static int TimesLocationsCleared = 0; + + + /********* + ** Accessors + *********/ + /// A unique name for this patch. + public string Name => $"{nameof(LoadForNewGamePatch)}"; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// Simplifies access to private code. + /// A callback to invoke when the load stage changes. + public LoadForNewGamePatch(Reflector reflection, Action onStageChanged) + { + LoadForNewGamePatch.Reflection = reflection; + LoadForNewGamePatch.OnStageChanged = onStageChanged; + } + + /// Apply the Harmony patch. + /// The Harmony instance. + public void Apply(HarmonyInstance harmony) + { + MethodInfo method = AccessTools.Method(typeof(Game1), nameof(Game1.loadForNewGame)); + MethodInfo prefix = AccessTools.Method(this.GetType(), nameof(LoadForNewGamePatch.Prefix)); + MethodInfo postfix = AccessTools.Method(this.GetType(), nameof(LoadForNewGamePatch.Postfix)); + + harmony.Patch(method, new HarmonyMethod(prefix), new HarmonyMethod(postfix)); + } + + + /********* + ** Private methods + *********/ + /// The method to call instead of . + /// 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 Prefix() + { + LoadForNewGamePatch.IsCreating = Game1.activeClickableMenu is TitleMenu menu && LoadForNewGamePatch.Reflection.GetField(menu, "transitioningCharacterCreationMenu").GetValue(); + LoadForNewGamePatch.TimesLocationsCleared = 0; + if (LoadForNewGamePatch.IsCreating) + { + // raise CreatedBasicInfo after locations are cleared twice + ObservableCollection locations = (ObservableCollection)Game1.locations; + locations.CollectionChanged += LoadForNewGamePatch.OnLocationListChanged; + } + + return true; + } + + /// The method to call instead after . + /// This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments. + private static void Postfix() + { + if (LoadForNewGamePatch.IsCreating) + { + // clean up + ObservableCollection locations = (ObservableCollection) Game1.locations; + locations.CollectionChanged -= LoadForNewGamePatch.OnLocationListChanged; + + // raise stage changed + LoadForNewGamePatch.OnStageChanged(LoadStage.CreatedLocations); + } + } + + /// Raised when changes. + /// The event sender. + /// The event arguments. + private static void OnLocationListChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (++LoadForNewGamePatch.TimesLocationsCleared == 2) + LoadForNewGamePatch.OnStageChanged(LoadStage.CreatedBasicInfo); + } + } +} diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj index 36fa7e0b..fdb0c6c7 100644 --- a/src/SMAPI/StardewModdingAPI.csproj +++ b/src/SMAPI/StardewModdingAPI.csproj @@ -76,6 +76,7 @@ Properties\GlobalAssemblyInfo.cs + @@ -123,6 +124,7 @@ + @@ -150,7 +152,6 @@ - @@ -327,6 +328,7 @@ + -- cgit From 8e0573d7d9f18792a19e741660b6a090cca1fb38 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 25 Dec 2018 15:10:22 -0500 Subject: add GameLoop.OneSecondUpdateTicking/Ticked --- docs/release-notes.md | 1 + src/SMAPI/Events/IGameLoopEvents.cs | 6 ++++ src/SMAPI/Events/OneSecondUpdateTickedEventArgs.cs | 32 ++++++++++++++++++++++ .../Events/OneSecondUpdateTickingEventArgs.cs | 32 ++++++++++++++++++++++ src/SMAPI/Framework/Events/EventManager.cs | 8 ++++++ src/SMAPI/Framework/Events/ModGameLoopEvents.cs | 14 ++++++++++ src/SMAPI/Framework/SGame.cs | 5 ++++ src/SMAPI/StardewModdingAPI.csproj | 2 ++ 8 files changed, 100 insertions(+) create mode 100644 src/SMAPI/Events/OneSecondUpdateTickedEventArgs.cs create mode 100644 src/SMAPI/Events/OneSecondUpdateTickingEventArgs.cs (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 15747488..5293f1c1 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -5,6 +5,7 @@ * Tweaked installer to reduce antivirus false positives. * For modders: + * Added `GameLoop.OneSecondUpdateTicking/Ticked` events. * Added `Specialised.LoadStageChanged` for mods which need to do something at a specific point in the game's save loading process. * You can now use read/write save data as soon as the save is loaded (instead of once the world is initialised). diff --git a/src/SMAPI/Events/IGameLoopEvents.cs b/src/SMAPI/Events/IGameLoopEvents.cs index ea79aa74..6fb56c8b 100644 --- a/src/SMAPI/Events/IGameLoopEvents.cs +++ b/src/SMAPI/Events/IGameLoopEvents.cs @@ -14,6 +14,12 @@ namespace StardewModdingAPI.Events /// Raised after the game state is updated (≈60 times per second). event EventHandler UpdateTicked; + /// Raised once per second before the game state is updated. + event EventHandler OneSecondUpdateTicking; + + /// Raised once per second after the game state is updated. + event EventHandler OneSecondUpdateTicked; + /// Raised before the game creates a new save file. event EventHandler SaveCreating; diff --git a/src/SMAPI/Events/OneSecondUpdateTickedEventArgs.cs b/src/SMAPI/Events/OneSecondUpdateTickedEventArgs.cs new file mode 100644 index 00000000..d330502a --- /dev/null +++ b/src/SMAPI/Events/OneSecondUpdateTickedEventArgs.cs @@ -0,0 +1,32 @@ +using System; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for an event. + public class OneSecondUpdateTickedEventArgs : EventArgs + { + /********* + ** Accessors + *********/ + /// The number of ticks elapsed since the game started, including the current tick. + public uint Ticks { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The number of ticks elapsed since the game started, including the current tick. + internal OneSecondUpdateTickedEventArgs(uint ticks) + { + this.Ticks = ticks; + } + + /// Get whether is a multiple of the given . This is mainly useful if you want to run logic intermittently (e.g. e.IsMultipleOf(30) for every half-second). + /// The factor to check. + public bool IsMultipleOf(uint number) + { + return this.Ticks % number == 0; + } + } +} diff --git a/src/SMAPI/Events/OneSecondUpdateTickingEventArgs.cs b/src/SMAPI/Events/OneSecondUpdateTickingEventArgs.cs new file mode 100644 index 00000000..cdd9f4cc --- /dev/null +++ b/src/SMAPI/Events/OneSecondUpdateTickingEventArgs.cs @@ -0,0 +1,32 @@ +using System; + +namespace StardewModdingAPI.Events +{ + /// Event arguments for an event. + public class OneSecondUpdateTickingEventArgs : EventArgs + { + /********* + ** Accessors + *********/ + /// The number of ticks elapsed since the game started, including the current tick. + public uint Ticks { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The number of ticks elapsed since the game started, including the current tick. + internal OneSecondUpdateTickingEventArgs(uint ticks) + { + this.Ticks = ticks; + } + + /// Get whether is a multiple of the given . This is mainly useful if you want to run logic intermittently (e.g. e.IsMultipleOf(30) for every half-second). + /// The factor to check. + public bool IsMultipleOf(uint number) + { + return this.Ticks % number == 0; + } + } +} diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs index b7f00f52..13244601 100644 --- a/src/SMAPI/Framework/Events/EventManager.cs +++ b/src/SMAPI/Framework/Events/EventManager.cs @@ -58,6 +58,12 @@ namespace StardewModdingAPI.Framework.Events /// Raised after the game performs its overall update tick (≈60 times per second). public readonly ManagedEvent UpdateTicked; + /// Raised once per second before the game performs its overall update tick. + public readonly ManagedEvent OneSecondUpdateTicking; + + /// Raised once per second after the game performs its overall update tick. + public readonly ManagedEvent OneSecondUpdateTicked; + /// Raised before the game creates the save file. public readonly ManagedEvent SaveCreating; @@ -380,6 +386,8 @@ namespace StardewModdingAPI.Framework.Events this.GameLaunched = ManageEventOf(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.GameLaunched)); this.UpdateTicking = ManageEventOf(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.UpdateTicking)); this.UpdateTicked = ManageEventOf(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.UpdateTicked)); + this.OneSecondUpdateTicking = ManageEventOf(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.OneSecondUpdateTicking)); + this.OneSecondUpdateTicked = ManageEventOf(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.OneSecondUpdateTicked)); this.SaveCreating = ManageEventOf(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.SaveCreating)); this.SaveCreated = ManageEventOf(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.SaveCreated)); this.Saving = ManageEventOf(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.Saving)); diff --git a/src/SMAPI/Framework/Events/ModGameLoopEvents.cs b/src/SMAPI/Framework/Events/ModGameLoopEvents.cs index 3a764ab0..0177c22e 100644 --- a/src/SMAPI/Framework/Events/ModGameLoopEvents.cs +++ b/src/SMAPI/Framework/Events/ModGameLoopEvents.cs @@ -30,6 +30,20 @@ namespace StardewModdingAPI.Framework.Events remove => this.EventManager.UpdateTicked.Remove(value); } + /// Raised once per second before the game state is updated. + public event EventHandler OneSecondUpdateTicking + { + add => this.EventManager.OneSecondUpdateTicking.Add(value); + remove => this.EventManager.OneSecondUpdateTicking.Remove(value); + } + + /// Raised once per second after the game state is updated. + public event EventHandler OneSecondUpdateTicked + { + add => this.EventManager.OneSecondUpdateTicked.Add(value); + remove => this.EventManager.OneSecondUpdateTicked.Remove(value); + } + /// Raised before the game creates a new save file. public event EventHandler SaveCreating { diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index cb62de2a..8abe4d16 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -878,8 +878,11 @@ namespace StardewModdingAPI.Framework this.OnLoadStageChanged(LoadStage.Loaded); // update tick + bool isOneSecond = this.TicksElapsed % 60 == 0; this.Events.UnvalidatedUpdateTicking.Raise(new UnvalidatedUpdateTickingEventArgs(this.TicksElapsed)); this.Events.UpdateTicking.Raise(new UpdateTickingEventArgs(this.TicksElapsed)); + if (isOneSecond) + this.Events.OneSecondUpdateTicking.Raise(new OneSecondUpdateTickingEventArgs(this.TicksElapsed)); try { this.Input.UpdateSuppression(); @@ -891,6 +894,8 @@ namespace StardewModdingAPI.Framework } this.Events.UnvalidatedUpdateTicked.Raise(new UnvalidatedUpdateTickedEventArgs(this.TicksElapsed)); this.Events.UpdateTicked.Raise(new UpdateTickedEventArgs(this.TicksElapsed)); + if (isOneSecond) + this.Events.OneSecondUpdateTicked.Raise(new OneSecondUpdateTickedEventArgs(this.TicksElapsed)); /********* ** Update events diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj index fdb0c6c7..5540f277 100644 --- a/src/SMAPI/StardewModdingAPI.csproj +++ b/src/SMAPI/StardewModdingAPI.csproj @@ -135,6 +135,8 @@ + + -- cgit From 382b5fe914894b87e44462060ca7ae8415c9533e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 25 Dec 2018 15:12:58 -0500 Subject: minor performance optimisation --- docs/release-notes.md | 1 + src/SMAPI/Events/OneSecondUpdateTickedEventArgs.cs | 10 +- .../Events/OneSecondUpdateTickingEventArgs.cs | 10 +- .../Events/UnvalidatedUpdateTickedEventArgs.cs | 13 +- .../Events/UnvalidatedUpdateTickingEventArgs.cs | 13 +- src/SMAPI/Events/UpdateTickedEventArgs.cs | 13 +- src/SMAPI/Events/UpdateTickingEventArgs.cs | 13 +- src/SMAPI/Framework/Events/ManagedEvent.cs | 2 + src/SMAPI/Framework/SGame.cs | 294 +++++++++++---------- 9 files changed, 167 insertions(+), 202 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 5293f1c1..3913862b 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,7 @@ ## Upcoming release * For players: * Added `world_clear` console command to remove spawned or placed entities. + * Minor performance improvement. * Tweaked installer to reduce antivirus false positives. * For modders: diff --git a/src/SMAPI/Events/OneSecondUpdateTickedEventArgs.cs b/src/SMAPI/Events/OneSecondUpdateTickedEventArgs.cs index d330502a..dadbb71a 100644 --- a/src/SMAPI/Events/OneSecondUpdateTickedEventArgs.cs +++ b/src/SMAPI/Events/OneSecondUpdateTickedEventArgs.cs @@ -1,4 +1,5 @@ using System; +using StardewValley; namespace StardewModdingAPI.Events { @@ -9,19 +10,12 @@ namespace StardewModdingAPI.Events ** Accessors *********/ /// The number of ticks elapsed since the game started, including the current tick. - public uint Ticks { get; } + public uint Ticks => (uint)Game1.ticks; /********* ** Public methods *********/ - /// Construct an instance. - /// The number of ticks elapsed since the game started, including the current tick. - internal OneSecondUpdateTickedEventArgs(uint ticks) - { - this.Ticks = ticks; - } - /// Get whether is a multiple of the given . This is mainly useful if you want to run logic intermittently (e.g. e.IsMultipleOf(30) for every half-second). /// The factor to check. public bool IsMultipleOf(uint number) diff --git a/src/SMAPI/Events/OneSecondUpdateTickingEventArgs.cs b/src/SMAPI/Events/OneSecondUpdateTickingEventArgs.cs index cdd9f4cc..e9bb46c6 100644 --- a/src/SMAPI/Events/OneSecondUpdateTickingEventArgs.cs +++ b/src/SMAPI/Events/OneSecondUpdateTickingEventArgs.cs @@ -1,4 +1,5 @@ using System; +using StardewValley; namespace StardewModdingAPI.Events { @@ -9,19 +10,12 @@ namespace StardewModdingAPI.Events ** Accessors *********/ /// The number of ticks elapsed since the game started, including the current tick. - public uint Ticks { get; } + public uint Ticks => (uint)Game1.ticks; /********* ** Public methods *********/ - /// Construct an instance. - /// The number of ticks elapsed since the game started, including the current tick. - internal OneSecondUpdateTickingEventArgs(uint ticks) - { - this.Ticks = ticks; - } - /// Get whether is a multiple of the given . This is mainly useful if you want to run logic intermittently (e.g. e.IsMultipleOf(30) for every half-second). /// The factor to check. public bool IsMultipleOf(uint number) diff --git a/src/SMAPI/Events/UnvalidatedUpdateTickedEventArgs.cs b/src/SMAPI/Events/UnvalidatedUpdateTickedEventArgs.cs index 95ae59d8..d15e9531 100644 --- a/src/SMAPI/Events/UnvalidatedUpdateTickedEventArgs.cs +++ b/src/SMAPI/Events/UnvalidatedUpdateTickedEventArgs.cs @@ -1,4 +1,5 @@ using System; +using StardewValley; namespace StardewModdingAPI.Events { @@ -9,23 +10,15 @@ namespace StardewModdingAPI.Events ** Accessors *********/ /// The number of ticks elapsed since the game started, including the current tick. - public uint Ticks { get; } + public uint Ticks => (uint)Game1.ticks; /// Whether is a multiple of 60, which happens approximately once per second. - public bool IsOneSecond { get; } + public bool IsOneSecond => Game1.ticks % 60 == 0; /********* ** Public methods *********/ - /// Construct an instance. - /// The number of ticks elapsed since the game started, including the current tick. - internal UnvalidatedUpdateTickedEventArgs(uint ticks) - { - this.Ticks = ticks; - this.IsOneSecond = this.IsMultipleOf(60); - } - /// Get whether is a multiple of the given . This is mainly useful if you want to run logic intermittently (e.g. e.IsMultipleOf(30) for every half-second). /// The factor to check. public bool IsMultipleOf(uint number) diff --git a/src/SMAPI/Events/UnvalidatedUpdateTickingEventArgs.cs b/src/SMAPI/Events/UnvalidatedUpdateTickingEventArgs.cs index 4ed781e0..577f0776 100644 --- a/src/SMAPI/Events/UnvalidatedUpdateTickingEventArgs.cs +++ b/src/SMAPI/Events/UnvalidatedUpdateTickingEventArgs.cs @@ -1,4 +1,5 @@ using System; +using StardewValley; namespace StardewModdingAPI.Events { @@ -9,23 +10,15 @@ namespace StardewModdingAPI.Events ** Accessors *********/ /// The number of ticks elapsed since the game started, including the current tick. - public uint Ticks { get; } + public uint Ticks => (uint)Game1.ticks; /// Whether is a multiple of 60, which happens approximately once per second. - public bool IsOneSecond { get; } + public bool IsOneSecond => Game1.ticks % 60 == 0; /********* ** Public methods *********/ - /// Construct an instance. - /// The number of ticks elapsed since the game started, including the current tick. - internal UnvalidatedUpdateTickingEventArgs(uint ticks) - { - this.Ticks = ticks; - this.IsOneSecond = this.IsMultipleOf(60); - } - /// Get whether is a multiple of the given . This is mainly useful if you want to run logic intermittently (e.g. e.IsMultipleOf(30) for every half-second). /// The factor to check. public bool IsMultipleOf(uint number) diff --git a/src/SMAPI/Events/UpdateTickedEventArgs.cs b/src/SMAPI/Events/UpdateTickedEventArgs.cs index 3466b731..aa710b44 100644 --- a/src/SMAPI/Events/UpdateTickedEventArgs.cs +++ b/src/SMAPI/Events/UpdateTickedEventArgs.cs @@ -1,4 +1,5 @@ using System; +using StardewValley; namespace StardewModdingAPI.Events { @@ -9,23 +10,15 @@ namespace StardewModdingAPI.Events ** Accessors *********/ /// The number of ticks elapsed since the game started, including the current tick. - public uint Ticks { get; } + public uint Ticks => (uint)Game1.ticks; /// Whether is a multiple of 60, which happens approximately once per second. - public bool IsOneSecond { get; } + public bool IsOneSecond => Game1.ticks % 60 == 0; /********* ** Public methods *********/ - /// Construct an instance. - /// The number of ticks elapsed since the game started, including the current tick. - internal UpdateTickedEventArgs(uint ticks) - { - this.Ticks = ticks; - this.IsOneSecond = this.IsMultipleOf(60); - } - /// Get whether is a multiple of the given . This is mainly useful if you want to run logic intermittently (e.g. e.IsMultipleOf(30) for every half-second). /// The factor to check. public bool IsMultipleOf(uint number) diff --git a/src/SMAPI/Events/UpdateTickingEventArgs.cs b/src/SMAPI/Events/UpdateTickingEventArgs.cs index d4913268..cacf5a54 100644 --- a/src/SMAPI/Events/UpdateTickingEventArgs.cs +++ b/src/SMAPI/Events/UpdateTickingEventArgs.cs @@ -1,4 +1,5 @@ using System; +using StardewValley; namespace StardewModdingAPI.Events { @@ -9,23 +10,15 @@ namespace StardewModdingAPI.Events ** Accessors *********/ /// The number of ticks elapsed since the game started, including the current tick. - public uint Ticks { get; } + public uint Ticks => (uint)Game1.ticks; /// Whether is a multiple of 60, which happens approximately once per second. - public bool IsOneSecond { get; } + public bool IsOneSecond => Game1.ticks % 60 == 0; /********* ** Public methods *********/ - /// Construct an instance. - /// The number of ticks elapsed since the game started, including the current tick. - internal UpdateTickingEventArgs(uint ticks) - { - this.Ticks = ticks; - this.IsOneSecond = this.IsMultipleOf(60); - } - /// Get whether is a multiple of the given . This is mainly useful if you want to run logic intermittently (e.g. e.IsMultipleOf(30) for every half-second). /// The factor to check. public bool IsMultipleOf(uint number) diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs index 65f6e38e..ebd79e6c 100644 --- a/src/SMAPI/Framework/Events/ManagedEvent.cs +++ b/src/SMAPI/Framework/Events/ManagedEvent.cs @@ -93,6 +93,7 @@ namespace StardewModdingAPI.Framework.Events } } +#if !SMAPI_3_0_STRICT /// An event wrapper which intercepts and logs errors in handler code. internal class ManagedEvent : ManagedEventBase { @@ -156,4 +157,5 @@ namespace StardewModdingAPI.Framework.Events } } } +#endif } diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 8abe4d16..957fd1d6 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -96,15 +96,9 @@ namespace StardewModdingAPI.Framework /// Monitors the entire game state for changes. private WatcherCore Watchers; - /// An index incremented on every tick and reset every 60th tick (0–59). - private int CurrentUpdateTick; - /// Whether post-game-startup initialisation has been performed. private bool IsInitialised; - /// The number of update ticks which have already executed. - private uint TicksElapsed; - /// Whether the next content manager requested by the game will be for . private bool NextContentManagerIsMain; @@ -271,6 +265,8 @@ namespace StardewModdingAPI.Framework /// A snapshot of the game timing state. protected override void Update(GameTime gameTime) { + var events = this.Events; + try { this.DeprecationManager.PrintQueued(); @@ -342,11 +338,11 @@ namespace StardewModdingAPI.Framework // update tick are neglible and not worth the complications of bypassing Game1.Update. if (Game1._newDayTask != null || Game1.gameMode == Game1.loadingMode) { - this.Events.UnvalidatedUpdateTicking.Raise(new UnvalidatedUpdateTickingEventArgs(this.TicksElapsed)); + events.UnvalidatedUpdateTicking.RaiseEmpty(); base.Update(gameTime); - this.Events.UnvalidatedUpdateTicked.Raise(new UnvalidatedUpdateTickedEventArgs(this.TicksElapsed)); + events.UnvalidatedUpdateTicked.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_UnvalidatedUpdateTick.Raise(); + events.Legacy_UnvalidatedUpdateTick.Raise(); #endif return; } @@ -416,9 +412,9 @@ namespace StardewModdingAPI.Framework { this.IsBetweenCreateEvents = true; this.Monitor.Log("Context: before save creation.", LogLevel.Trace); - this.Events.SaveCreating.RaiseEmpty(); + events.SaveCreating.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_BeforeCreateSave.Raise(); + events.Legacy_BeforeCreateSave.Raise(); #endif } @@ -427,18 +423,18 @@ namespace StardewModdingAPI.Framework { this.IsBetweenSaveEvents = true; this.Monitor.Log("Context: before save.", LogLevel.Trace); - this.Events.Saving.RaiseEmpty(); + events.Saving.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_BeforeSave.Raise(); + events.Legacy_BeforeSave.Raise(); #endif } // suppress non-save events - this.Events.UnvalidatedUpdateTicking.Raise(new UnvalidatedUpdateTickingEventArgs(this.TicksElapsed)); + events.UnvalidatedUpdateTicking.RaiseEmpty(); base.Update(gameTime); - this.Events.UnvalidatedUpdateTicked.Raise(new UnvalidatedUpdateTickedEventArgs(this.TicksElapsed)); + events.UnvalidatedUpdateTicked.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_UnvalidatedUpdateTick.Raise(); + events.Legacy_UnvalidatedUpdateTick.Raise(); #endif return; } @@ -448,9 +444,9 @@ namespace StardewModdingAPI.Framework this.IsBetweenCreateEvents = false; this.Monitor.Log($"Context: after save creation, starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}.", LogLevel.Trace); this.OnLoadStageChanged(LoadStage.CreatedSaveFile); - this.Events.SaveCreated.RaiseEmpty(); + events.SaveCreated.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_AfterCreateSave.Raise(); + events.Legacy_AfterCreateSave.Raise(); #endif } if (this.IsBetweenSaveEvents) @@ -458,11 +454,11 @@ namespace StardewModdingAPI.Framework // 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(); + events.Saved.RaiseEmpty(); + events.DayStarted.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_AfterSave.Raise(); - this.Events.Legacy_AfterDayStarted.Raise(); + events.Legacy_AfterSave.Raise(); + events.Legacy_AfterDayStarted.Raise(); #endif } @@ -499,7 +495,7 @@ namespace StardewModdingAPI.Framework this.OnLocaleChanged(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_LocaleChanged.Raise(new EventArgsValueChanged(was.ToString(), now.ToString())); + events.Legacy_LocaleChanged.Raise(new EventArgsValueChanged(was.ToString(), now.ToString())); #endif this.Watchers.LocaleWatcher.Reset(); @@ -525,11 +521,11 @@ namespace StardewModdingAPI.Framework // raise events this.OnLoadStageChanged(LoadStage.Ready); - this.Events.SaveLoaded.RaiseEmpty(); - this.Events.DayStarted.RaiseEmpty(); + events.SaveLoaded.RaiseEmpty(); + events.DayStarted.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_AfterLoad.Raise(); - this.Events.Legacy_AfterDayStarted.Raise(); + events.Legacy_AfterLoad.Raise(); + events.Legacy_AfterDayStarted.Raise(); #endif } @@ -548,9 +544,9 @@ namespace StardewModdingAPI.Framework Point oldSize = this.Watchers.WindowSizeWatcher.PreviousValue; Point newSize = this.Watchers.WindowSizeWatcher.CurrentValue; - this.Events.WindowResized.Raise(new WindowResizedEventArgs(oldSize, newSize)); + events.WindowResized.Raise(new WindowResizedEventArgs(oldSize, newSize)); #if !SMAPI_3_0_STRICT - this.Events.Legacy_Resize.Raise(); + events.Legacy_Resize.Raise(); #endif this.Watchers.WindowSizeWatcher.Reset(); } @@ -569,23 +565,33 @@ namespace StardewModdingAPI.Framework // raise cursor moved event if (this.Watchers.CursorWatcher.IsChanged) { - ICursorPosition was = this.Watchers.CursorWatcher.PreviousValue; - ICursorPosition now = this.Watchers.CursorWatcher.CurrentValue; - this.Watchers.CursorWatcher.Reset(); + if (events.CursorMoved.HasListeners()) + { + ICursorPosition was = this.Watchers.CursorWatcher.PreviousValue; + ICursorPosition now = this.Watchers.CursorWatcher.CurrentValue; + this.Watchers.CursorWatcher.Reset(); - this.Events.CursorMoved.Raise(new CursorMovedEventArgs(was, now)); + events.CursorMoved.Raise(new CursorMovedEventArgs(was, now)); + } + else + this.Watchers.CursorWatcher.Reset(); } // raise mouse wheel scrolled if (this.Watchers.MouseWheelScrollWatcher.IsChanged) { - int was = this.Watchers.MouseWheelScrollWatcher.PreviousValue; - int now = this.Watchers.MouseWheelScrollWatcher.CurrentValue; - this.Watchers.MouseWheelScrollWatcher.Reset(); + if (events.MouseWheelScrolled.HasListeners() || this.Monitor.IsVerbose) + { + int was = this.Watchers.MouseWheelScrollWatcher.PreviousValue; + int now = this.Watchers.MouseWheelScrollWatcher.CurrentValue; + this.Watchers.MouseWheelScrollWatcher.Reset(); - if (this.Monitor.IsVerbose) - this.Monitor.Log($"Events: mouse wheel scrolled to {now}.", LogLevel.Trace); - this.Events.MouseWheelScrolled.Raise(new MouseWheelScrolledEventArgs(cursor, was, now)); + 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(); } // raise input button events @@ -599,22 +605,22 @@ namespace StardewModdingAPI.Framework if (this.Monitor.IsVerbose) this.Monitor.Log($"Events: button {button} pressed.", LogLevel.Trace); - this.Events.ButtonPressed.Raise(new ButtonPressedEventArgs(button, cursor, inputState)); + events.ButtonPressed.Raise(new ButtonPressedEventArgs(button, cursor, inputState)); #if !SMAPI_3_0_STRICT // legacy events - this.Events.Legacy_ButtonPressed.Raise(new EventArgsInput(button, cursor, inputState.SuppressButtons)); + events.Legacy_ButtonPressed.Raise(new EventArgsInput(button, cursor, inputState.SuppressButtons)); if (button.TryGetKeyboard(out Keys key)) { if (key != Keys.None) - this.Events.Legacy_KeyPressed.Raise(new EventArgsKeyPressed(key)); + events.Legacy_KeyPressed.Raise(new EventArgsKeyPressed(key)); } else if (button.TryGetController(out Buttons controllerButton)) { if (controllerButton == Buttons.LeftTrigger || controllerButton == Buttons.RightTrigger) - this.Events.Legacy_ControllerTriggerPressed.Raise(new EventArgsControllerTriggerPressed(PlayerIndex.One, controllerButton, controllerButton == Buttons.LeftTrigger ? inputState.RealController.Triggers.Left : inputState.RealController.Triggers.Right)); + events.Legacy_ControllerTriggerPressed.Raise(new EventArgsControllerTriggerPressed(PlayerIndex.One, controllerButton, controllerButton == Buttons.LeftTrigger ? inputState.RealController.Triggers.Left : inputState.RealController.Triggers.Right)); else - this.Events.Legacy_ControllerButtonPressed.Raise(new EventArgsControllerButtonPressed(PlayerIndex.One, controllerButton)); + events.Legacy_ControllerButtonPressed.Raise(new EventArgsControllerButtonPressed(PlayerIndex.One, controllerButton)); } #endif } @@ -623,22 +629,22 @@ namespace StardewModdingAPI.Framework if (this.Monitor.IsVerbose) this.Monitor.Log($"Events: button {button} released.", LogLevel.Trace); - this.Events.ButtonReleased.Raise(new ButtonReleasedEventArgs(button, cursor, inputState)); + events.ButtonReleased.Raise(new ButtonReleasedEventArgs(button, cursor, inputState)); #if !SMAPI_3_0_STRICT // legacy events - this.Events.Legacy_ButtonReleased.Raise(new EventArgsInput(button, cursor, inputState.SuppressButtons)); + events.Legacy_ButtonReleased.Raise(new EventArgsInput(button, cursor, inputState.SuppressButtons)); if (button.TryGetKeyboard(out Keys key)) { if (key != Keys.None) - this.Events.Legacy_KeyReleased.Raise(new EventArgsKeyPressed(key)); + events.Legacy_KeyReleased.Raise(new EventArgsKeyPressed(key)); } else if (button.TryGetController(out Buttons controllerButton)) { if (controllerButton == Buttons.LeftTrigger || controllerButton == Buttons.RightTrigger) - this.Events.Legacy_ControllerTriggerReleased.Raise(new EventArgsControllerTriggerReleased(PlayerIndex.One, controllerButton, controllerButton == Buttons.LeftTrigger ? inputState.RealController.Triggers.Left : inputState.RealController.Triggers.Right)); + events.Legacy_ControllerTriggerReleased.Raise(new EventArgsControllerTriggerReleased(PlayerIndex.One, controllerButton, controllerButton == Buttons.LeftTrigger ? inputState.RealController.Triggers.Left : inputState.RealController.Triggers.Right)); else - this.Events.Legacy_ControllerButtonReleased.Raise(new EventArgsControllerButtonReleased(PlayerIndex.One, controllerButton)); + events.Legacy_ControllerButtonReleased.Raise(new EventArgsControllerButtonReleased(PlayerIndex.One, controllerButton)); } #endif } @@ -647,9 +653,9 @@ namespace StardewModdingAPI.Framework #if !SMAPI_3_0_STRICT // raise legacy state-changed events if (inputState.RealKeyboard != previousInputState.RealKeyboard) - this.Events.Legacy_KeyboardChanged.Raise(new EventArgsKeyboardStateChanged(previousInputState.RealKeyboard, inputState.RealKeyboard)); + events.Legacy_KeyboardChanged.Raise(new EventArgsKeyboardStateChanged(previousInputState.RealKeyboard, inputState.RealKeyboard)); if (inputState.RealMouse != previousInputState.RealMouse) - this.Events.Legacy_MouseChanged.Raise(new EventArgsMouseStateChanged(previousInputState.RealMouse, inputState.RealMouse, new Point((int)previousInputState.CursorPosition.ScreenPixels.X, (int)previousInputState.CursorPosition.ScreenPixels.Y), new Point((int)inputState.CursorPosition.ScreenPixels.X, (int)inputState.CursorPosition.ScreenPixels.Y))); + events.Legacy_MouseChanged.Raise(new EventArgsMouseStateChanged(previousInputState.RealMouse, inputState.RealMouse, new Point((int)previousInputState.CursorPosition.ScreenPixels.X, (int)previousInputState.CursorPosition.ScreenPixels.Y), new Point((int)inputState.CursorPosition.ScreenPixels.X, (int)inputState.CursorPosition.ScreenPixels.Y))); #endif } } @@ -667,12 +673,12 @@ namespace StardewModdingAPI.Framework this.Monitor.Log($"Context: menu changed from {was?.GetType().FullName ?? "none"} to {now?.GetType().FullName ?? "none"}.", LogLevel.Trace); // raise menu events - this.Events.MenuChanged.Raise(new MenuChangedEventArgs(was, now)); + events.MenuChanged.Raise(new MenuChangedEventArgs(was, now)); #if !SMAPI_3_0_STRICT if (now != null) - this.Events.Legacy_MenuChanged.Raise(new EventArgsClickableMenuChanged(was, now)); + events.Legacy_MenuChanged.Raise(new EventArgsClickableMenuChanged(was, now)); else - this.Events.Legacy_MenuClosed.Raise(new EventArgsClickableMenuClosed(was)); + events.Legacy_MenuClosed.Raise(new EventArgsClickableMenuClosed(was)); #endif } @@ -700,9 +706,9 @@ namespace StardewModdingAPI.Framework this.Monitor.Log($"Context: location list changed (added {addedText}; removed {removedText}).", LogLevel.Trace); } - this.Events.LocationListChanged.Raise(new LocationListChangedEventArgs(added, removed)); + events.LocationListChanged.Raise(new LocationListChangedEventArgs(added, removed)); #if !SMAPI_3_0_STRICT - this.Events.Legacy_LocationsChanged.Raise(new EventArgsLocationsChanged(added, removed)); + events.Legacy_LocationsChanged.Raise(new EventArgsLocationsChanged(added, removed)); #endif } @@ -719,9 +725,9 @@ namespace StardewModdingAPI.Framework Building[] removed = watcher.BuildingsWatcher.Removed.ToArray(); watcher.BuildingsWatcher.Reset(); - this.Events.BuildingListChanged.Raise(new BuildingListChangedEventArgs(location, added, removed)); + events.BuildingListChanged.Raise(new BuildingListChangedEventArgs(location, added, removed)); #if !SMAPI_3_0_STRICT - this.Events.Legacy_BuildingsChanged.Raise(new EventArgsLocationBuildingsChanged(location, added, removed)); + events.Legacy_BuildingsChanged.Raise(new EventArgsLocationBuildingsChanged(location, added, removed)); #endif } @@ -733,7 +739,7 @@ namespace StardewModdingAPI.Framework Debris[] removed = watcher.DebrisWatcher.Removed.ToArray(); watcher.DebrisWatcher.Reset(); - this.Events.DebrisListChanged.Raise(new DebrisListChangedEventArgs(location, added, removed)); + events.DebrisListChanged.Raise(new DebrisListChangedEventArgs(location, added, removed)); } // large terrain features changed @@ -744,7 +750,7 @@ namespace StardewModdingAPI.Framework LargeTerrainFeature[] removed = watcher.LargeTerrainFeaturesWatcher.Removed.ToArray(); watcher.LargeTerrainFeaturesWatcher.Reset(); - this.Events.LargeTerrainFeatureListChanged.Raise(new LargeTerrainFeatureListChangedEventArgs(location, added, removed)); + events.LargeTerrainFeatureListChanged.Raise(new LargeTerrainFeatureListChangedEventArgs(location, added, removed)); } // NPCs changed @@ -755,7 +761,7 @@ namespace StardewModdingAPI.Framework NPC[] removed = watcher.NpcsWatcher.Removed.ToArray(); watcher.NpcsWatcher.Reset(); - this.Events.NpcListChanged.Raise(new NpcListChangedEventArgs(location, added, removed)); + events.NpcListChanged.Raise(new NpcListChangedEventArgs(location, added, removed)); } // objects changed @@ -766,9 +772,9 @@ namespace StardewModdingAPI.Framework KeyValuePair[] removed = watcher.ObjectsWatcher.Removed.ToArray(); watcher.ObjectsWatcher.Reset(); - this.Events.ObjectListChanged.Raise(new ObjectListChangedEventArgs(location, added, removed)); + events.ObjectListChanged.Raise(new ObjectListChangedEventArgs(location, added, removed)); #if !SMAPI_3_0_STRICT - this.Events.Legacy_ObjectsChanged.Raise(new EventArgsLocationObjectsChanged(location, added, removed)); + events.Legacy_ObjectsChanged.Raise(new EventArgsLocationObjectsChanged(location, added, removed)); #endif } @@ -780,7 +786,7 @@ namespace StardewModdingAPI.Framework KeyValuePair[] removed = watcher.TerrainFeaturesWatcher.Removed.ToArray(); watcher.TerrainFeaturesWatcher.Reset(); - this.Events.TerrainFeatureListChanged.Raise(new TerrainFeatureListChangedEventArgs(location, added, removed)); + events.TerrainFeatureListChanged.Raise(new TerrainFeatureListChangedEventArgs(location, added, removed)); } } } @@ -798,9 +804,9 @@ namespace StardewModdingAPI.Framework if (this.Monitor.IsVerbose) this.Monitor.Log($"Events: time changed from {was} to {now}.", LogLevel.Trace); - this.Events.TimeChanged.Raise(new TimeChangedEventArgs(was, now)); + events.TimeChanged.Raise(new TimeChangedEventArgs(was, now)); #if !SMAPI_3_0_STRICT - this.Events.Legacy_TimeOfDayChanged.Raise(new EventArgsIntChanged(was, now)); + events.Legacy_TimeOfDayChanged.Raise(new EventArgsIntChanged(was, now)); #endif } else @@ -818,9 +824,9 @@ namespace StardewModdingAPI.Framework this.Monitor.Log($"Context: set location to {newLocation.Name}.", LogLevel.Trace); GameLocation oldLocation = playerTracker.LocationWatcher.PreviousValue; - this.Events.Warped.Raise(new WarpedEventArgs(playerTracker.Player, oldLocation, newLocation)); + events.Warped.Raise(new WarpedEventArgs(playerTracker.Player, oldLocation, newLocation)); #if !SMAPI_3_0_STRICT - this.Events.Legacy_PlayerWarped.Raise(new EventArgsPlayerWarped(oldLocation, newLocation)); + events.Legacy_PlayerWarped.Raise(new EventArgsPlayerWarped(oldLocation, newLocation)); #endif } @@ -830,9 +836,9 @@ namespace StardewModdingAPI.Framework if (this.Monitor.IsVerbose) this.Monitor.Log($"Events: player skill '{pair.Key}' changed from {pair.Value.PreviousValue} to {pair.Value.CurrentValue}.", LogLevel.Trace); - this.Events.LevelChanged.Raise(new LevelChangedEventArgs(playerTracker.Player, pair.Key, pair.Value.PreviousValue, pair.Value.CurrentValue)); + events.LevelChanged.Raise(new LevelChangedEventArgs(playerTracker.Player, pair.Key, pair.Value.PreviousValue, pair.Value.CurrentValue)); #if !SMAPI_3_0_STRICT - this.Events.Legacy_LeveledUp.Raise(new EventArgsLevelUp((EventArgsLevelUp.LevelType)pair.Key, pair.Value.CurrentValue)); + events.Legacy_LeveledUp.Raise(new EventArgsLevelUp((EventArgsLevelUp.LevelType)pair.Key, pair.Value.CurrentValue)); #endif } @@ -842,9 +848,9 @@ namespace StardewModdingAPI.Framework { if (this.Monitor.IsVerbose) this.Monitor.Log("Events: player inventory changed.", LogLevel.Trace); - this.Events.InventoryChanged.Raise(new InventoryChangedEventArgs(playerTracker.Player, changedItems)); + events.InventoryChanged.Raise(new InventoryChangedEventArgs(playerTracker.Player, changedItems)); #if !SMAPI_3_0_STRICT - this.Events.Legacy_InventoryChanged.Raise(new EventArgsInventoryChanged(Game1.player.Items, changedItems)); + events.Legacy_InventoryChanged.Raise(new EventArgsInventoryChanged(Game1.player.Items, changedItems)); #endif } @@ -854,7 +860,7 @@ namespace StardewModdingAPI.Framework if (this.Monitor.IsVerbose) this.Monitor.Log($"Context: mine level changed to {mineLevel}.", LogLevel.Trace); #if !SMAPI_3_0_STRICT - this.Events.Legacy_MineLevelChanged.Raise(new EventArgsMineLevelChanged(playerTracker.MineLevelWatcher.PreviousValue, mineLevel)); + events.Legacy_MineLevelChanged.Raise(new EventArgsMineLevelChanged(playerTracker.MineLevelWatcher.PreviousValue, mineLevel)); #endif } } @@ -867,22 +873,21 @@ namespace StardewModdingAPI.Framework /********* ** Game update *********/ - this.TicksElapsed++; - // game launched - if (this.TicksElapsed == 1) - this.Events.GameLaunched.Raise(new GameLaunchedEventArgs()); + bool isFirstTick = Game1.ticks == 0; + if (isFirstTick) + events.GameLaunched.Raise(new GameLaunchedEventArgs()); // preloaded if (Context.IsSaveLoaded && this.LoadStage != LoadStage.Loaded && this.LoadStage != LoadStage.Ready) this.OnLoadStageChanged(LoadStage.Loaded); // update tick - bool isOneSecond = this.TicksElapsed % 60 == 0; - this.Events.UnvalidatedUpdateTicking.Raise(new UnvalidatedUpdateTickingEventArgs(this.TicksElapsed)); - this.Events.UpdateTicking.Raise(new UpdateTickingEventArgs(this.TicksElapsed)); + bool isOneSecond = Game1.ticks % 60 == 0; + events.UnvalidatedUpdateTicking.RaiseEmpty(); + events.UpdateTicking.RaiseEmpty(); if (isOneSecond) - this.Events.OneSecondUpdateTicking.Raise(new OneSecondUpdateTickingEventArgs(this.TicksElapsed)); + events.OneSecondUpdateTicking.RaiseEmpty(); try { this.Input.UpdateSuppression(); @@ -892,35 +897,32 @@ namespace StardewModdingAPI.Framework { this.MonitorForGame.Log($"An error occured in the base update loop: {ex.GetLogSummary()}", LogLevel.Error); } - this.Events.UnvalidatedUpdateTicked.Raise(new UnvalidatedUpdateTickedEventArgs(this.TicksElapsed)); - this.Events.UpdateTicked.Raise(new UpdateTickedEventArgs(this.TicksElapsed)); + events.UnvalidatedUpdateTicked.RaiseEmpty(); + events.UpdateTicked.RaiseEmpty(); if (isOneSecond) - this.Events.OneSecondUpdateTicked.Raise(new OneSecondUpdateTickedEventArgs(this.TicksElapsed)); + events.OneSecondUpdateTicked.RaiseEmpty(); /********* ** Update events *********/ #if !SMAPI_3_0_STRICT - this.Events.Legacy_UnvalidatedUpdateTick.Raise(); - if (this.TicksElapsed == 1) - this.Events.Legacy_FirstUpdateTick.Raise(); - this.Events.Legacy_UpdateTick.Raise(); - if (this.CurrentUpdateTick % 2 == 0) - this.Events.Legacy_SecondUpdateTick.Raise(); - if (this.CurrentUpdateTick % 4 == 0) - this.Events.Legacy_FourthUpdateTick.Raise(); - if (this.CurrentUpdateTick % 8 == 0) - this.Events.Legacy_EighthUpdateTick.Raise(); - if (this.CurrentUpdateTick % 15 == 0) - this.Events.Legacy_QuarterSecondTick.Raise(); - if (this.CurrentUpdateTick % 30 == 0) - this.Events.Legacy_HalfSecondTick.Raise(); - if (this.CurrentUpdateTick % 60 == 0) - this.Events.Legacy_OneSecondTick.Raise(); + events.Legacy_UnvalidatedUpdateTick.Raise(); + if (isFirstTick) + events.Legacy_FirstUpdateTick.Raise(); + events.Legacy_UpdateTick.Raise(); + if (Game1.ticks % 2 == 0) + events.Legacy_SecondUpdateTick.Raise(); + if (Game1.ticks % 4 == 0) + events.Legacy_FourthUpdateTick.Raise(); + if (Game1.ticks % 8 == 0) + events.Legacy_EighthUpdateTick.Raise(); + if (Game1.ticks % 15 == 0) + events.Legacy_QuarterSecondTick.Raise(); + if (Game1.ticks % 30 == 0) + events.Legacy_HalfSecondTick.Raise(); + if (Game1.ticks % 60 == 0) + events.Legacy_OneSecondTick.Raise(); #endif - this.CurrentUpdateTick += 1; - if (this.CurrentUpdateTick >= 60) - this.CurrentUpdateTick = 0; this.UpdateCrashTimer.Reset(); } @@ -988,10 +990,10 @@ namespace StardewModdingAPI.Framework [SuppressMessage("SMAPI.CommonErrors", "AvoidImplicitNetFieldCast", Justification = "copied from game code as-is")] private void DrawImpl(GameTime gameTime) { + var events = this.Events; + if (Game1._newDayTask != null) - { this.GraphicsDevice.Clear(this.bgColor); - } else { if ((double)Game1.options.zoomLevel != 1.0) @@ -1003,17 +1005,17 @@ namespace StardewModdingAPI.Framework if (activeClickableMenu != null) { Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); - this.Events.Rendering.RaiseEmpty(); + events.Rendering.RaiseEmpty(); try { - this.Events.RenderingActiveMenu.RaiseEmpty(); + events.RenderingActiveMenu.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_OnPreRenderGuiEvent.Raise(); + events.Legacy_OnPreRenderGuiEvent.Raise(); #endif activeClickableMenu.draw(Game1.spriteBatch); - this.Events.RenderedActiveMenu.RaiseEmpty(); + events.RenderedActiveMenu.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_OnPostRenderGuiEvent.Raise(); + events.Legacy_OnPostRenderGuiEvent.Raise(); #endif } catch (Exception ex) @@ -1021,9 +1023,9 @@ namespace StardewModdingAPI.Framework this.Monitor.Log($"The {activeClickableMenu.GetType().FullName} menu crashed while drawing itself during save. SMAPI will force it to exit to avoid crashing the game.\n{ex.GetLogSummary()}", LogLevel.Error); activeClickableMenu.exitThisMenu(); } - this.Events.Rendered.RaiseEmpty(); + events.Rendered.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_OnPostRenderEvent.Raise(); + events.Legacy_OnPostRenderEvent.Raise(); #endif Game1.spriteBatch.End(); @@ -1043,18 +1045,18 @@ namespace StardewModdingAPI.Framework { Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); - this.Events.Rendering.RaiseEmpty(); + events.Rendering.RaiseEmpty(); try { Game1.activeClickableMenu.drawBackground(Game1.spriteBatch); - this.Events.RenderingActiveMenu.RaiseEmpty(); + events.RenderingActiveMenu.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_OnPreRenderGuiEvent.Raise(); + events.Legacy_OnPreRenderGuiEvent.Raise(); #endif Game1.activeClickableMenu.draw(Game1.spriteBatch); - this.Events.RenderedActiveMenu.RaiseEmpty(); + events.RenderedActiveMenu.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_OnPostRenderGuiEvent.Raise(); + events.Legacy_OnPostRenderGuiEvent.Raise(); #endif } catch (Exception ex) @@ -1062,9 +1064,9 @@ namespace StardewModdingAPI.Framework this.Monitor.Log($"The {Game1.activeClickableMenu.GetType().FullName} menu crashed while drawing itself. SMAPI will force it to exit to avoid crashing the game.\n{ex.GetLogSummary()}", LogLevel.Error); Game1.activeClickableMenu.exitThisMenu(); } - this.Events.Rendered.RaiseEmpty(); + events.Rendered.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_OnPostRenderEvent.Raise(); + events.Legacy_OnPostRenderEvent.Raise(); #endif Game1.spriteBatch.End(); this.drawOverlays(Game1.spriteBatch); @@ -1085,13 +1087,13 @@ namespace StardewModdingAPI.Framework else if (Game1.gameMode == (byte)11) { Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); - this.Events.Rendering.RaiseEmpty(); + events.Rendering.RaiseEmpty(); Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3685"), new Vector2(16f, 16f), Color.HotPink); Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3686"), new Vector2(16f, 32f), new Color(0, (int)byte.MaxValue, 0)); Game1.spriteBatch.DrawString(Game1.dialogueFont, Game1.parseText(Game1.errorMessage, Game1.dialogueFont, Game1.graphics.GraphicsDevice.Viewport.Width), new Vector2(16f, 48f), Color.White); - this.Events.Rendered.RaiseEmpty(); + events.Rendered.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_OnPostRenderEvent.Raise(); + events.Legacy_OnPostRenderEvent.Raise(); #endif Game1.spriteBatch.End(); } @@ -1119,19 +1121,19 @@ namespace StardewModdingAPI.Framework else if (Game1.showingEndOfNightStuff) { Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); - this.Events.Rendering.RaiseEmpty(); + events.Rendering.RaiseEmpty(); if (Game1.activeClickableMenu != null) { try { - this.Events.RenderingActiveMenu.RaiseEmpty(); + events.RenderingActiveMenu.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_OnPreRenderGuiEvent.Raise(); + events.Legacy_OnPreRenderGuiEvent.Raise(); #endif Game1.activeClickableMenu.draw(Game1.spriteBatch); - this.Events.RenderedActiveMenu.RaiseEmpty(); + events.RenderedActiveMenu.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_OnPostRenderGuiEvent.Raise(); + events.Legacy_OnPostRenderGuiEvent.Raise(); #endif } catch (Exception ex) @@ -1140,7 +1142,7 @@ namespace StardewModdingAPI.Framework Game1.activeClickableMenu.exitThisMenu(); } } - this.Events.Rendered.RaiseEmpty(); + events.Rendered.RaiseEmpty(); Game1.spriteBatch.End(); this.drawOverlays(Game1.spriteBatch); if ((double)Game1.options.zoomLevel == 1.0) @@ -1154,7 +1156,7 @@ namespace StardewModdingAPI.Framework else if (Game1.gameMode == (byte)6 || Game1.gameMode == (byte)3 && Game1.currentLocation == null) { Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); - this.Events.Rendering.RaiseEmpty(); + events.Rendering.RaiseEmpty(); string str1 = ""; for (int index = 0; (double)index < gameTime.TotalGameTime.TotalMilliseconds % 999.0 / 333.0; ++index) str1 += "."; @@ -1166,7 +1168,7 @@ namespace StardewModdingAPI.Framework int x = 64; int y = Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Bottom - height; SpriteText.drawString(Game1.spriteBatch, s, x, y, 999999, widthOfString, height, 1f, 0.88f, false, 0, str3, -1); - this.Events.Rendered.RaiseEmpty(); + events.Rendered.RaiseEmpty(); Game1.spriteBatch.End(); this.drawOverlays(Game1.spriteBatch); if ((double)Game1.options.zoomLevel != 1.0) @@ -1195,7 +1197,7 @@ namespace StardewModdingAPI.Framework { Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); if (++batchOpens == 1) - this.Events.Rendering.RaiseEmpty(); + events.Rendering.RaiseEmpty(); } else { @@ -1205,7 +1207,7 @@ namespace StardewModdingAPI.Framework this.GraphicsDevice.Clear(Color.White * 0.0f); Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); if (++batchOpens == 1) - this.Events.Rendering.RaiseEmpty(); + events.Rendering.RaiseEmpty(); Game1.spriteBatch.Draw(Game1.staminaRect, Game1.lightmap.Bounds, Game1.currentLocation.Name.StartsWith("UndergroundMine") ? Game1.mine.getLightingColor(gameTime) : (Game1.ambientLight.Equals(Color.White) || Game1.isRaining && (bool)((NetFieldBase)Game1.currentLocation.isOutdoors) ? Game1.outdoorLight : Game1.ambientLight)); for (int index = 0; index < Game1.currentLightSources.Count; ++index) { @@ -1220,10 +1222,10 @@ namespace StardewModdingAPI.Framework this.GraphicsDevice.Clear(this.bgColor); Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); if (++batchOpens == 1) - this.Events.Rendering.RaiseEmpty(); - this.Events.RenderingWorld.RaiseEmpty(); + events.Rendering.RaiseEmpty(); + events.RenderingWorld.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_OnPreRenderEvent.Raise(); + events.Legacy_OnPreRenderEvent.Raise(); #endif if (Game1.background != null) Game1.background.draw(Game1.spriteBatch); @@ -1480,7 +1482,7 @@ namespace StardewModdingAPI.Framework Game1.spriteBatch.End(); } Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); - this.Events.RenderedWorld.RaiseEmpty(); + events.RenderedWorld.RaiseEmpty(); if (Game1.drawGrid) { int num1 = -Game1.viewport.X % 64; @@ -1536,14 +1538,14 @@ namespace StardewModdingAPI.Framework this.drawBillboard(); if ((Game1.displayHUD || Game1.eventUp) && (Game1.currentBillboard == 0 && Game1.gameMode == (byte)3) && (!Game1.freezeControls && !Game1.panMode && !Game1.HostPaused)) { - this.Events.RenderingHud.RaiseEmpty(); + events.RenderingHud.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_OnPreRenderHudEvent.Raise(); + events.Legacy_OnPreRenderHudEvent.Raise(); #endif this.drawHUD(); - this.Events.RenderedHud.RaiseEmpty(); + events.RenderedHud.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_OnPostRenderHudEvent.Raise(); + events.Legacy_OnPostRenderHudEvent.Raise(); #endif } else if (Game1.activeClickableMenu == null && Game1.farmEvent == null) @@ -1652,14 +1654,14 @@ namespace StardewModdingAPI.Framework { try { - this.Events.RenderingActiveMenu.RaiseEmpty(); + events.RenderingActiveMenu.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_OnPreRenderGuiEvent.Raise(); + events.Legacy_OnPreRenderGuiEvent.Raise(); #endif Game1.activeClickableMenu.draw(Game1.spriteBatch); - this.Events.RenderedActiveMenu.RaiseEmpty(); + events.RenderedActiveMenu.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_OnPostRenderGuiEvent.Raise(); + events.Legacy_OnPostRenderGuiEvent.Raise(); #endif } catch (Exception ex) @@ -1676,9 +1678,9 @@ namespace StardewModdingAPI.Framework SpriteText.drawStringWithScrollBackground(Game1.spriteBatch, s, 96, 32, "", 1f, -1); } - this.Events.Rendered.RaiseEmpty(); + events.Rendered.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_OnPostRenderEvent.Raise(); + events.Legacy_OnPostRenderEvent.Raise(); #endif Game1.spriteBatch.End(); this.drawOverlays(Game1.spriteBatch); -- cgit From 29907844331b258db6b9760eb7ed9131eccfa83f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 27 Dec 2018 21:54:03 -0500 Subject: tweak release notes --- docs/release-notes.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 3913862b..fc8adc61 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -6,9 +6,10 @@ * Tweaked installer to reduce antivirus false positives. * For modders: - * Added `GameLoop.OneSecondUpdateTicking/Ticked` events. - * Added `Specialised.LoadStageChanged` for mods which need to do something at a specific point in the game's save loading process. - * You can now use read/write save data as soon as the save is loaded (instead of once the world is initialised). + * Added new [events](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events): + * `GameLoop.OneSecondUpdateTicking` and `OneSecondUpdateTicked`; + * `Specialised.LoadStageChanged`. + * You can now use `helper.Data.Read/WriteSaveData` as soon as the save is loaded (instead of once the world is initialised). ## 2.9.3 * For players: -- cgit From 5db5ca1c88cf6592ceb4304d3b0682394050bbc8 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 29 Dec 2018 15:53:11 -0500 Subject: reduce cache time for mod compatibility list --- docs/release-notes.md | 3 +++ src/SMAPI.Web/Controllers/ModsController.cs | 10 +++++----- .../Framework/ConfigModels/ModCompatibilityListConfig.cs | 12 ++++++++++++ src/SMAPI.Web/Startup.cs | 1 + src/SMAPI.Web/appsettings.json | 4 ++++ 5 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 src/SMAPI.Web/Framework/ConfigModels/ModCompatibilityListConfig.cs (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index fc8adc61..f389ba25 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -11,6 +11,9 @@ * `Specialised.LoadStageChanged`. * You can now use `helper.Data.Read/WriteSaveData` as soon as the save is loaded (instead of once the world is initialised). +* For the web UI: + * Reduced mod compatibility list's cache time. + ## 2.9.3 * For players: * Fixed errors hovering items in some cases with SMAPI 2.9.2. diff --git a/src/SMAPI.Web/Controllers/ModsController.cs b/src/SMAPI.Web/Controllers/ModsController.cs index 1ac0aff2..ca866a8d 100644 --- a/src/SMAPI.Web/Controllers/ModsController.cs +++ b/src/SMAPI.Web/Controllers/ModsController.cs @@ -22,7 +22,7 @@ namespace StardewModdingAPI.Web.Controllers private readonly IMemoryCache Cache; /// The number of minutes successful update checks should be cached before refetching them. - private readonly int SuccessCacheMinutes; + private readonly int CacheMinutes; /********* @@ -31,12 +31,12 @@ namespace StardewModdingAPI.Web.Controllers /// Construct an instance. /// The cache in which to store mod metadata. /// The config settings for mod update checks. - public ModsController(IMemoryCache cache, IOptions configProvider) + public ModsController(IMemoryCache cache, IOptions configProvider) { - ModUpdateCheckConfig config = configProvider.Value; + ModCompatibilityListConfig config = configProvider.Value; this.Cache = cache; - this.SuccessCacheMinutes = config.SuccessCacheMinutes; + this.CacheMinutes = config.CacheMinutes; } /// Display information for all mods. @@ -66,7 +66,7 @@ namespace StardewModdingAPI.Web.Controllers .OrderBy(p => Regex.Replace(p.Name.ToLower(), "[^a-z0-9]", "")) // ignore case, spaces, and special characters when sorting ); - entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(this.SuccessCacheMinutes); + entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(this.CacheMinutes); return model; }); } diff --git a/src/SMAPI.Web/Framework/ConfigModels/ModCompatibilityListConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/ModCompatibilityListConfig.cs new file mode 100644 index 00000000..d9ac9f02 --- /dev/null +++ b/src/SMAPI.Web/Framework/ConfigModels/ModCompatibilityListConfig.cs @@ -0,0 +1,12 @@ +namespace StardewModdingAPI.Web.Framework.ConfigModels +{ + /// The config settings for mod compatibility list. + internal class ModCompatibilityListConfig + { + /********* + ** Accessors + *********/ + /// The number of minutes data from the wiki should be cached before refetching it. + public int CacheMinutes { get; set; } + } +} diff --git a/src/SMAPI.Web/Startup.cs b/src/SMAPI.Web/Startup.cs index 4e3aaed3..91553513 100644 --- a/src/SMAPI.Web/Startup.cs +++ b/src/SMAPI.Web/Startup.cs @@ -50,6 +50,7 @@ namespace StardewModdingAPI.Web { // init configuration services + .Configure(this.Configuration.GetSection("ModCompatibilityList")) .Configure(this.Configuration.GetSection("ModUpdateCheck")) .Configure(this.Configuration.GetSection("Site")) .Configure(options => options.ConstraintMap.Add("semanticVersion", typeof(VersionConstraint))) diff --git a/src/SMAPI.Web/appsettings.json b/src/SMAPI.Web/appsettings.json index 89505a45..e97b2339 100644 --- a/src/SMAPI.Web/appsettings.json +++ b/src/SMAPI.Web/appsettings.json @@ -47,6 +47,10 @@ "PastebinDevKey": null // see top note }, + "ModCompatibilityList": { + "WikiCacheMinutes": 10 + }, + "ModUpdateCheck": { "SuccessCacheMinutes": 60, "ErrorCacheMinutes": 5, -- cgit From 3a5356b039bb9a25dc4031eb5a896894b8f917e2 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 29 Dec 2018 18:16:12 -0500 Subject: add dates & compatible game versions to release notes, standardise inconsistent older notes --- docs/release-notes.md | 183 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 118 insertions(+), 65 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index f389ba25..a8fa99c3 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,4 +1,4 @@ -# Release notes +# Release notes ## Upcoming release * For players: * Added `world_clear` console command to remove spawned or placed entities. @@ -15,11 +15,15 @@ * Reduced mod compatibility list's cache time. ## 2.9.3 +Released 16 December 2018 for Stardew Valley 1.3.32. + * For players: * Fixed errors hovering items in some cases with SMAPI 2.9.2. * Fixed some multiplayer features broken when a farmhand returns to title and rejoins. ## 2.9.2 +Released 16 December 2018 for Stardew Valley 1.3.32. + * For players: * SMAPI now prevents invalid items from crashing the game on hover. * Fixed some multiplayer features broken when connecting via Steam friends. @@ -40,11 +44,15 @@ * Added SMAPI 3.0 readiness to mod API data. ## 2.9.1 +Released 07 December 2018 for Stardew Valley 1.3.32. + * For players: * Fixed crash in SMAPI 2.9 when constructing certain buildings. * Fixed error when a map asset is reloaded in rare cases. ## 2.9 +Released 07 December 2018 for Stardew Valley 1.3.32. + * For players: * Added support for ModDrop in update checks and the mod compatibility list. * Added friendly error for Steam players when Steam isn't loaded. @@ -72,12 +80,18 @@ * Fixed compatibility list showing beta header when there's no beta in progress. ## 2.8.2 +Released 19 November 2018 for Stardew Valley 1.3.32. + * Fixed game crash in MacOS with SMAPI 2.8. ## 2.8.1 +Released 19 November 2018 for Stardew Valley 1.3.32. + * Fixed installer error on Windows with SMAPI 2.8. ## 2.8 +Released 19 November 2018 for Stardew Valley 1.3.32. + * For players: * Reorganised SMAPI files: * Moved most SMAPI files into a `smapi-internal` subfolder (so your game folder is less messy). @@ -145,6 +159,8 @@ * Fixed mod web API returning a concatenated name for mods with alternate names. ## 2.7 +Released 14 August 2018 for Stardew Valley 1.3.28. + * For players: * Updated for Stardew Valley 1.3.28. * Improved how mod issues are listed in the console and log. @@ -174,6 +190,8 @@ _These are no longer useful, even if the player still has earlier versions of SMAPI. Older versions of SMAPI won't launch in Stardew Valley 1.3 (so they won't check for updates), and newer versions of SMAPI/mods won't work with older versions of the game._ ## 2.6 +Released 01 August 2018 for Stardew Valley 1.3.27. + * For players: * Updated for Stardew Valley 1.3. * Added automatic save backups. @@ -274,6 +292,8 @@ * Updated to Mono.Cecil 0.10. ## 2.5.5 +Released 11 April 2018 for Stardew Valley 1.2.30–1.2.33. + * For players: * Fixed mod not loaded if it has an optional dependency that's loaded but skipped. * Fixed mod update alerts not shown if one mod has an invalid remote version. @@ -282,17 +302,19 @@ * Fixed error when two content packs use different capitalisation for the same required mod ID. * Fixed rare crash if the game duplicates an item. -* For the [log parser][]: +* For the [log parser](https://log.smapi.io): * Tweaked UI. ## 2.5.4 +Released 26 March 2018 for Stardew Valley 1.2.30–1.2.33. + * For players: * Fixed some textures not updated when a mod changes them. * Fixed visual bug on Linux/Mac when mods overlay textures. * Fixed error when mods remove an asset editor/loader. * Fixed minimum game version incorrectly increased in SMAPI 2.5.3. -* For the [log parser][]: +* For the [log parser](https://log.smapi.io): * Fixed error when log text contains certain tokens. * For modders: @@ -302,6 +324,8 @@ * Added support for beta update track to support upcoming Stardew Valley 1.3 beta. ## 2.5.3 +Released 13 March 2018 for Stardew Valley ~~1.2.30~~–1.2.33. + * For players: * Simplified and improved skipped-mod messages. * Fixed rare crash with some combinations of manifest fields and internal mod data. @@ -312,7 +336,7 @@ * Fixed Linux ["magic number is wrong" errors](https://github.com/mono/mono/issues/6752) by changing default terminal order. * Updated compatibility list and added update checks for more mods. -* For the [log parser][]: +* For the [log parser](https://log.smapi.io): * Fixed incorrect filtering in some cases. * Fixed error if mods have duplicate names. * Fixed parse bugs if a mod has no author name. @@ -321,17 +345,23 @@ * Internal changes to support the upcoming Stardew Valley 1.3 update. ## 2.5.2 +Released 25 February 2018 for Stardew Valley 1.2.30–1.2.33. + * For modders: * Fixed issue where replacing an asset through `asset.AsImage()` or `asset.AsDictionary()` didn't take effect. -* For the [log parser][]: +* For the [log parser](https://log.smapi.io): * Fixed blank page after uploading a log in some cases. ## 2.5.1 +Released 24 February 2018 for Stardew Valley 1.2.30–1.2.33. + * For players: * Fixed event error in rare cases. ## 2.5 +Released 24 February 2018 for Stardew Valley 1.2.30–1.2.33. + * For players: * **Added support for [content packs](https://stardewvalleywiki.com/Modding:Content_packs)**. _Content packs are collections of files for a SMAPI mod to load. These can be installed directly under `Mods` like a normal SMAPI mod, get automatic update and compatibility checks, and provide convenient APIs to the mods that read them._ @@ -351,7 +381,7 @@ * Fixed unhelpful error when a translation file has duplicate keys due to case-insensitivity. * Fixed some JSON field names being case-sensitive. -* For the [log parser][]: +* For the [log parser](https://log.smapi.io): * Added support for SMAPI 2.5 content packs. * Reduced download size when viewing a parsed log with repeated errors. * Improved parse error handling. @@ -362,6 +392,8 @@ * Reimplemented log parser with serverside parsing and vue.js on the frontend. ## 2.4 +Released 24 January 2018 for Stardew Valley 1.2.30–1.2.33. + * For players: * Fixed visual map glitch in rare cases. * Fixed error parsing JSON files which have curly quotes. @@ -370,7 +402,7 @@ * Fixed intermittent errors (e.g. 'collection has been modified') with some mods when loading a save. * Fixed compatibility with Linux Terminator terminal. -* For the [log parser][]: +* For the [log parser](https://log.smapi.io): * Fixed error parsing logs with zero installed mods. * For modders: @@ -387,6 +419,8 @@ * Overhauled input handling to support future input events. ## 2.3 +Released 26 December 2017 for Stardew Valley 1.2.30–1.2.33. + * For players: * Added a user-friendly [download page](https://smapi.io). * Improved cryptic libgdiplus errors on Mac when Mono isn't installed. @@ -403,10 +437,12 @@ * Fixed issue where a mod could change the cursor position reported to other mods. * Updated compatibility list. -* For the [log parser][]: +* For the [log parser](https://log.smapi.io): * Fixed broken favicon. ## 2.2 +Released 02 December 2017 for Stardew Valley 1.2.30–1.2.33. + * For players: * Fixed error when a mod loads custom assets on Linux/Mac. * Fixed error when checking for updates on Linux/Mac due to API HTTPS redirect. @@ -415,7 +451,7 @@ * Improved error when a mod has an invalid `EntryDLL` filename format. * Updated compatibility list. -* For the [log parser][]: +* For the [log parser](https://log.smapi.io): * Logs no longer expire after a week. * Fixed error when uploading very large logs. * Slightly improved the UI. @@ -427,8 +463,10 @@ * Fixed input events' `e.SuppressButton()` method not working with mouse buttons. ## 2.1 +Released 01 November 2017 for Stardew Valley 1.2.30–1.2.33. + * For players: - * Added a [log parser][] site. + * Added a [log parser](https://log.smapi.io) site. * Added better Steam instructions to the SMAPI installer. * Renamed the bundled _TrainerMod_ to _ConsoleCommands_ to make its purpose clearer. * Removed the game's test messages from the console log. @@ -455,6 +493,8 @@ * Added the installer version and platform to the installer window title to simplify troubleshooting. ## 2.0 +Released 14 October 2017 for Stardew Valley 1.2.30–1.2.33. + ### Release highlights * **Mod update checks** SMAPI now checks if your mods have updates available, and will alert you in the console with a convenient link to the @@ -541,6 +581,8 @@ For SMAPI developers: * Removed SMAPI 1._x_ compatibility mode. ## 1.15.4 +Released 09 September 2017 for Stardew Valley 1.2.30–1.2.33. + For players: * Fixed errors when loading some custom maps on Linux/Mac or using XNB Loader. * Fixed errors in rare cases when a mod calculates an in-game date. @@ -552,10 +594,14 @@ For SMAPI developers: * Internal changes to support the upcoming SMAPI 2.0 release. ## 1.15.3 +Released 23 August 2017 for Stardew Valley 1.2.30–1.2.33. + For players: * Fixed mods being wrongly marked as duplicate in some cases. ## 1.15.2 +Released 23 August 2017 for Stardew Valley 1.2.30–1.2.33. + For players: * Improved errors when a mod DLL can't be loaded. * Improved errors when using very old versions of Stardew Valley. @@ -573,6 +619,8 @@ For SMAPI developers: * Internal changes to support the upcoming SMAPI 2.0 release. ## 1.15.1 +Released 10 July 2017 for Stardew Valley 1.2.30–1.2.33. + For players: * Fixed controller mod input broken in 1.15. * Fixed TrainerMod packaging unneeded files. @@ -581,6 +629,8 @@ For modders: * Fixed mod registry lookups by unique ID not being case-insensitive. ## 1.15 +Released 08 July 2017 for Stardew Valley 1.2.30–1.2.31. + For players: * Cleaned up SMAPI console a bit. * Revamped TrainerMod's item commands: @@ -612,7 +662,7 @@ For SMAPI developers: * Compiling SMAPI now uses your `~/stardewvalley.targets` file if present. ## 1.14 -See [log](https://github.com/Pathoschild/SMAPI/compare/1.13...1.14). +Released 02 July 2017 for Stardew Valley 1.2.30. For players: * SMAPI now shows friendly errors when... @@ -640,12 +690,14 @@ For modders: * Deprecated `TimeEvents.DayOfMonthChanged`, `SeasonOfYearChanged`, and `YearOfGameChanged`. These don't do what most modders think they do and aren't very reliable, since they depend on the SMAPI/game lifecycle which can change. You should use `TimeEvents.AfterDayStarted` or `SaveEvents.BeforeSave` instead. ## 1.13.1 +Released 19 May 2017 for Stardew Valley 1.2.26–1.2.29. + For players: * Fixed errors when loading a mod with no name or version. * Fixed mods with no manifest `Name` field having no name (SMAPI will now shows their filename). ## 1.13 -See [log](https://github.com/Pathoschild/SMAPI/compare/1.12...1.13). +Released 19 May 2017 for Stardew Valley 1.2.26–1.2.29. For players: * SMAPI now recovers better from mod draw errors and detects when the error is irrecoverable. @@ -668,7 +720,7 @@ For mod developers: * Internal refactoring for upcoming features. ## 1.12 -See [log](https://github.com/Pathoschild/SMAPI/compare/1.11...1.12). +Released 03 May 2017 for Stardew Valley 1.2.26–1.2.29. For players: * The installer now lets you choose the install path if you have multiple copies of the game, instead of using the first path found. @@ -684,7 +736,7 @@ For mod developers: * Fixed content API error when loading an XNB from the mod folder on Mac. ## 1.11 -See [log](https://github.com/Pathoschild/SMAPI/compare/1.10...1.11). +Released 30 April 2017 for Stardew Valley 1.2.26. For players: * SMAPI now detects issues in `ObjectInformation.xnb` files caused by outdated XNB mods. @@ -700,7 +752,7 @@ For mod developers: * Fixed value-changed events being raised when the player loads a save due to values being initialised. ## 1.10 -See [log](https://github.com/Pathoschild/SMAPI/compare/1.9...1.10). +Released 24 April 2017 for Stardew Valley 1.2.26. For players: * Updated to Stardew Valley 1.2. @@ -720,7 +772,7 @@ For mod developers: * Fixed `Constants.SaveFolderName` not set for a new game until the save is created. ## 1.9 -See [log](https://github.com/Pathoschild/SMAPI/compare/1.8...1.9). +Released 05 April 2017 for Stardew Valley 1.1–1.11. For players: * SMAPI now detects incompatible mods and disables them before they cause problems. @@ -762,7 +814,7 @@ For SMAPI developers: * Added support for debugging SMAPI on Linux/Mac if supported by the editor. ## 1.8 -See [log](https://github.com/Pathoschild/SMAPI/compare/1.7...1.8). +Released 04 February 2017 for Stardew Valley 1.1–1.11. For players: * Mods no longer generate `.cache` subfolders. @@ -787,7 +839,7 @@ For SMAPI developers: * reduces log verbosity. ## 1.7 -See [log](https://github.com/Pathoschild/SMAPI/compare/1.6...1.7). +Released 19 January 2017 for Stardew Valley 1.1–1.11. For players: * The console now shows the folder path where mods should be added. @@ -802,7 +854,7 @@ For mod developers: * Increased deprecation levels for `SObject`, `LogWriter` (not `Log`), and `Mod.Entry(ModHelper)` (not `Mod.Entry(IModHelper)`) to _pending removal_. Increased deprecation levels for `Mod.PerSaveConfigFolder`, `Mod.PerSaveConfigPath`, and `Version.VersionString` to _info_. ## 1.6 -See [log](https://github.com/Pathoschild/SMAPI/compare/1.5...1.6). +Released 16 January 2017 for Stardew Valley 1.1–1.11. For players: * Added console commands to open the game/data folders. @@ -827,7 +879,7 @@ For SMAPI developers: * Fixed issue where `TrainerMod` used older logic to detect the game path. ## 1.5 -See [log](https://github.com/Pathoschild/SMAPI/compare/1.4...1.5). +Released 27 December 2016 for Stardew Valley 1.1–1.11. For players: * Added an option to disable update checks. @@ -841,7 +893,7 @@ For developers: * Increased deprecation levels for `SObject`, `Extensions`, `LogWriter` (not `Log`), `SPlayer`, and `Mod.Entry(ModHelper)` (not `Mod.Entry(IModHelper)`). ## 1.4 -See [log](https://github.com/Pathoschild/SMAPI/compare/1.3...1.4). +Released 12 December 2016 for Stardew Valley 1.1–1.11. For players: * SMAPI will now prevent mods from crashing your game with menu errors. @@ -860,14 +912,14 @@ For developers: * Fixed an issue where you couldn't debug into an assembly because it was copied into the `.cache` directory. That will now only happen if necessary. ## 1.3 -See [log](https://github.com/Pathoschild/SMAPI/compare/1.2...1.3). +Released 04 December 2016 for Stardew Valley 1.1–1.11. For players: * You can now run most mods on any platform (e.g. run Windows mods on Linux/Mac). * Fixed the normal uninstaller not removing files added by the 'SMAPI for developers' installer. ## 1.2 -See [log](https://github.com/Pathoschild/SMAPI/compare/1.1.1...1.2). +Released 25 November 2016 for Stardew Valley 1.1–1.11. For players: * Fixed compatibility with some older mods. @@ -878,15 +930,23 @@ For players: For developers: * Improved logging to show `ReflectionTypeLoadException` details when it's caught by SMAPI. +## 1.1.1 +Released 19 November 2016 for Stardew Valley 1.1–1.11. + +For players: + * Fixed compatibility with some older mods. + * Fixed race condition where some mods would sometimes crash because the game wasn't ready yet. + +For developers: + * Fixed deprecation warnings being repeated if the mod can't be identified. + ## 1.1 -See [log](https://github.com/Pathoschild/SMAPI/compare/1.0...1.1.1). +Released 17 November 2016 for Stardew Valley 1.1–1.11. For players: * Fixed console exiting immediately when some exceptions occur. * Fixed an error in 1.0 when mod uses `config.json` but the file doesn't exist. * Fixed critical errors being saved to a separate log file. - * Fixed compatibility with some older mods.1.1.1 - * Fixed race condition where some mods would sometimes crash because the game wasn't ready yet.1.1.1 For developers: * Added new logging interface: @@ -897,10 +957,9 @@ For developers: * more consistent and intuitive console color scheme. * Added optional `MinimumApiVersion` to `manifest.json`. * Added emergency interrupt feature for dangerous mods. - * Fixed deprecation warnings being repeated if the mod can't be identified.1.1.1 ## 1.0 -See [log](https://github.com/Pathoschild/SMAPI/compare/0.40.1.1-3...1.0). +Released 11 November 2016 for Stardew Valley 1.1–1.11. For players: * Added support for Linux and Mac. @@ -929,46 +988,40 @@ For SMAPI developers: * Internal cleanup & refactoring. ## 0.x -* 0.40.1.1 (2016-09-30, [log](https://github.com/Pathoschild/SMAPI/compare/0.40.0...0.40.1.1-3)) +* 0.40.1.1 (30 September 2016) * Added support for Stardew Valley 1.1. - -* 0.40.0 (2016-04-05, [log](https://github.com/Pathoschild/SMAPI/compare/0.39.7...0.40.0)) +* 0.40.0 (05 April 2016) * Fixed an error that ocurred during minigames. - -* 0.39.7 (2016-04-04, [log](https://github.com/Pathoschild/SMAPI/compare/0.39.6...0.39.7)) +* 0.39.7 (04 April 2016) * Added 'no check' graphics events that are triggered regardless of game's if checks. - -* 0.39.6 (2016-04-01, [log](https://github.com/Pathoschild/SMAPI/compare/0.39.5...0.39.6)) +* 0.39.6 (01 April 2016) * Added game & SMAPI versions to log. * Fixed conflict in graphics tick events. * Bug fixes. - -* 0.39.5 (2016-03-30, [log](https://github.com/Pathoschild/SMAPI/compare/0.39.4...0.39.5)) -* 0.39.4 (2016-03-29, [log](https://github.com/Pathoschild/SMAPI/compare/0.39.3...0.39.4)) -* 0.39.3 (2016-03-28, [log](https://github.com/Pathoschild/SMAPI/compare/0.39.2...0.39.3)) -* 0.39.2 (2016-03-23, [log](https://github.com/Pathoschild/SMAPI/compare/0.39.1...0.39.2)) -* 0.39.1 (2016-03-23, [log](https://github.com/Pathoschild/SMAPI/compare/0.38.8...0.39.1)) -* 0.38.8 (2016-03-23, [log](https://github.com/Pathoschild/SMAPI/compare/0.38.7...0.38.8)) -* 0.38.7 (2016-03-23, [log](https://github.com/Pathoschild/SMAPI/compare/0.38.6...0.38.7)) -* 0.38.6 (2016-03-22, [log](https://github.com/Pathoschild/SMAPI/compare/0.38.5...0.38.6)) -* 0.38.5 (2016-03-22, [log](https://github.com/Pathoschild/SMAPI/compare/0.38.4...0.38.5)) -* 0.38.4 (2016-03-21, [log](https://github.com/Pathoschild/SMAPI/compare/0.38.3...0.38.4)) -* 0.38.3 (2016-03-21, [log](https://github.com/Pathoschild/SMAPI/compare/0.38.2...0.38.3)) -* 0.38.2 (2016-03-21, [log](https://github.com/Pathoschild/SMAPI/compare/0.38.0...0.38.2)) -* 0.38.0 (2016-03-20, [log](https://github.com/Pathoschild/SMAPI/compare/0.38.1...0.38.0)) -* 0.38.1 (2016-03-20, [log](https://github.com/Pathoschild/SMAPI/compare/0.37.3...0.38.1)) -* 0.37.3 (2016-03-08, [log](https://github.com/Pathoschild/SMAPI/compare/0.37.2...0.37.3)) -* 0.37.2 (2016-03-07, [log](https://github.com/Pathoschild/SMAPI/compare/0.37.1...0.37.2)) -* 0.37.1 (2016-03-06, [log](https://github.com/Pathoschild/SMAPI/compare/0.36...0.37.1)) -* 0.36 (2016-03-04, [log](https://github.com/Pathoschild/SMAPI/compare/0.37...0.36)) -* 0.37 (2016-03-04, [log](https://github.com/Pathoschild/SMAPI/compare/0.35...0.37)) -* 0.35 (2016-03-02, [log](https://github.com/Pathoschild/SMAPI/compare/0.34...0.35)) -* 0.34 (2016-03-02, [log](https://github.com/Pathoschild/SMAPI/compare/0.33...0.34)) -* 0.33 (2016-03-02, [log](https://github.com/Pathoschild/SMAPI/compare/0.32...0.33)) -* 0.32 (2016-03-02, [log](https://github.com/Pathoschild/SMAPI/compare/0.31...0.32)) -* 0.31 (2016-03-02, [log](https://github.com/Pathoschild/SMAPI/compare/0.3...0.31)) -* 0.3 (2016-03-01, [log](https://github.com/Pathoschild/SMAPI/compare/Alpha0.2...0.3)) -* 0.2 (2016-02-29, [log](https://github.com/Pathoschild/SMAPI/compare/Alpha0.1...Alpha0.2) -* 0.1 (2016-02-28) - -[log parser]: https://log.smapi.io +* 0.39.5 (30 March 2016) +* 0.39.4 (29 March 2016) +* 0.39.3 (28 March 2016) +* 0.39.2 (23 March 2016) +* 0.39.1 (23 March 2016) +* 0.38.8 (23 March 2016) +* 0.38.7 (23 March 2016) +* 0.38.6 (22 March 2016) +* 0.38.5 (22 March 2016) +* 0.38.4 (21 March 2016) +* 0.38.3 (21 March 2016) +* 0.38.2 (21 March 2016) +* 0.38.0 (20 March 2016) +* 0.38.1 (20 March 2016) +* 0.37.3 (08 March 2016) +* 0.37.2 (07 March 2016) +* 0.37.1 (06 March 2016) +* 0.36 (04 March 2016) +* 0.37 (04 March 2016) +* 0.35 (02 March 2016) +* 0.34 (02 March 2016) +* 0.33 (02 March 2016) +* 0.32 (02 March 2016) +* 0.31 (02 March 2016) +* 0.3 (01 March 2016) +* 0.2 (29 February 2016) +* 0.1 (28 February 2016) -- cgit From dae12c16e5ecb88e8043272c9d8ffdc88bc351ca Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 29 Dec 2018 18:31:23 -0500 Subject: add IsCurrentLocation event arg to World events --- docs/release-notes.md | 3 ++- src/SMAPI/Events/BuildingListChangedEventArgs.cs | 3 +++ src/SMAPI/Events/DebrisListChangedEventArgs.cs | 3 +++ src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs | 3 +++ src/SMAPI/Events/NpcListChangedEventArgs.cs | 3 +++ src/SMAPI/Events/ObjectListChangedEventArgs.cs | 3 +++ src/SMAPI/Events/TerrainFeatureListChangedEventArgs.cs | 3 +++ 7 files changed, 20 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index a8fa99c3..56c2f50f 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,4 +1,4 @@ -# Release notes +# Release notes ## Upcoming release * For players: * Added `world_clear` console command to remove spawned or placed entities. @@ -9,6 +9,7 @@ * Added new [events](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events): * `GameLoop.OneSecondUpdateTicking` and `OneSecondUpdateTicked`; * `Specialised.LoadStageChanged`. + * Added `e.IsCurrentLocation` event arg to `World` events. * You can now use `helper.Data.Read/WriteSaveData` as soon as the save is loaded (instead of once the world is initialised). * For the web UI: diff --git a/src/SMAPI/Events/BuildingListChangedEventArgs.cs b/src/SMAPI/Events/BuildingListChangedEventArgs.cs index 0237342f..74f37710 100644 --- a/src/SMAPI/Events/BuildingListChangedEventArgs.cs +++ b/src/SMAPI/Events/BuildingListChangedEventArgs.cs @@ -21,6 +21,9 @@ namespace StardewModdingAPI.Events /// The buildings removed from the location. public IEnumerable Removed { get; } + /// Whether this is the location containing the local player. + public bool IsCurrentLocation => object.ReferenceEquals(this.Location, Game1.player?.currentLocation); + /********* ** Public methods diff --git a/src/SMAPI/Events/DebrisListChangedEventArgs.cs b/src/SMAPI/Events/DebrisListChangedEventArgs.cs index 68328885..61b7590a 100644 --- a/src/SMAPI/Events/DebrisListChangedEventArgs.cs +++ b/src/SMAPI/Events/DebrisListChangedEventArgs.cs @@ -20,6 +20,9 @@ namespace StardewModdingAPI.Events /// The debris removed from the location. public IEnumerable Removed { get; } + /// Whether this is the location containing the local player. + public bool IsCurrentLocation => object.ReferenceEquals(this.Location, Game1.player?.currentLocation); + /********* ** Public methods diff --git a/src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs b/src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs index c7d55bf8..59d79f0f 100644 --- a/src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs +++ b/src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs @@ -21,6 +21,9 @@ namespace StardewModdingAPI.Events /// The large terrain features removed from the location. public IEnumerable Removed { get; } + /// Whether this is the location containing the local player. + public bool IsCurrentLocation => object.ReferenceEquals(this.Location, Game1.player?.currentLocation); + /********* ** Public methods diff --git a/src/SMAPI/Events/NpcListChangedEventArgs.cs b/src/SMAPI/Events/NpcListChangedEventArgs.cs index a9ec2a3b..3a37f1e7 100644 --- a/src/SMAPI/Events/NpcListChangedEventArgs.cs +++ b/src/SMAPI/Events/NpcListChangedEventArgs.cs @@ -20,6 +20,9 @@ namespace StardewModdingAPI.Events /// The NPCs removed from the location. public IEnumerable Removed { get; } + /// Whether this is the location containing the local player. + public bool IsCurrentLocation => object.ReferenceEquals(this.Location, Game1.player?.currentLocation); + /********* ** Public methods diff --git a/src/SMAPI/Events/ObjectListChangedEventArgs.cs b/src/SMAPI/Events/ObjectListChangedEventArgs.cs index d0cf9e7b..b21d2867 100644 --- a/src/SMAPI/Events/ObjectListChangedEventArgs.cs +++ b/src/SMAPI/Events/ObjectListChangedEventArgs.cs @@ -22,6 +22,9 @@ namespace StardewModdingAPI.Events /// The objects removed from the location. public IEnumerable> Removed { get; } + /// Whether this is the location containing the local player. + public bool IsCurrentLocation => object.ReferenceEquals(this.Location, Game1.player?.currentLocation); + /********* ** Public methods diff --git a/src/SMAPI/Events/TerrainFeatureListChangedEventArgs.cs b/src/SMAPI/Events/TerrainFeatureListChangedEventArgs.cs index 0992633e..cdf1e6dc 100644 --- a/src/SMAPI/Events/TerrainFeatureListChangedEventArgs.cs +++ b/src/SMAPI/Events/TerrainFeatureListChangedEventArgs.cs @@ -22,6 +22,9 @@ namespace StardewModdingAPI.Events /// The terrain features removed from the location. public IEnumerable> Removed { get; } + /// Whether this is the location containing the local player. + public bool IsCurrentLocation => object.ReferenceEquals(this.Location, Game1.player?.currentLocation); + /********* ** Public methods -- cgit From e77f17d80993d479090d1992c6a750f8041121fb Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 29 Dec 2018 19:03:16 -0500 Subject: increase deprecation levels for the upcoming SMAPI 3.0 --- docs/release-notes.md | 1 + src/SMAPI/Framework/Content/AssetDataForDictionary.cs | 6 +++--- src/SMAPI/Framework/DeprecationManager.cs | 8 +++++++- src/SMAPI/Framework/ModHelpers/ModHelper.cs | 2 +- src/SMAPI/Framework/SCore.cs | 2 +- src/SMAPI/SemanticVersion.cs | 2 +- 6 files changed, 14 insertions(+), 7 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 56c2f50f..12fd563a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -11,6 +11,7 @@ * `Specialised.LoadStageChanged`. * Added `e.IsCurrentLocation` event arg to `World` events. * You can now use `helper.Data.Read/WriteSaveData` as soon as the save is loaded (instead of once the world is initialised). + * Increased deprecation levels from _notice_ to _info_ for APIs removed in the upcoming SMAPI 3.0. * For the web UI: * Reduced mod compatibility list's cache time. diff --git a/src/SMAPI/Framework/Content/AssetDataForDictionary.cs b/src/SMAPI/Framework/Content/AssetDataForDictionary.cs index 9bd70711..fd3edd5d 100644 --- a/src/SMAPI/Framework/Content/AssetDataForDictionary.cs +++ b/src/SMAPI/Framework/Content/AssetDataForDictionary.cs @@ -26,7 +26,7 @@ namespace StardewModdingAPI.Framework.Content [Obsolete("Access " + nameof(AssetData>.Data) + "field directly.")] public void Set(TKey key, TValue value) { - SCore.DeprecationManager.Warn($"AssetDataForDictionary.{nameof(Set)}", "2.10", DeprecationLevel.Notice); + SCore.DeprecationManager.Warn($"AssetDataForDictionary.{nameof(Set)}", "2.10", DeprecationLevel.Info); this.Data[key] = value; } @@ -36,7 +36,7 @@ namespace StardewModdingAPI.Framework.Content [Obsolete("Access " + nameof(AssetData>.Data) + "field directly.")] public void Set(TKey key, Func value) { - SCore.DeprecationManager.Warn($"AssetDataForDictionary.{nameof(Set)}", "2.10", DeprecationLevel.Notice); + SCore.DeprecationManager.Warn($"AssetDataForDictionary.{nameof(Set)}", "2.10", DeprecationLevel.Info); this.Data[key] = value(this.Data[key]); } @@ -45,7 +45,7 @@ namespace StardewModdingAPI.Framework.Content [Obsolete("Access " + nameof(AssetData>.Data) + "field directly.")] public void Set(Func replacer) { - SCore.DeprecationManager.Warn($"AssetDataForDictionary.{nameof(Set)}", "2.10", DeprecationLevel.Notice); + SCore.DeprecationManager.Warn($"AssetDataForDictionary.{nameof(Set)}", "2.10", DeprecationLevel.Info); foreach (var pair in this.Data.ToArray()) this.Data[pair.Key] = replacer(pair.Key, pair.Value); } diff --git a/src/SMAPI/Framework/DeprecationManager.cs b/src/SMAPI/Framework/DeprecationManager.cs index 70f88b5e..76c2f616 100644 --- a/src/SMAPI/Framework/DeprecationManager.cs +++ b/src/SMAPI/Framework/DeprecationManager.cs @@ -38,7 +38,7 @@ namespace StardewModdingAPI.Framework /// Log a deprecation warning for the old-style events. public void WarnForOldEvents() { - this.Warn("legacy events", "2.9", DeprecationLevel.Notice); + this.Warn("legacy events", "2.9", DeprecationLevel.Info); } /// Log a deprecation warning. @@ -71,7 +71,13 @@ namespace StardewModdingAPI.Framework foreach (DeprecationWarning warning in this.QueuedWarnings.OrderBy(p => p.ModName).ThenBy(p => p.NounPhrase)) { // build message +#if SMAPI_3_0_STRICT string message = $"{warning.ModName ?? "An unknown mod"} uses deprecated code ({warning.NounPhrase} is deprecated since SMAPI {warning.Version})."; +#else + string message = warning.NounPhrase == "legacy events" + ? $"{warning.ModName ?? "An unknown mod"} uses deprecated code (legacy events are deprecated since SMAPI {warning.Version})." + : $"{warning.ModName ?? "An unknown mod"} uses deprecated code ({warning.NounPhrase} is deprecated since SMAPI {warning.Version})."; +#endif if (warning.ModName == null) message += $"{Environment.NewLine}{Environment.StackTrace}"; diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index ca872e32..18040a78 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -166,7 +166,7 @@ namespace StardewModdingAPI.Framework.ModHelpers [Obsolete("Use " + nameof(IModHelper) + "." + nameof(IModHelper.ContentPacks) + "." + nameof(IContentPackHelper.CreateTemporary) + " instead")] public IContentPack CreateTransitionalContentPack(string directoryPath, string id, string name, string description, string author, ISemanticVersion version) { - SCore.DeprecationManager.Warn($"{nameof(IModHelper)}.{nameof(IModHelper.CreateTransitionalContentPack)}", "2.5", DeprecationLevel.Notice); + SCore.DeprecationManager.Warn($"{nameof(IModHelper)}.{nameof(IModHelper.CreateTransitionalContentPack)}", "2.5", DeprecationLevel.Info); return this.ContentPacks.CreateTemporary(directoryPath, id, name, description, author, version); } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 6df94ad4..27c0c40b 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -929,7 +929,7 @@ namespace StardewModdingAPI.Framework // add deprecation warning for old version format { if (mod.Manifest?.Version is Toolkit.SemanticVersion version && version.IsLegacyFormat) - SCore.DeprecationManager.Warn(mod.DisplayName, "non-string manifest version", "2.8", DeprecationLevel.Notice); + SCore.DeprecationManager.Warn(mod.DisplayName, "non-string manifest version", "2.8", DeprecationLevel.Info); } #endif diff --git a/src/SMAPI/SemanticVersion.cs b/src/SMAPI/SemanticVersion.cs index 50830d17..e8e5dfa4 100644 --- a/src/SMAPI/SemanticVersion.cs +++ b/src/SMAPI/SemanticVersion.cs @@ -33,7 +33,7 @@ namespace StardewModdingAPI { get { - SCore.DeprecationManager?.Warn($"{nameof(ISemanticVersion)}.{nameof(ISemanticVersion.Build)}", "2.8", DeprecationLevel.Notice); + SCore.DeprecationManager?.Warn($"{nameof(ISemanticVersion)}.{nameof(ISemanticVersion.Build)}", "2.8", DeprecationLevel.Info); return this.Version.PrereleaseTag; } } -- cgit From ca18a2867b457fd6bfda71d9828884032ecadfb8 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 29 Dec 2018 20:09:19 -0500 Subject: update for release --- build/GlobalAssemblyInfo.cs | 4 ++-- docs/release-notes.md | 12 ++++++------ src/SMAPI.Mods.ConsoleCommands/manifest.json | 4 ++-- src/SMAPI.Mods.SaveBackup/manifest.json | 4 ++-- src/SMAPI/Constants.cs | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) (limited to 'docs') diff --git a/build/GlobalAssemblyInfo.cs b/build/GlobalAssemblyInfo.cs index 079d9040..298204dc 100644 --- a/build/GlobalAssemblyInfo.cs +++ b/build/GlobalAssemblyInfo.cs @@ -1,5 +1,5 @@ using System.Reflection; [assembly: AssemblyProduct("SMAPI")] -[assembly: AssemblyVersion("2.9.3")] -[assembly: AssemblyFileVersion("2.9.3")] +[assembly: AssemblyVersion("2.10.0")] +[assembly: AssemblyFileVersion("2.10.0")] diff --git a/docs/release-notes.md b/docs/release-notes.md index 12fd563a..2fd4b42c 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,17 +1,17 @@ # Release notes -## Upcoming release +## 2.10 +Released 29 December 2018 for Stardew Valley 1.3.32. + * For players: * Added `world_clear` console command to remove spawned or placed entities. - * Minor performance improvement. + * Minor performance improvements. * Tweaked installer to reduce antivirus false positives. * For modders: - * Added new [events](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events): - * `GameLoop.OneSecondUpdateTicking` and `OneSecondUpdateTicked`; - * `Specialised.LoadStageChanged`. + * Added [events](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events): `GameLoop.OneSecondUpdateTicking`, `GameLoop.OneSecondUpdateTicked`, and `Specialised.LoadStageChanged`. * Added `e.IsCurrentLocation` event arg to `World` events. * You can now use `helper.Data.Read/WriteSaveData` as soon as the save is loaded (instead of once the world is initialised). - * Increased deprecation levels from _notice_ to _info_ for APIs removed in the upcoming SMAPI 3.0. + * Increased deprecation levels to _info_ for the upcoming SMAPI 3.0. * For the web UI: * Reduced mod compatibility list's cache time. diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index 541d1045..3e7001d5 100644 --- a/src/SMAPI.Mods.ConsoleCommands/manifest.json +++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json @@ -1,9 +1,9 @@ { "Name": "Console Commands", "Author": "SMAPI", - "Version": "2.9.3", + "Version": "2.10.0", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "2.9.3" + "MinimumApiVersion": "2.10.0" } diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index d8dea086..0e2601ad 100644 --- a/src/SMAPI.Mods.SaveBackup/manifest.json +++ b/src/SMAPI.Mods.SaveBackup/manifest.json @@ -1,9 +1,9 @@ { "Name": "Save Backup", "Author": "SMAPI", - "Version": "2.9.3", + "Version": "2.10.0", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "2.9.3" + "MinimumApiVersion": "2.10.0" } diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 021984e0..9ceaf11d 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -29,7 +29,7 @@ namespace StardewModdingAPI ** Public ****/ /// SMAPI's current semantic version. - public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("2.9.3"); + public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("2.10.0"); /// The minimum supported version of Stardew Valley. public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.3.32"); -- cgit