diff options
author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2018-12-29 20:09:33 -0500 |
---|---|---|
committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2018-12-29 20:09:33 -0500 |
commit | f046091fe637963fd6a8cc8c1324daf81b64899f (patch) | |
tree | adeffec4a5d31503548ef5dead7d67b3bff9e694 /src/SMAPI | |
parent | 82beefd8531467de318c1881afd15a258d489f37 (diff) | |
parent | ca18a2867b457fd6bfda71d9828884032ecadfb8 (diff) | |
download | SMAPI-f046091fe637963fd6a8cc8c1324daf81b64899f.tar.gz SMAPI-f046091fe637963fd6a8cc8c1324daf81b64899f.tar.bz2 SMAPI-f046091fe637963fd6a8cc8c1324daf81b64899f.zip |
Merge branch 'develop' into stable
Diffstat (limited to 'src/SMAPI')
117 files changed, 647 insertions, 321 deletions
diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 2d67284e..9ceaf11d 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -13,7 +13,7 @@ namespace StardewModdingAPI public static class Constants { /********* - ** Properties + ** Fields *********/ /// <summary>The directory path containing the current save's data (if a save is loaded).</summary> private static string RawSavePath => Context.IsSaveLoaded ? Path.Combine(Constants.SavesPath, Constants.GetSaveFolderName()) : null; @@ -29,7 +29,7 @@ namespace StardewModdingAPI ** Public ****/ /// <summary>SMAPI's current semantic version.</summary> - public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("2.9.3"); + public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("2.10.0"); /// <summary>The minimum supported version of Stardew Valley.</summary> public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.3.32"); 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 +{ + /// <summary>A low-level stage in the game's loading process.</summary> + public enum LoadStage + { + /// <summary>A save is not loaded or loading.</summary> + None, + + /// <summary>The game is creating a new save slot, and has initialised the basic save info.</summary> + CreatedBasicInfo, + + /// <summary>The game is creating a new save slot, and has initialised the in-game locations.</summary> + CreatedLocations, + + /// <summary>The game is creating a new save slot, and has created the physical save files.</summary> + CreatedSaveFile, + + /// <summary>The game is loading a save slot, and has read the raw save data into <see cref="StardewValley.SaveGame.loaded"/>. Not applicable when connecting to a multiplayer host. This is equivalent to <see cref="StardewValley.SaveGame.getLoadEnumerator"/> value 20.</summary> + SaveParsed, + + /// <summary>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 <see cref="StardewValley.SaveGame.getLoadEnumerator"/> value 36.</summary> + SaveLoadedBasicInfo, + + /// <summary>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 <see cref="StardewValley.SaveGame.getLoadEnumerator"/> value 50.</summary> + SaveLoadedLocations, + + /// <summary>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.</summary> + Preloaded, + + /// <summary>The save is fully loaded, but the world may not be fully initialised yet.</summary> + Loaded, + + /// <summary>The save is fully loaded, the world has been initialised, and <see cref="Context.IsWorldReady"/> is now true.</summary> + Ready + } +} 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 /// <summary>The buildings removed from the location.</summary> public IEnumerable<Building> Removed { get; } + /// <summary>Whether this is the location containing the local player.</summary> + public bool IsCurrentLocation => object.ReferenceEquals(this.Location, Game1.player?.currentLocation); + /********* ** Public methods diff --git a/src/SMAPI/Events/ButtonPressedEventArgs.cs b/src/SMAPI/Events/ButtonPressedEventArgs.cs index 9e6c187f..5d922666 100644 --- a/src/SMAPI/Events/ButtonPressedEventArgs.cs +++ b/src/SMAPI/Events/ButtonPressedEventArgs.cs @@ -7,7 +7,7 @@ namespace StardewModdingAPI.Events public class ButtonPressedEventArgs : EventArgs { /********* - ** Properties + ** Fields *********/ /// <summary>The game's current input state.</summary> private readonly SInputState InputState; diff --git a/src/SMAPI/Events/ButtonReleasedEventArgs.cs b/src/SMAPI/Events/ButtonReleasedEventArgs.cs index 2a289bc7..f5282230 100644 --- a/src/SMAPI/Events/ButtonReleasedEventArgs.cs +++ b/src/SMAPI/Events/ButtonReleasedEventArgs.cs @@ -7,7 +7,7 @@ namespace StardewModdingAPI.Events public class ButtonReleasedEventArgs : EventArgs { /********* - ** Properties + ** Fields *********/ /// <summary>The game's current input state.</summary> private readonly SInputState InputState; diff --git a/src/SMAPI/Events/ContentEvents.cs b/src/SMAPI/Events/ContentEvents.cs index 1a2dd526..aca76ef7 100644 --- a/src/SMAPI/Events/ContentEvents.cs +++ b/src/SMAPI/Events/ContentEvents.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Events public static class ContentEvents { /********* - ** Properties + ** Fields *********/ /// <summary>The core event manager.</summary> private static EventManager EventManager; diff --git a/src/SMAPI/Events/ControlEvents.cs b/src/SMAPI/Events/ControlEvents.cs index be849f95..45aedc9b 100644 --- a/src/SMAPI/Events/ControlEvents.cs +++ b/src/SMAPI/Events/ControlEvents.cs @@ -11,7 +11,7 @@ namespace StardewModdingAPI.Events public static class ControlEvents { /********* - ** Properties + ** Fields *********/ /// <summary>The core event manager.</summary> private static EventManager EventManager; 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 /// <summary>The debris removed from the location.</summary> public IEnumerable<Debris> Removed { get; } + /// <summary>Whether this is the location containing the local player.</summary> + public bool IsCurrentLocation => object.ReferenceEquals(this.Location, Game1.player?.currentLocation); + /********* ** Public methods diff --git a/src/SMAPI/Events/EventArgsInput.cs b/src/SMAPI/Events/EventArgsInput.cs index 837de2f8..5cff3408 100644 --- a/src/SMAPI/Events/EventArgsInput.cs +++ b/src/SMAPI/Events/EventArgsInput.cs @@ -8,7 +8,7 @@ namespace StardewModdingAPI.Events public class EventArgsInput : EventArgs { /********* - ** Properties + ** Fields *********/ /// <summary>The buttons to suppress.</summary> private readonly HashSet<SButton> SuppressButtons; diff --git a/src/SMAPI/Events/GameEvents.cs b/src/SMAPI/Events/GameEvents.cs index 6069a185..9d945277 100644 --- a/src/SMAPI/Events/GameEvents.cs +++ b/src/SMAPI/Events/GameEvents.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Events public static class GameEvents { /********* - ** Properties + ** Fields *********/ /// <summary>The core event manager.</summary> private static EventManager EventManager; diff --git a/src/SMAPI/Events/GraphicsEvents.cs b/src/SMAPI/Events/GraphicsEvents.cs index 88a32c3f..24a16a29 100644 --- a/src/SMAPI/Events/GraphicsEvents.cs +++ b/src/SMAPI/Events/GraphicsEvents.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Events public static class GraphicsEvents { /********* - ** Properties + ** Fields *********/ /// <summary>The core event manager.</summary> private static EventManager EventManager; diff --git a/src/SMAPI/Events/IGameLoopEvents.cs b/src/SMAPI/Events/IGameLoopEvents.cs index e1900f79..6fb56c8b 100644 --- a/src/SMAPI/Events/IGameLoopEvents.cs +++ b/src/SMAPI/Events/IGameLoopEvents.cs @@ -14,6 +14,12 @@ namespace StardewModdingAPI.Events /// <summary>Raised after the game state is updated (≈60 times per second).</summary> event EventHandler<UpdateTickedEventArgs> UpdateTicked; + /// <summary>Raised once per second before the game state is updated.</summary> + event EventHandler<OneSecondUpdateTickingEventArgs> OneSecondUpdateTicking; + + /// <summary>Raised once per second after the game state is updated.</summary> + event EventHandler<OneSecondUpdateTickedEventArgs> OneSecondUpdateTicked; + /// <summary>Raised before the game creates a new save file.</summary> event EventHandler<SaveCreatingEventArgs> SaveCreating; @@ -26,7 +32,7 @@ namespace StardewModdingAPI.Events /// <summary>Raised after the game finishes writing data to the save file (except the initial save creation).</summary> event EventHandler<SavedEventArgs> Saved; - /// <summary>Raised after the player loads a save slot.</summary> + /// <summary>Raised after the player loads a save slot and the world is initialised.</summary> event EventHandler<SaveLoadedEventArgs> SaveLoaded; /// <summary>Raised after the game begins a new day (including when the player loads a save).</summary> diff --git a/src/SMAPI/Events/ISpecialisedEvents.cs b/src/SMAPI/Events/ISpecialisedEvents.cs index 928cd05d..ecb109e6 100644 --- a/src/SMAPI/Events/ISpecialisedEvents.cs +++ b/src/SMAPI/Events/ISpecialisedEvents.cs @@ -5,6 +5,9 @@ namespace StardewModdingAPI.Events /// <summary>Events serving specialised edge cases that shouldn't be used by most mods.</summary> public interface ISpecialisedEvents { + /// <summary>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 <see cref="IGameLoopEvents"/> instead.</summary> + event EventHandler<LoadStageChangedEventArgs> LoadStageChanged; + /// <summary>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.</summary> event EventHandler<UnvalidatedUpdateTickingEventArgs> UnvalidatedUpdateTicking; diff --git a/src/SMAPI/Events/InputEvents.cs b/src/SMAPI/Events/InputEvents.cs index 900e53ea..c5ab8c83 100644 --- a/src/SMAPI/Events/InputEvents.cs +++ b/src/SMAPI/Events/InputEvents.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Events public static class InputEvents { /********* - ** Properties + ** Fields *********/ /// <summary>The core event manager.</summary> private static EventManager EventManager; 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 /// <summary>The large terrain features removed from the location.</summary> public IEnumerable<LargeTerrainFeature> Removed { get; } + /// <summary>Whether this is the location containing the local player.</summary> + public bool IsCurrentLocation => object.ReferenceEquals(this.Location, Game1.player?.currentLocation); + /********* ** Public methods 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 +{ + /// <summary>Event arguments for an <see cref="ISpecialisedEvents.LoadStageChanged"/> event.</summary> + public class LoadStageChangedEventArgs : EventArgs + { + /********* + ** Accessors + *********/ + /// <summary>The previous load stage.</summary> + public LoadStage OldStage { get; } + + /// <summary>The new load stage.</summary> + public LoadStage NewStage { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="old">The previous load stage.</param> + /// <param name="current">The new load stage.</param> + public LoadStageChangedEventArgs(LoadStage old, LoadStage current) + { + this.OldStage = old; + this.NewStage = current; + } + } +} diff --git a/src/SMAPI/Events/LocationEvents.cs b/src/SMAPI/Events/LocationEvents.cs index 5eb228b7..0761bdd8 100644 --- a/src/SMAPI/Events/LocationEvents.cs +++ b/src/SMAPI/Events/LocationEvents.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Events public static class LocationEvents { /********* - ** Properties + ** Fields *********/ /// <summary>The core event manager.</summary> private static EventManager EventManager; diff --git a/src/SMAPI/Events/MenuEvents.cs b/src/SMAPI/Events/MenuEvents.cs index 914948dd..8647c268 100644 --- a/src/SMAPI/Events/MenuEvents.cs +++ b/src/SMAPI/Events/MenuEvents.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Events public static class MenuEvents { /********* - ** Properties + ** Fields *********/ /// <summary>The core event manager.</summary> private static EventManager EventManager; diff --git a/src/SMAPI/Events/MineEvents.cs b/src/SMAPI/Events/MineEvents.cs index fd35237e..929da35b 100644 --- a/src/SMAPI/Events/MineEvents.cs +++ b/src/SMAPI/Events/MineEvents.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Events public static class MineEvents { /********* - ** Properties + ** Fields *********/ /// <summary>The core event manager.</summary> private static EventManager EventManager; diff --git a/src/SMAPI/Events/ModMessageReceivedEventArgs.cs b/src/SMAPI/Events/ModMessageReceivedEventArgs.cs index 49366ec6..d4370028 100644 --- a/src/SMAPI/Events/ModMessageReceivedEventArgs.cs +++ b/src/SMAPI/Events/ModMessageReceivedEventArgs.cs @@ -7,7 +7,7 @@ namespace StardewModdingAPI.Events public class ModMessageReceivedEventArgs : EventArgs { /********* - ** Properties + ** Fields *********/ /// <summary>The underlying message model.</summary> private readonly ModMessageModel Message; diff --git a/src/SMAPI/Events/MultiplayerEvents.cs b/src/SMAPI/Events/MultiplayerEvents.cs index 5e6a22dc..0650a8e2 100644 --- a/src/SMAPI/Events/MultiplayerEvents.cs +++ b/src/SMAPI/Events/MultiplayerEvents.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Events public static class MultiplayerEvents { /********* - ** Properties + ** Fields *********/ /// <summary>The core event manager.</summary> private static EventManager EventManager; 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 /// <summary>The NPCs removed from the location.</summary> public IEnumerable<NPC> Removed { get; } + /// <summary>Whether this is the location containing the local player.</summary> + 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 /// <summary>The objects removed from the location.</summary> public IEnumerable<KeyValuePair<Vector2, Object>> Removed { get; } + /// <summary>Whether this is the location containing the local player.</summary> + public bool IsCurrentLocation => object.ReferenceEquals(this.Location, Game1.player?.currentLocation); + /********* ** Public methods diff --git a/src/SMAPI/Events/OneSecondUpdateTickedEventArgs.cs b/src/SMAPI/Events/OneSecondUpdateTickedEventArgs.cs new file mode 100644 index 00000000..dadbb71a --- /dev/null +++ b/src/SMAPI/Events/OneSecondUpdateTickedEventArgs.cs @@ -0,0 +1,26 @@ +using System; +using StardewValley; + +namespace StardewModdingAPI.Events +{ + /// <summary>Event arguments for an <see cref="IGameLoopEvents.OneSecondUpdateTicked"/> event.</summary> + public class OneSecondUpdateTickedEventArgs : EventArgs + { + /********* + ** Accessors + *********/ + /// <summary>The number of ticks elapsed since the game started, including the current tick.</summary> + public uint Ticks => (uint)Game1.ticks; + + + /********* + ** Public methods + *********/ + /// <summary>Get whether <see cref="Ticks"/> is a multiple of the given <paramref name="number"/>. This is mainly useful if you want to run logic intermittently (e.g. <code>e.IsMultipleOf(30)</code> for every half-second).</summary> + /// <param name="number">The factor to check.</param> + 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..e9bb46c6 --- /dev/null +++ b/src/SMAPI/Events/OneSecondUpdateTickingEventArgs.cs @@ -0,0 +1,26 @@ +using System; +using StardewValley; + +namespace StardewModdingAPI.Events +{ + /// <summary>Event arguments for an <see cref="IGameLoopEvents.OneSecondUpdateTicking"/> event.</summary> + public class OneSecondUpdateTickingEventArgs : EventArgs + { + /********* + ** Accessors + *********/ + /// <summary>The number of ticks elapsed since the game started, including the current tick.</summary> + public uint Ticks => (uint)Game1.ticks; + + + /********* + ** Public methods + *********/ + /// <summary>Get whether <see cref="Ticks"/> is a multiple of the given <paramref name="number"/>. This is mainly useful if you want to run logic intermittently (e.g. <code>e.IsMultipleOf(30)</code> for every half-second).</summary> + /// <param name="number">The factor to check.</param> + public bool IsMultipleOf(uint number) + { + return this.Ticks % number == 0; + } + } +} diff --git a/src/SMAPI/Events/PlayerEvents.cs b/src/SMAPI/Events/PlayerEvents.cs index 98bc3da5..11ba1e54 100644 --- a/src/SMAPI/Events/PlayerEvents.cs +++ b/src/SMAPI/Events/PlayerEvents.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Events public static class PlayerEvents { /********* - ** Properties + ** Fields *********/ /// <summary>The core event manager.</summary> private static EventManager EventManager; diff --git a/src/SMAPI/Events/SaveEvents.cs b/src/SMAPI/Events/SaveEvents.cs index 959fb5d2..da276d22 100644 --- a/src/SMAPI/Events/SaveEvents.cs +++ b/src/SMAPI/Events/SaveEvents.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Events public static class SaveEvents { /********* - ** Properties + ** Fields *********/ /// <summary>The core event manager.</summary> private static EventManager EventManager; diff --git a/src/SMAPI/Events/SpecialisedEvents.cs b/src/SMAPI/Events/SpecialisedEvents.cs index 2c6d0230..4f16e4da 100644 --- a/src/SMAPI/Events/SpecialisedEvents.cs +++ b/src/SMAPI/Events/SpecialisedEvents.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Events public static class SpecialisedEvents { /********* - ** Properties + ** Fields *********/ /// <summary>The core event manager.</summary> private static EventManager EventManager; 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 /// <summary>The terrain features removed from the location.</summary> public IEnumerable<KeyValuePair<Vector2, TerrainFeature>> Removed { get; } + /// <summary>Whether this is the location containing the local player.</summary> + public bool IsCurrentLocation => object.ReferenceEquals(this.Location, Game1.player?.currentLocation); + /********* ** Public methods diff --git a/src/SMAPI/Events/TimeEvents.cs b/src/SMAPI/Events/TimeEvents.cs index 40892491..389532d9 100644 --- a/src/SMAPI/Events/TimeEvents.cs +++ b/src/SMAPI/Events/TimeEvents.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Events public static class TimeEvents { /********* - ** Properties + ** Fields *********/ /// <summary>The core event manager.</summary> private static EventManager EventManager; 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 *********/ /// <summary>The number of ticks elapsed since the game started, including the current tick.</summary> - public uint Ticks { get; } + public uint Ticks => (uint)Game1.ticks; /// <summary>Whether <see cref="Ticks"/> is a multiple of 60, which happens approximately once per second.</summary> - public bool IsOneSecond { get; } + public bool IsOneSecond => Game1.ticks % 60 == 0; /********* ** Public methods *********/ - /// <summary>Construct an instance.</summary> - /// <param name="ticks">The number of ticks elapsed since the game started, including the current tick.</param> - internal UnvalidatedUpdateTickedEventArgs(uint ticks) - { - this.Ticks = ticks; - this.IsOneSecond = this.IsMultipleOf(60); - } - /// <summary>Get whether <see cref="Ticks"/> is a multiple of the given <paramref name="number"/>. This is mainly useful if you want to run logic intermittently (e.g. <code>e.IsMultipleOf(30)</code> for every half-second).</summary> /// <param name="number">The factor to check.</param> 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 *********/ /// <summary>The number of ticks elapsed since the game started, including the current tick.</summary> - public uint Ticks { get; } + public uint Ticks => (uint)Game1.ticks; /// <summary>Whether <see cref="Ticks"/> is a multiple of 60, which happens approximately once per second.</summary> - public bool IsOneSecond { get; } + public bool IsOneSecond => Game1.ticks % 60 == 0; /********* ** Public methods *********/ - /// <summary>Construct an instance.</summary> - /// <param name="ticks">The number of ticks elapsed since the game started, including the current tick.</param> - internal UnvalidatedUpdateTickingEventArgs(uint ticks) - { - this.Ticks = ticks; - this.IsOneSecond = this.IsMultipleOf(60); - } - /// <summary>Get whether <see cref="Ticks"/> is a multiple of the given <paramref name="number"/>. This is mainly useful if you want to run logic intermittently (e.g. <code>e.IsMultipleOf(30)</code> for every half-second).</summary> /// <param name="number">The factor to check.</param> 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 *********/ /// <summary>The number of ticks elapsed since the game started, including the current tick.</summary> - public uint Ticks { get; } + public uint Ticks => (uint)Game1.ticks; /// <summary>Whether <see cref="Ticks"/> is a multiple of 60, which happens approximately once per second.</summary> - public bool IsOneSecond { get; } + public bool IsOneSecond => Game1.ticks % 60 == 0; /********* ** Public methods *********/ - /// <summary>Construct an instance.</summary> - /// <param name="ticks">The number of ticks elapsed since the game started, including the current tick.</param> - internal UpdateTickedEventArgs(uint ticks) - { - this.Ticks = ticks; - this.IsOneSecond = this.IsMultipleOf(60); - } - /// <summary>Get whether <see cref="Ticks"/> is a multiple of the given <paramref name="number"/>. This is mainly useful if you want to run logic intermittently (e.g. <code>e.IsMultipleOf(30)</code> for every half-second).</summary> /// <param name="number">The factor to check.</param> 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 *********/ /// <summary>The number of ticks elapsed since the game started, including the current tick.</summary> - public uint Ticks { get; } + public uint Ticks => (uint)Game1.ticks; /// <summary>Whether <see cref="Ticks"/> is a multiple of 60, which happens approximately once per second.</summary> - public bool IsOneSecond { get; } + public bool IsOneSecond => Game1.ticks % 60 == 0; /********* ** Public methods *********/ - /// <summary>Construct an instance.</summary> - /// <param name="ticks">The number of ticks elapsed since the game started, including the current tick.</param> - internal UpdateTickingEventArgs(uint ticks) - { - this.Ticks = ticks; - this.IsOneSecond = this.IsMultipleOf(60); - } - /// <summary>Get whether <see cref="Ticks"/> is a multiple of the given <paramref name="number"/>. This is mainly useful if you want to run logic intermittently (e.g. <code>e.IsMultipleOf(30)</code> for every half-second).</summary> /// <param name="number">The factor to check.</param> public bool IsMultipleOf(uint number) diff --git a/src/SMAPI/Framework/CommandManager.cs b/src/SMAPI/Framework/CommandManager.cs index aabe99c3..fdaafff1 100644 --- a/src/SMAPI/Framework/CommandManager.cs +++ b/src/SMAPI/Framework/CommandManager.cs @@ -9,7 +9,7 @@ namespace StardewModdingAPI.Framework internal class CommandManager { /********* - ** Properties + ** Fields *********/ /// <summary>The commands registered with SMAPI.</summary> private readonly IDictionary<string, Command> Commands = new Dictionary<string, Command>(StringComparer.InvariantCultureIgnoreCase); diff --git a/src/SMAPI/Framework/Content/AssetData.cs b/src/SMAPI/Framework/Content/AssetData.cs index 0a86f54d..553404d3 100644 --- a/src/SMAPI/Framework/Content/AssetData.cs +++ b/src/SMAPI/Framework/Content/AssetData.cs @@ -7,7 +7,7 @@ namespace StardewModdingAPI.Framework.Content internal class AssetData<TValue> : AssetInfo, IAssetData<TValue> { /********* - ** Properties + ** Fields *********/ /// <summary>A callback to invoke when the data is replaced (if any).</summary> private readonly Action<TValue> OnDataReplaced; 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<IDictionary<TKey, TValue>>.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<IDictionary<TKey, TValue>>.Data) + "field directly.")] public void Set(TKey key, Func<TValue, 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(this.Data[key]); } @@ -45,7 +45,7 @@ namespace StardewModdingAPI.Framework.Content [Obsolete("Access " + nameof(AssetData<IDictionary<TKey, TValue>>.Data) + "field directly.")] public void Set(Func<TKey, TValue, TValue> 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/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index f970762a..f2d21b5e 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -8,7 +8,7 @@ namespace StardewModdingAPI.Framework.Content internal class AssetDataForImage : AssetData<Texture2D>, IAssetDataForImage { /********* - ** Properties + ** Fields *********/ /// <summary>The minimum value to consider non-transparent.</summary> /// <remarks>On Linux/Mac, fully transparent pixels may have an alpha up to 4 for some reason.</remarks> diff --git a/src/SMAPI/Framework/Content/AssetInfo.cs b/src/SMAPI/Framework/Content/AssetInfo.cs index d580dc06..e5211290 100644 --- a/src/SMAPI/Framework/Content/AssetInfo.cs +++ b/src/SMAPI/Framework/Content/AssetInfo.cs @@ -7,7 +7,7 @@ namespace StardewModdingAPI.Framework.Content internal class AssetInfo : IAssetInfo { /********* - ** Properties + ** Fields *********/ /// <summary>Normalises an asset key to match the cache key.</summary> protected readonly Func<string, string> GetNormalisedPath; diff --git a/src/SMAPI/Framework/Content/ContentCache.cs b/src/SMAPI/Framework/Content/ContentCache.cs index a5dfac9d..55a96ed2 100644 --- a/src/SMAPI/Framework/Content/ContentCache.cs +++ b/src/SMAPI/Framework/Content/ContentCache.cs @@ -14,7 +14,7 @@ namespace StardewModdingAPI.Framework.Content internal class ContentCache { /********* - ** Properties + ** Fields *********/ /// <summary>The underlying asset cache.</summary> private readonly IDictionary<string, object> Cache; diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 7e256939..4dd1b6e1 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -19,7 +19,7 @@ namespace StardewModdingAPI.Framework internal class ContentCoordinator : IDisposable { /********* - ** Properties + ** Fields *********/ /// <summary>An asset key prefix for assets from SMAPI mod folders.</summary> private readonly string ManagedPrefix = "SMAPI"; diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index 724a6e1c..7821e454 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -18,7 +18,7 @@ namespace StardewModdingAPI.Framework.ContentManagers internal abstract class BaseContentManager : LocalizedContentManager, IContentManager { /********* - ** Properties + ** Fields *********/ /// <summary>The central coordinator which manages content managers.</summary> protected readonly ContentCoordinator Coordinator; diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index 81732d3f..ee940cc7 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -14,7 +14,7 @@ namespace StardewModdingAPI.Framework.ContentManagers internal class GameContentManager : BaseContentManager { /********* - ** Properties + ** Fields *********/ /// <summary>The assets currently being intercepted by <see cref="IAssetLoader"/> instances. This is used to prevent infinite loops when a loader loads a new asset.</summary> private readonly ContextHash<string> AssetsBeingLoaded = new ContextHash<string>(); diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index ed76a925..6485b3d4 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -14,7 +14,7 @@ namespace StardewModdingAPI.Framework.ContentManagers internal class ModContentManager : BaseContentManager { /********* - ** Properties + ** Fields *********/ /// <summary>Encapsulates SMAPI's JSON file parsing.</summary> private readonly JsonHelper JsonHelper; diff --git a/src/SMAPI/Framework/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs index 49285388..e39d03a1 100644 --- a/src/SMAPI/Framework/ContentPack.cs +++ b/src/SMAPI/Framework/ContentPack.cs @@ -12,7 +12,7 @@ namespace StardewModdingAPI.Framework internal class ContentPack : IContentPack { /********* - ** Properties + ** Fields *********/ /// <summary>Provides an API for loading content assets.</summary> private readonly IContentHelper Content; diff --git a/src/SMAPI/Framework/DeprecationManager.cs b/src/SMAPI/Framework/DeprecationManager.cs index be564c22..76c2f616 100644 --- a/src/SMAPI/Framework/DeprecationManager.cs +++ b/src/SMAPI/Framework/DeprecationManager.cs @@ -8,7 +8,7 @@ namespace StardewModdingAPI.Framework internal class DeprecationManager { /********* - ** Properties + ** Fields *********/ /// <summary>The deprecations which have already been logged (as 'mod name::noun phrase::version').</summary> private readonly HashSet<string> LoggedDeprecations = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase); @@ -38,7 +38,7 @@ namespace StardewModdingAPI.Framework /// <summary>Log a deprecation warning for the old-style events.</summary> public void WarnForOldEvents() { - this.Warn("legacy events", "2.9", DeprecationLevel.Notice); + this.Warn("legacy events", "2.9", DeprecationLevel.Info); } /// <summary>Log a deprecation warning.</summary> @@ -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/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs index 0ad85adf..13244601 100644 --- a/src/SMAPI/Framework/Events/EventManager.cs +++ b/src/SMAPI/Framework/Events/EventManager.cs @@ -58,6 +58,12 @@ namespace StardewModdingAPI.Framework.Events /// <summary>Raised after the game performs its overall update tick (≈60 times per second).</summary> public readonly ManagedEvent<UpdateTickedEventArgs> UpdateTicked; + /// <summary>Raised once per second before the game performs its overall update tick.</summary> + public readonly ManagedEvent<OneSecondUpdateTickingEventArgs> OneSecondUpdateTicking; + + /// <summary>Raised once per second after the game performs its overall update tick.</summary> + public readonly ManagedEvent<OneSecondUpdateTickedEventArgs> OneSecondUpdateTicked; + /// <summary>Raised before the game creates the save file.</summary> public readonly ManagedEvent<SaveCreatingEventArgs> SaveCreating; @@ -70,7 +76,7 @@ namespace StardewModdingAPI.Framework.Events /// <summary>Raised after the game finishes writing data to the save file (except the initial save creation).</summary> public readonly ManagedEvent<SavedEventArgs> Saved; - /// <summary>Raised after the player loads a save slot.</summary> + /// <summary>Raised after the player loads a save slot and the world is initialised.</summary> public readonly ManagedEvent<SaveLoadedEventArgs> SaveLoaded; /// <summary>Raised after the game begins a new day, including when loading a save.</summary> @@ -151,6 +157,9 @@ namespace StardewModdingAPI.Framework.Events /**** ** Specialised ****/ + /// <summary>Raised when the low-level stage in the game's loading process has changed. See notes on <see cref="ISpecialisedEvents.LoadStageChanged"/>.</summary> + public readonly ManagedEvent<LoadStageChangedEventArgs> LoadStageChanged; + /// <summary>Raised before the game performs its overall update tick (≈60 times per second). See notes on <see cref="ISpecialisedEvents.UnvalidatedUpdateTicking"/>.</summary> public readonly ManagedEvent<UnvalidatedUpdateTickingEventArgs> UnvalidatedUpdateTicking; @@ -377,6 +386,8 @@ namespace StardewModdingAPI.Framework.Events this.GameLaunched = ManageEventOf<GameLaunchedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.GameLaunched)); this.UpdateTicking = ManageEventOf<UpdateTickingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.UpdateTicking)); this.UpdateTicked = ManageEventOf<UpdateTickedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.UpdateTicked)); + this.OneSecondUpdateTicking = ManageEventOf<OneSecondUpdateTickingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.OneSecondUpdateTicking)); + this.OneSecondUpdateTicked = ManageEventOf<OneSecondUpdateTickedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.OneSecondUpdateTicked)); this.SaveCreating = ManageEventOf<SaveCreatingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.SaveCreating)); this.SaveCreated = ManageEventOf<SaveCreatedEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.SaveCreated)); this.Saving = ManageEventOf<SavingEventArgs>(nameof(IModEvents.GameLoop), nameof(IGameLoopEvents.Saving)); @@ -408,6 +419,7 @@ namespace StardewModdingAPI.Framework.Events this.ObjectListChanged = ManageEventOf<ObjectListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.ObjectListChanged)); this.TerrainFeatureListChanged = ManageEventOf<TerrainFeatureListChangedEventArgs>(nameof(IModEvents.World), nameof(IWorldEvents.TerrainFeatureListChanged)); + this.LoadStageChanged = ManageEventOf<LoadStageChangedEventArgs>(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.LoadStageChanged)); this.UnvalidatedUpdateTicking = ManageEventOf<UnvalidatedUpdateTickingEventArgs>(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.UnvalidatedUpdateTicking)); this.UnvalidatedUpdateTicked = ManageEventOf<UnvalidatedUpdateTickedEventArgs>(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.UnvalidatedUpdateTicked)); diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs index 65f6e38e..f9e7f6ec 100644 --- a/src/SMAPI/Framework/Events/ManagedEvent.cs +++ b/src/SMAPI/Framework/Events/ManagedEvent.cs @@ -8,7 +8,7 @@ namespace StardewModdingAPI.Framework.Events internal class ManagedEvent<TEventArgs> : ManagedEventBase<EventHandler<TEventArgs>> { /********* - ** Properties + ** Fields *********/ /// <summary>The underlying event.</summary> private event EventHandler<TEventArgs> Event; @@ -93,11 +93,12 @@ namespace StardewModdingAPI.Framework.Events } } +#if !SMAPI_3_0_STRICT /// <summary>An event wrapper which intercepts and logs errors in handler code.</summary> internal class ManagedEvent : ManagedEventBase<EventHandler> { /********* - ** Properties + ** Fields *********/ /// <summary>The underlying event.</summary> private event EventHandler Event; @@ -156,4 +157,5 @@ namespace StardewModdingAPI.Framework.Events } } } +#endif } diff --git a/src/SMAPI/Framework/Events/ManagedEventBase.cs b/src/SMAPI/Framework/Events/ManagedEventBase.cs index defd903a..c8c3516b 100644 --- a/src/SMAPI/Framework/Events/ManagedEventBase.cs +++ b/src/SMAPI/Framework/Events/ManagedEventBase.cs @@ -8,7 +8,7 @@ namespace StardewModdingAPI.Framework.Events internal abstract class ManagedEventBase<TEventHandler> { /********* - ** Properties + ** Fields *********/ /// <summary>A human-readable name for the event.</summary> private readonly string EventName; diff --git a/src/SMAPI/Framework/Events/ModEventsBase.cs b/src/SMAPI/Framework/Events/ModEventsBase.cs index 545c58a8..77708fc1 100644 --- a/src/SMAPI/Framework/Events/ModEventsBase.cs +++ b/src/SMAPI/Framework/Events/ModEventsBase.cs @@ -4,7 +4,7 @@ namespace StardewModdingAPI.Framework.Events internal abstract class ModEventsBase { /********* - ** Properties + ** Fields *********/ /// <summary>The underlying event manager.</summary> protected readonly EventManager EventManager; diff --git a/src/SMAPI/Framework/Events/ModGameLoopEvents.cs b/src/SMAPI/Framework/Events/ModGameLoopEvents.cs index a5beac99..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); } + /// <summary>Raised once per second before the game state is updated.</summary> + public event EventHandler<OneSecondUpdateTickingEventArgs> OneSecondUpdateTicking + { + add => this.EventManager.OneSecondUpdateTicking.Add(value); + remove => this.EventManager.OneSecondUpdateTicking.Remove(value); + } + + /// <summary>Raised once per second after the game state is updated.</summary> + public event EventHandler<OneSecondUpdateTickedEventArgs> OneSecondUpdateTicked + { + add => this.EventManager.OneSecondUpdateTicked.Add(value); + remove => this.EventManager.OneSecondUpdateTicked.Remove(value); + } + /// <summary>Raised before the game creates a new save file.</summary> public event EventHandler<SaveCreatingEventArgs> SaveCreating { @@ -58,7 +72,7 @@ namespace StardewModdingAPI.Framework.Events remove => this.EventManager.Saved.Remove(value); } - /// <summary>Raised after the player loads a save slot.</summary> + /// <summary>Raised after the player loads a save slot and the world is initialised.</summary> public event EventHandler<SaveLoadedEventArgs> 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..7c3e9dee 100644 --- a/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs +++ b/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs @@ -9,6 +9,13 @@ namespace StardewModdingAPI.Framework.Events /********* ** Accessors *********/ + /// <summary>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 <see cref="IGameLoopEvents"/> instead.</summary> + public event EventHandler<LoadStageChangedEventArgs> LoadStageChanged + { + add => this.EventManager.LoadStageChanged.Add(value); + remove => this.EventManager.LoadStageChanged.Remove(value); + } + /// <summary>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.</summary> public event EventHandler<UnvalidatedUpdateTickingEventArgs> UnvalidatedUpdateTicking { diff --git a/src/SMAPI/Framework/Input/GamePadStateBuilder.cs b/src/SMAPI/Framework/Input/GamePadStateBuilder.cs index 33557385..a20e1248 100644 --- a/src/SMAPI/Framework/Input/GamePadStateBuilder.cs +++ b/src/SMAPI/Framework/Input/GamePadStateBuilder.cs @@ -8,7 +8,7 @@ namespace StardewModdingAPI.Framework.Input internal class GamePadStateBuilder { /********* - ** Properties + ** Fields *********/ /// <summary>The current button states.</summary> private readonly IDictionary<SButton, ButtonState> ButtonStates; diff --git a/src/SMAPI/Framework/Logging/ConsoleInterceptionManager.cs b/src/SMAPI/Framework/Logging/ConsoleInterceptionManager.cs index c04bcd1a..ef42e536 100644 --- a/src/SMAPI/Framework/Logging/ConsoleInterceptionManager.cs +++ b/src/SMAPI/Framework/Logging/ConsoleInterceptionManager.cs @@ -6,7 +6,7 @@ namespace StardewModdingAPI.Framework.Logging internal class ConsoleInterceptionManager : IDisposable { /********* - ** Properties + ** Fields *********/ /// <summary>The intercepting console writer.</summary> private readonly InterceptingTextWriter Output; diff --git a/src/SMAPI/Framework/Logging/LogFileManager.cs b/src/SMAPI/Framework/Logging/LogFileManager.cs index 8cfe0527..6b5babcd 100644 --- a/src/SMAPI/Framework/Logging/LogFileManager.cs +++ b/src/SMAPI/Framework/Logging/LogFileManager.cs @@ -7,7 +7,7 @@ namespace StardewModdingAPI.Framework.Logging internal class LogFileManager : IDisposable { /********* - ** Properties + ** Fields *********/ /// <summary>The underlying stream writer.</summary> private readonly StreamWriter Stream; diff --git a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs index a4fd21c1..e9d53d84 100644 --- a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs @@ -6,7 +6,7 @@ namespace StardewModdingAPI.Framework.ModHelpers internal class CommandHelper : BaseHelper, ICommandHelper { /********* - ** Properties + ** Fields *********/ /// <summary>The mod using this instance.</summary> private readonly IModMetadata Mod; diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index a8b24a13..dac627ba 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -21,7 +21,7 @@ namespace StardewModdingAPI.Framework.ModHelpers internal class ContentHelper : BaseHelper, IContentHelper { /********* - ** Properties + ** Fields *********/ /// <summary>SMAPI's core content logic.</summary> private readonly ContentCoordinator ContentCore; diff --git a/src/SMAPI/Framework/ModHelpers/ContentPackHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentPackHelper.cs index 26c4648c..34f24d65 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentPackHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentPackHelper.cs @@ -9,7 +9,7 @@ namespace StardewModdingAPI.Framework.ModHelpers internal class ContentPackHelper : BaseHelper, IContentPackHelper { /********* - ** Properties + ** Fields *********/ /// <summary>The content packs loaded for this mod.</summary> private readonly Lazy<IContentPack[]> ContentPacks; diff --git a/src/SMAPI/Framework/ModHelpers/DataHelper.cs b/src/SMAPI/Framework/ModHelpers/DataHelper.cs index e5100aed..2cb886ba 100644 --- a/src/SMAPI/Framework/ModHelpers/DataHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/DataHelper.cs @@ -11,7 +11,7 @@ namespace StardewModdingAPI.Framework.ModHelpers internal class DataHelper : BaseHelper, IDataHelper { /********* - ** Properties + ** Fields *********/ /// <summary>Encapsulates SMAPI's JSON file parsing.</summary> private readonly JsonHelper JsonHelper; @@ -77,9 +77,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <exception cref="InvalidOperationException">The player hasn't loaded a save file yet or isn't the main player.</exception> public TModel ReadSaveData<TModel>(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 /// <exception cref="InvalidOperationException">The player hasn't loaded a save file yet or isn't the main player.</exception> public void WriteSaveData<TModel>(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); 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/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 5cc2a20f..8330e078 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -8,7 +8,7 @@ namespace StardewModdingAPI.Framework.ModHelpers internal class ModRegistryHelper : BaseHelper, IModRegistry { /********* - ** Properties + ** Fields *********/ /// <summary>The underlying mod registry.</summary> private readonly ModRegistry Registry; diff --git a/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs b/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs index eedad0bc..c62dd121 100644 --- a/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs @@ -9,7 +9,7 @@ namespace StardewModdingAPI.Framework.ModHelpers internal class MultiplayerHelper : BaseHelper, IMultiplayerHelper { /********* - ** Properties + ** Fields *********/ /// <summary>SMAPI's core multiplayer utility.</summary> private readonly SMultiplayer Multiplayer; diff --git a/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs b/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs index cfe2ddbe..0ce72a9e 100644 --- a/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs @@ -9,7 +9,7 @@ namespace StardewModdingAPI.Framework.ModHelpers internal class ReflectionHelper : BaseHelper, IReflectionHelper { /********* - ** Properties + ** Fields *********/ /// <summary>The underlying reflection helper.</summary> private readonly Reflector Reflector; diff --git a/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs b/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs index bbe3a81a..3252e047 100644 --- a/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs @@ -9,7 +9,7 @@ namespace StardewModdingAPI.Framework.ModHelpers internal class TranslationHelper : BaseHelper, ITranslationHelper { /********* - ** Properties + ** Fields *********/ /// <summary>The name of the relevant mod for error messages.</summary> private readonly string ModName; diff --git a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs index 91c9e192..aefb0126 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs @@ -7,7 +7,7 @@ namespace StardewModdingAPI.Framework.ModLoading internal class AssemblyDefinitionResolver : DefaultAssemblyResolver { /********* - ** Properties + ** Fields *********/ /// <summary>The known assemblies.</summary> private readonly IDictionary<string, AssemblyDefinition> Lookup = new Dictionary<string, AssemblyDefinition>(); diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index 7292cf3f..5e0571a0 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -15,7 +15,7 @@ namespace StardewModdingAPI.Framework.ModLoading internal class AssemblyLoader : IDisposable { /********* - ** Properties + ** Fields *********/ /// <summary>Encapsulates monitoring and logging.</summary> private readonly IMonitor Monitor; diff --git a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs index e4beb7a9..898bafb4 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs @@ -7,7 +7,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders internal class EventFinder : IInstructionHandler { /********* - ** Properties + ** Fields *********/ /// <summary>The full type name for which to find references.</summary> private readonly string FullTypeName; diff --git a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs index 00805815..606ca8b7 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs @@ -7,7 +7,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders internal class FieldFinder : IInstructionHandler { /********* - ** Properties + ** Fields *********/ /// <summary>The full type name for which to find references.</summary> private readonly string FullTypeName; diff --git a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs index 5358f181..9ca246ff 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs @@ -7,7 +7,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders internal class MethodFinder : IInstructionHandler { /********* - ** Properties + ** Fields *********/ /// <summary>The full type name for which to find references.</summary> private readonly string FullTypeName; diff --git a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs index e54c86cf..0677aa88 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs @@ -7,7 +7,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders internal class PropertyFinder : IInstructionHandler { /********* - ** Properties + ** Fields *********/ /// <summary>The full type name for which to find references.</summary> private readonly string FullTypeName; diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs index 3a26660f..82c4920a 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders internal class ReferenceToMemberWithUnexpectedTypeFinder : IInstructionHandler { /********* - ** Properties + ** Fields *********/ /// <summary>The assembly names to which to heuristically detect broken references.</summary> private readonly HashSet<string> ValidateReferencesToAssemblies; diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs index b95dd79c..44b531a5 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders internal class ReferenceToMissingMemberFinder : IInstructionHandler { /********* - ** Properties + ** Fields *********/ /// <summary>The assembly names to which to heuristically detect broken references.</summary> private readonly HashSet<string> ValidateReferencesToAssemblies; diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index ace84054..835b0a54 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -400,7 +400,7 @@ namespace StardewModdingAPI.Framework.ModLoading ** Private models *********/ /// <summary>Represents a dependency from one mod to another.</summary> - private struct ModDependency + private readonly struct ModDependency { /********* ** Accessors diff --git a/src/SMAPI/Framework/ModLoading/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/RewriteHelper.cs index 9ff43d45..f8f10dc4 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteHelper.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteHelper.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Framework.ModLoading internal static class RewriteHelper { /********* - ** Properties + ** Fields *********/ /// <summary>The comparer which heuristically compares type definitions.</summary> private static readonly TypeReferenceComparer TypeDefinitionComparer = new TypeReferenceComparer(); diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs index 806a074f..ff86c6e2 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters internal class FieldReplaceRewriter : FieldFinder { /********* - ** Properties + ** Fields *********/ /// <summary>The new field to reference.</summary> private readonly FieldInfo ToField; diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs index e6ede9e3..a43c5e9a 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/FieldToPropertyRewriter.cs @@ -9,7 +9,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters internal class FieldToPropertyRewriter : FieldFinder { /********* - ** Properties + ** Fields *********/ /// <summary>The type whose field to which references should be rewritten.</summary> private readonly Type Type; diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs index 99bd9125..6b8c2de1 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs @@ -8,7 +8,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters internal class MethodParentRewriter : IInstructionHandler { /********* - ** Properties + ** Fields *********/ /// <summary>The type whose methods to remap.</summary> private readonly Type FromType; diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs index 5e12b46a..7e7c0efa 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/StaticFieldToConstantRewriter.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters internal class StaticFieldToConstantRewriter<TValue> : FieldFinder { /********* - ** Properties + ** Fields *********/ /// <summary>The constant value to replace with.</summary> private readonly TValue Value; diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs index 62e15731..fade082b 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs @@ -9,7 +9,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters internal class TypeReferenceRewriter : TypeFinder { /********* - ** Properties + ** Fields *********/ /// <summary>The full type name to which to find references.</summary> private readonly string FromTypeName; diff --git a/src/SMAPI/Framework/ModRegistry.cs b/src/SMAPI/Framework/ModRegistry.cs index 8ce3172c..e9ceb66e 100644 --- a/src/SMAPI/Framework/ModRegistry.cs +++ b/src/SMAPI/Framework/ModRegistry.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Framework internal class ModRegistry { /********* - ** Properties + ** Fields *********/ /// <summary>The registered mod data.</summary> private readonly List<IModMetadata> Mods = new List<IModMetadata>(); diff --git a/src/SMAPI/Framework/Monitor.cs b/src/SMAPI/Framework/Monitor.cs index a4d92e4b..47ebc2d7 100644 --- a/src/SMAPI/Framework/Monitor.cs +++ b/src/SMAPI/Framework/Monitor.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Framework internal class Monitor : IMonitor { /********* - ** Properties + ** Fields *********/ /// <summary>The name of the module which logs messages using this instance.</summary> private readonly string Source; diff --git a/src/SMAPI/Framework/Networking/MultiplayerPeer.cs b/src/SMAPI/Framework/Networking/MultiplayerPeer.cs index 44a71978..b4e39379 100644 --- a/src/SMAPI/Framework/Networking/MultiplayerPeer.cs +++ b/src/SMAPI/Framework/Networking/MultiplayerPeer.cs @@ -9,7 +9,7 @@ namespace StardewModdingAPI.Framework.Networking internal class MultiplayerPeer : IMultiplayerPeer { /********* - ** Properties + ** Fields *********/ /// <summary>A method which sends a message to the peer.</summary> private readonly Action<OutgoingMessage> SendMessageImpl; diff --git a/src/SMAPI/Framework/Networking/SGalaxyNetClient.cs b/src/SMAPI/Framework/Networking/SGalaxyNetClient.cs index fddd423d..01095c66 100644 --- a/src/SMAPI/Framework/Networking/SGalaxyNetClient.cs +++ b/src/SMAPI/Framework/Networking/SGalaxyNetClient.cs @@ -9,7 +9,7 @@ namespace StardewModdingAPI.Framework.Networking internal class SGalaxyNetClient : GalaxyNetClient { /********* - ** Properties + ** Fields *********/ /// <summary>A callback to raise when receiving a message. This receives the incoming message, a method to send an arbitrary message, and a callback to run the default logic.</summary> private readonly Action<IncomingMessage, Action<OutgoingMessage>, Action> OnProcessingMessage; diff --git a/src/SMAPI/Framework/Networking/SGalaxyNetServer.cs b/src/SMAPI/Framework/Networking/SGalaxyNetServer.cs index 82d11938..bb67f70e 100644 --- a/src/SMAPI/Framework/Networking/SGalaxyNetServer.cs +++ b/src/SMAPI/Framework/Networking/SGalaxyNetServer.cs @@ -11,7 +11,7 @@ namespace StardewModdingAPI.Framework.Networking internal class SGalaxyNetServer : GalaxyNetServer { /********* - ** Properties + ** Fields *********/ /// <summary>A callback to raise when receiving a message. This receives the incoming message, a method to send a message, and a callback to run the default logic.</summary> private readonly Action<IncomingMessage, Action<OutgoingMessage>, Action> OnProcessingMessage; diff --git a/src/SMAPI/Framework/Networking/SLidgrenClient.cs b/src/SMAPI/Framework/Networking/SLidgrenClient.cs index 02d9d68f..39876744 100644 --- a/src/SMAPI/Framework/Networking/SLidgrenClient.cs +++ b/src/SMAPI/Framework/Networking/SLidgrenClient.cs @@ -7,7 +7,7 @@ namespace StardewModdingAPI.Framework.Networking internal class SLidgrenClient : LidgrenClient { /********* - ** Properties + ** Fields *********/ /// <summary>A callback to raise when receiving a message. This receives the incoming message, a method to send an arbitrary message, and a callback to run the default logic.</summary> private readonly Action<IncomingMessage, Action<OutgoingMessage>, Action> OnProcessingMessage; diff --git a/src/SMAPI/Framework/Networking/SLidgrenServer.cs b/src/SMAPI/Framework/Networking/SLidgrenServer.cs index 251e5268..1bce47fe 100644 --- a/src/SMAPI/Framework/Networking/SLidgrenServer.cs +++ b/src/SMAPI/Framework/Networking/SLidgrenServer.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Framework.Networking internal class SLidgrenServer : LidgrenServer { /********* - ** Properties + ** Fields *********/ /// <summary>SMAPI's implementation of the game's core multiplayer logic.</summary> private readonly SMultiplayer Multiplayer; diff --git a/src/SMAPI/Framework/Patching/GamePatcher.cs b/src/SMAPI/Framework/Patching/GamePatcher.cs index 71ca8e55..f82159d0 100644 --- a/src/SMAPI/Framework/Patching/GamePatcher.cs +++ b/src/SMAPI/Framework/Patching/GamePatcher.cs @@ -7,7 +7,7 @@ namespace StardewModdingAPI.Framework.Patching internal class GamePatcher { /********* - ** Properties + ** Fields *********/ /// <summary>Encapsulates monitoring and logging.</summary> private readonly IMonitor Monitor; diff --git a/src/SMAPI/Framework/Reflection/CacheEntry.cs b/src/SMAPI/Framework/Reflection/CacheEntry.cs index 30faca37..912662e3 100644 --- a/src/SMAPI/Framework/Reflection/CacheEntry.cs +++ b/src/SMAPI/Framework/Reflection/CacheEntry.cs @@ -3,16 +3,16 @@ using System.Reflection; namespace StardewModdingAPI.Framework.Reflection { /// <summary>A cached member reflection result.</summary> - internal struct CacheEntry + internal readonly struct CacheEntry { /********* ** Accessors *********/ /// <summary>Whether the lookup found a valid match.</summary> - public bool IsValid; + public bool IsValid { get; } /// <summary>The reflection data for this member (or <c>null</c> if invalid).</summary> - public MemberInfo MemberInfo; + public MemberInfo MemberInfo { get; } /********* diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs index 7a2958fb..70ef81f8 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs @@ -9,7 +9,7 @@ namespace StardewModdingAPI.Framework.Reflection internal class InterfaceProxyBuilder { /********* - ** Properties + ** Fields *********/ /// <summary>The target class type.</summary> private readonly Type TargetType; diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs index e14a9f08..464367b6 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs @@ -9,7 +9,7 @@ namespace StardewModdingAPI.Framework.Reflection internal class InterfaceProxyFactory { /********* - ** Properties + ** Fields *********/ /// <summary>The CLR module in which to create proxy classes.</summary> private readonly ModuleBuilder ModuleBuilder; diff --git a/src/SMAPI/Framework/Reflection/ReflectedField.cs b/src/SMAPI/Framework/Reflection/ReflectedField.cs index 09638b1d..d771422c 100644 --- a/src/SMAPI/Framework/Reflection/ReflectedField.cs +++ b/src/SMAPI/Framework/Reflection/ReflectedField.cs @@ -8,7 +8,7 @@ namespace StardewModdingAPI.Framework.Reflection internal class ReflectedField<TValue> : IReflectedField<TValue> { /********* - ** Properties + ** Fields *********/ /// <summary>The type that has the field.</summary> private readonly Type ParentType; diff --git a/src/SMAPI/Framework/Reflection/ReflectedMethod.cs b/src/SMAPI/Framework/Reflection/ReflectedMethod.cs index 7d9072a0..039f27c3 100644 --- a/src/SMAPI/Framework/Reflection/ReflectedMethod.cs +++ b/src/SMAPI/Framework/Reflection/ReflectedMethod.cs @@ -7,7 +7,7 @@ namespace StardewModdingAPI.Framework.Reflection internal class ReflectedMethod : IReflectedMethod { /********* - ** Properties + ** Fields *********/ /// <summary>The type that has the method.</summary> private readonly Type ParentType; diff --git a/src/SMAPI/Framework/Reflection/ReflectedProperty.cs b/src/SMAPI/Framework/Reflection/ReflectedProperty.cs index d59b71ac..8a10ff9a 100644 --- a/src/SMAPI/Framework/Reflection/ReflectedProperty.cs +++ b/src/SMAPI/Framework/Reflection/ReflectedProperty.cs @@ -8,7 +8,7 @@ namespace StardewModdingAPI.Framework.Reflection internal class ReflectedProperty<TValue> : IReflectedProperty<TValue> { /********* - ** Properties + ** Fields *********/ /// <summary>The display name shown in error messages.</summary> private readonly string DisplayName; diff --git a/src/SMAPI/Framework/Reflection/Reflector.cs b/src/SMAPI/Framework/Reflection/Reflector.cs index 910e3a54..ed1a4381 100644 --- a/src/SMAPI/Framework/Reflection/Reflector.cs +++ b/src/SMAPI/Framework/Reflection/Reflector.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Framework.Reflection internal class Reflector { /********* - ** Properties + ** Fields *********/ /// <summary>The cached fields and methods found via reflection.</summary> private readonly MemoryCache Cache = new MemoryCache(typeof(Reflector).FullName); diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 679838ba..27c0c40b 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -42,7 +42,7 @@ namespace StardewModdingAPI.Framework internal class SCore : IDisposable { /********* - ** Properties + ** Fields *********/ /// <summary>The log file to which to write messages.</summary> private readonly LogFileManager LogFile; @@ -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() - ); } /// <summary>Launch SMAPI.</summary> @@ -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(() => { @@ -928,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/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index d515d3ad..e2835a70 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -39,7 +39,7 @@ namespace StardewModdingAPI.Framework internal class SGame : Game1 { /********* - ** Properties + ** Fields *********/ /**** ** SMAPI state @@ -69,8 +69,8 @@ namespace StardewModdingAPI.Framework /// <remarks>Skipping a few frames ensures the game finishes initialising the world before mods try to change it.</remarks> private readonly Countdown AfterLoadTimer = new Countdown(5); - /// <summary>Whether the after-load events were raised for this session.</summary> - private bool RaisedAfterLoadEvent; + /// <summary>The current stage in the game's loading process.</summary> + private LoadStage LoadStage = LoadStage.None; /// <summary>Whether the game is saving and SMAPI has already raised <see cref="IGameLoopEvents.Saving"/>.</summary> private bool IsBetweenSaveEvents; @@ -96,15 +96,9 @@ namespace StardewModdingAPI.Framework /// <summary>Monitors the entire game state for changes.</summary> private WatcherCore Watchers; - /// <summary>An index incremented on every tick and reset every 60th tick (0–59).</summary> - private int CurrentUpdateTick; - /// <summary>Whether post-game-startup initialisation has been performed.</summary> private bool IsInitialised; - /// <summary>The number of update ticks which have already executed.</summary> - private uint TicksElapsed; - /// <summary>Whether the next content manager requested by the game will be for <see cref="Game1.content"/>.</summary> private bool NextContentManagerIsMain; @@ -213,15 +207,33 @@ namespace StardewModdingAPI.Framework this.Events.ModMessageReceived.RaiseForMods(new ModMessageReceivedEventArgs(message), mod => mod != null && modIDs.Contains(mod.Manifest.UniqueID)); } - /// <summary>A callback raised when the player quits a save and returns to the title screen.</summary> - private void OnReturnedToTitle() + /// <summary>A callback invoked when the game's low-level load stage changes.</summary> + /// <param name="newStage">The new load stage.</param> + internal void OnLoadStageChanged(LoadStage newStage) { - this.Monitor.Log("Context: returned to title", LogLevel.Trace); - 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 + } } /// <summary>Constructor a content manager to read XNB files.</summary> @@ -253,6 +265,8 @@ namespace StardewModdingAPI.Framework /// <param name="gameTime">A snapshot of the game timing state.</param> protected override void Update(GameTime gameTime) { + var events = this.Events; + try { this.DeprecationManager.PrintQueued(); @@ -280,7 +294,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); } @@ -302,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; } @@ -376,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 } @@ -387,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; } @@ -407,9 +443,10 @@ 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.Events.SaveCreated.RaiseEmpty(); + this.OnLoadStageChanged(LoadStage.CreatedSaveFile); + events.SaveCreated.RaiseEmpty(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_AfterCreateSave.Raise(); + events.Legacy_AfterCreateSave.Raise(); #endif } if (this.IsBetweenSaveEvents) @@ -417,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 } @@ -430,7 +467,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) @@ -455,7 +495,7 @@ namespace StardewModdingAPI.Framework this.OnLocaleChanged(); #if !SMAPI_3_0_STRICT - this.Events.Legacy_LocaleChanged.Raise(new EventArgsValueChanged<string>(was.ToString(), now.ToString())); + events.Legacy_LocaleChanged.Raise(new EventArgsValueChanged<string>(was.ToString(), now.ToString())); #endif this.Watchers.LocaleWatcher.Reset(); @@ -465,8 +505,8 @@ namespace StardewModdingAPI.Framework ** Load / return-to-title events *********/ if (wasWorldReady && !Context.IsWorldReady) - this.OnReturnedToTitle(); - else if (!this.RaisedAfterLoadEvent && 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}."; @@ -480,12 +520,12 @@ namespace StardewModdingAPI.Framework this.Monitor.Log(context, LogLevel.Trace); // raise events - this.RaisedAfterLoadEvent = true; - this.Events.SaveLoaded.RaiseEmpty(); - this.Events.DayStarted.RaiseEmpty(); + this.OnLoadStageChanged(LoadStage.Ready); + 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 } @@ -504,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(); } @@ -525,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 @@ -555,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 } @@ -579,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 } @@ -603,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 } } @@ -623,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 } @@ -656,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 } @@ -675,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 } @@ -689,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 @@ -700,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 @@ -711,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 @@ -722,9 +772,9 @@ namespace StardewModdingAPI.Framework KeyValuePair<Vector2, SObject>[] 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 } @@ -736,7 +786,7 @@ namespace StardewModdingAPI.Framework KeyValuePair<Vector2, TerrainFeature>[] 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)); } } } @@ -754,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 @@ -774,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 } @@ -786,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 } @@ -798,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 } @@ -810,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 } } @@ -823,11 +873,21 @@ namespace StardewModdingAPI.Framework /********* ** Game update *********/ - this.TicksElapsed++; - if (this.TicksElapsed == 1) - this.Events.GameLaunched.Raise(new GameLaunchedEventArgs()); - this.Events.UnvalidatedUpdateTicking.Raise(new UnvalidatedUpdateTickingEventArgs(this.TicksElapsed)); - this.Events.UpdateTicking.Raise(new UpdateTickingEventArgs(this.TicksElapsed)); + // game launched + 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 = Game1.ticks % 60 == 0; + events.UnvalidatedUpdateTicking.RaiseEmpty(); + events.UpdateTicking.RaiseEmpty(); + if (isOneSecond) + events.OneSecondUpdateTicking.RaiseEmpty(); try { this.Input.UpdateSuppression(); @@ -837,33 +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) + 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(); } @@ -931,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) @@ -946,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) @@ -964,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(); @@ -986,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) @@ -1005,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); @@ -1028,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(); } @@ -1062,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) @@ -1083,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) @@ -1097,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 += "."; @@ -1109,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) @@ -1138,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 { @@ -1148,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<bool, NetBool>)Game1.currentLocation.isOutdoors) ? Game1.outdoorLight : Game1.ambientLight)); for (int index = 0; index < Game1.currentLightSources.Count; ++index) { @@ -1163,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); @@ -1423,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; @@ -1479,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) @@ -1595,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) @@ -1619,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); @@ -1634,14 +1693,6 @@ namespace StardewModdingAPI.Framework /**** ** Methods ****/ - /// <summary>Perform any cleanup needed when a save is unloaded.</summary> - private void MarkWorldNotReady() - { - Context.IsWorldReady = false; - this.AfterLoadTimer.Reset(); - this.RaisedAfterLoadEvent = false; - } - #if !SMAPI_3_0_STRICT /// <summary>Raise the <see cref="GraphicsEvents.OnPostRenderEvent"/> if there are any listeners.</summary> /// <param name="needsNewBatch">Whether to create a new sprite batch.</param> diff --git a/src/SMAPI/Framework/SModHooks.cs b/src/SMAPI/Framework/SModHooks.cs index 9f0201c8..7dafc746 100644 --- a/src/SMAPI/Framework/SModHooks.cs +++ b/src/SMAPI/Framework/SModHooks.cs @@ -7,7 +7,7 @@ namespace StardewModdingAPI.Framework internal class SModHooks : ModHooks { /********* - ** Properties + ** Fields *********/ /// <summary>A callback to invoke before <see cref="Game1.newDayAfterFade"/> runs.</summary> private readonly Action BeforeNewDayAfterFade; diff --git a/src/SMAPI/Framework/SMultiplayer.cs b/src/SMAPI/Framework/SMultiplayer.cs index 29d9b2b8..0241ef02 100644 --- a/src/SMAPI/Framework/SMultiplayer.cs +++ b/src/SMAPI/Framework/SMultiplayer.cs @@ -31,7 +31,7 @@ namespace StardewModdingAPI.Framework internal class SMultiplayer : Multiplayer { /********* - ** Properties + ** Fields *********/ /// <summary>Encapsulates monitoring and logging.</summary> private readonly IMonitor Monitor; diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/BaseDisposableWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/BaseDisposableWatcher.cs index 40ec6c57..60006c51 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/BaseDisposableWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/BaseDisposableWatcher.cs @@ -6,7 +6,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers internal abstract class BaseDisposableWatcher : IDisposable { /********* - ** Properties + ** Fields *********/ /// <summary>Whether the watcher has been disposed.</summary> protected bool IsDisposed { get; private set; } diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs index 2ea6609a..6550f950 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs @@ -8,7 +8,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers internal class ComparableListWatcher<TValue> : BaseDisposableWatcher, ICollectionWatcher<TValue> { /********* - ** Properties + ** Fields *********/ /// <summary>The collection to watch.</summary> private readonly ICollection<TValue> CurrentValues; diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs index dda30a15..5ca4b9f4 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs @@ -8,7 +8,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers internal class ComparableWatcher<TValue> : IValueWatcher<TValue> { /********* - ** Properties + ** Fields *********/ /// <summary>Get the current value.</summary> private readonly Func<TValue> GetValue; diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs index d3022a69..21e84c47 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs @@ -9,7 +9,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers where TValue : class, INetObject<INetSerializable> { /********* - ** Properties + ** Fields *********/ /// <summary>The field being watched.</summary> private readonly NetCollection<TValue> Field; diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs index 7a7ab89d..e6882f7e 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs @@ -15,7 +15,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers where TSelf : NetDictionary<TKey, TValue, TField, TSerialDict, TSelf> { /********* - ** Properties + ** Fields *********/ /// <summary>The pairs added since the last reset.</summary> private readonly IDictionary<TKey, TValue> PairsAdded = new Dictionary<TKey, TValue>(); diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs index 85099988..48d5d681 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs @@ -8,7 +8,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers internal class NetValueWatcher<TValue, TNetField> : BaseDisposableWatcher, IValueWatcher<TValue> where TNetField : NetFieldBase<TValue, TNetField> { /********* - ** Properties + ** Fields *********/ /// <summary>The field being watched.</summary> private readonly NetFieldBase<TValue, TNetField> Field; diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs index 0c65789f..883b1023 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers internal class ObservableCollectionWatcher<TValue> : BaseDisposableWatcher, ICollectionWatcher<TValue> { /********* - ** Properties + ** Fields *********/ /// <summary>The field being watched.</summary> private readonly ObservableCollection<TValue> Field; diff --git a/src/SMAPI/Framework/StateTracking/LocationTracker.cs b/src/SMAPI/Framework/StateTracking/LocationTracker.cs index 708c0716..2249e41b 100644 --- a/src/SMAPI/Framework/StateTracking/LocationTracker.cs +++ b/src/SMAPI/Framework/StateTracking/LocationTracker.cs @@ -15,7 +15,7 @@ namespace StardewModdingAPI.Framework.StateTracking internal class LocationTracker : IWatcher { /********* - ** Properties + ** Fields *********/ /// <summary>The underlying watchers.</summary> private readonly List<IWatcher> Watchers = new List<IWatcher>(); diff --git a/src/SMAPI/Framework/StateTracking/PlayerTracker.cs b/src/SMAPI/Framework/StateTracking/PlayerTracker.cs index 6a705e50..abb4fa24 100644 --- a/src/SMAPI/Framework/StateTracking/PlayerTracker.cs +++ b/src/SMAPI/Framework/StateTracking/PlayerTracker.cs @@ -14,7 +14,7 @@ namespace StardewModdingAPI.Framework.StateTracking internal class PlayerTracker : IDisposable { /********* - ** Properties + ** Fields *********/ /// <summary>The player's inventory as of the last reset.</summary> private IDictionary<Item, int> PreviousInventory; diff --git a/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs b/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs index 930a8102..f09c69c1 100644 --- a/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs +++ b/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs @@ -13,7 +13,7 @@ namespace StardewModdingAPI.Framework.StateTracking internal class WorldLocationsTracker : IWatcher { /********* - ** Properties + ** Fields *********/ /// <summary>Tracks changes to the location list.</summary> private readonly ICollectionWatcher<GameLocation> LocationListWatcher; diff --git a/src/SMAPI/Framework/WatcherCore.cs b/src/SMAPI/Framework/WatcherCore.cs index 8d29cf18..32b7fdc6 100644 --- a/src/SMAPI/Framework/WatcherCore.cs +++ b/src/SMAPI/Framework/WatcherCore.cs @@ -14,7 +14,7 @@ namespace StardewModdingAPI.Framework internal class WatcherCore { /********* - ** Properties + ** Fields *********/ /// <summary>The underlying watchers for convenience. These are accessible individually as separate properties.</summary> private readonly List<IWatcher> Watchers = new List<IWatcher>(); diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index a44ab7d1..d83fc748 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -23,7 +23,7 @@ namespace StardewModdingAPI.Metadata internal class CoreAssetPropagator { /********* - ** Properties + ** Fields *********/ /// <summary>Normalises an asset key to match the cache key.</summary> private readonly Func<string, string> GetNormalisedPath; diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs index 7c840b2f..9ff99440 100644 --- a/src/SMAPI/Metadata/InstructionMetadata.cs +++ b/src/SMAPI/Metadata/InstructionMetadata.cs @@ -13,7 +13,7 @@ namespace StardewModdingAPI.Metadata internal class InstructionMetadata { /********* - ** Properties + ** Fields *********/ /// <summary>The assembly names to which to heuristically detect broken references.</summary> /// <remarks>The current implementation only works correctly with assemblies that should always be present.</remarks> 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 +{ + /// <summary>A Harmony patch for <see cref="Game1.loadForNewGame"/> which notifies SMAPI for save creation load stages.</summary> + /// <remarks>This patch hooks into <see cref="Game1.loadForNewGame"/>, checks if <c>TitleMenu.transitioningCharacterCreationMenu</c> is true (which means the player is creating a new save file), then raises <see cref="LoadStage.CreatedBasicInfo"/> after the location list is cleared twice (the second clear happens right before locations are created), and <see cref="LoadStage.CreatedLocations"/> when the method ends.</remarks> + internal class LoadForNewGamePatch : IHarmonyPatch + { + /********* + ** Accessors + *********/ + /// <summary>Simplifies access to private code.</summary> + private static Reflector Reflection; + + /// <summary>A callback to invoke when the load stage changes.</summary> + private static Action<LoadStage> OnStageChanged; + + /// <summary>Whether <see cref="Game1.loadForNewGame"/> was called as part of save creation.</summary> + private static bool IsCreating; + + /// <summary>The number of times that <see cref="Game1.locations"/> has been cleared since <see cref="Game1.loadForNewGame"/> started.</summary> + private static int TimesLocationsCleared = 0; + + + /********* + ** Accessors + *********/ + /// <summary>A unique name for this patch.</summary> + public string Name => $"{nameof(LoadForNewGamePatch)}"; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="reflection">Simplifies access to private code.</param> + /// <param name="onStageChanged">A callback to invoke when the load stage changes.</param> + public LoadForNewGamePatch(Reflector reflection, Action<LoadStage> onStageChanged) + { + LoadForNewGamePatch.Reflection = reflection; + LoadForNewGamePatch.OnStageChanged = onStageChanged; + } + + /// <summary>Apply the Harmony patch.</summary> + /// <param name="harmony">The Harmony instance.</param> + 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 + *********/ + /// <summary>The method to call instead of <see cref="Game1.loadForNewGame"/>.</summary> + /// <returns>Returns whether to execute the original method.</returns> + /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks> + private static bool Prefix() + { + LoadForNewGamePatch.IsCreating = Game1.activeClickableMenu is TitleMenu menu && LoadForNewGamePatch.Reflection.GetField<bool>(menu, "transitioningCharacterCreationMenu").GetValue(); + LoadForNewGamePatch.TimesLocationsCleared = 0; + if (LoadForNewGamePatch.IsCreating) + { + // raise CreatedBasicInfo after locations are cleared twice + ObservableCollection<GameLocation> locations = (ObservableCollection<GameLocation>)Game1.locations; + locations.CollectionChanged += LoadForNewGamePatch.OnLocationListChanged; + } + + return true; + } + + /// <summary>The method to call instead after <see cref="Game1.loadForNewGame"/>.</summary> + /// <remarks>This method must be static for Harmony to work correctly. See the Harmony documentation before renaming arguments.</remarks> + private static void Postfix() + { + if (LoadForNewGamePatch.IsCreating) + { + // clean up + ObservableCollection<GameLocation> locations = (ObservableCollection<GameLocation>) Game1.locations; + locations.CollectionChanged -= LoadForNewGamePatch.OnLocationListChanged; + + // raise stage changed + LoadForNewGamePatch.OnStageChanged(LoadStage.CreatedLocations); + } + } + + /// <summary>Raised when <see cref="Game1.locations"/> changes.</summary> + /// <param name="sender">The event sender.</param> + /// <param name="e">The event arguments.</param> + private static void OnLocationListChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (++LoadForNewGamePatch.TimesLocationsCleared == 2) + LoadForNewGamePatch.OnStageChanged(LoadStage.CreatedBasicInfo); + } + } +} diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 2efcfecb..2eec371c 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -15,7 +15,7 @@ namespace StardewModdingAPI internal class Program { /********* - ** Properties + ** Fields *********/ /// <summary>The absolute path to search for SMAPI's internal DLLs.</summary> /// <remarks>We can't use <see cref="Constants.ExecutionPath"/> directly, since <see cref="Constants"/> depends on DLLs loaded from this folder.</remarks> diff --git a/src/SMAPI/SemanticVersion.cs b/src/SMAPI/SemanticVersion.cs index 4fc6c219..e8e5dfa4 100644 --- a/src/SMAPI/SemanticVersion.cs +++ b/src/SMAPI/SemanticVersion.cs @@ -8,7 +8,7 @@ namespace StardewModdingAPI public class SemanticVersion : ISemanticVersion { /********* - ** Properties + ** Fields *********/ /// <summary>The underlying semantic version implementation.</summary> private readonly ISemanticVersion Version; @@ -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; } } diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj index 9b00e777..6692bc02 100644 --- a/src/SMAPI/StardewModdingAPI.csproj +++ b/src/SMAPI/StardewModdingAPI.csproj @@ -27,6 +27,7 @@ <ApplicationVersion>1.0.0.%2a</ApplicationVersion> <UseApplicationTrust>false</UseApplicationTrust> <BootstrapperEnabled>true</BootstrapperEnabled> + <LangVersion>latest</LangVersion> <LargeAddressAware Condition="'$(OS)' == 'Windows_NT'">true</LargeAddressAware> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'"> @@ -76,6 +77,7 @@ <Compile Include="..\..\build\GlobalAssemblyInfo.cs"> <Link>Properties\GlobalAssemblyInfo.cs</Link> </Compile> + <Compile Include="Enums\LoadStage.cs" /> <Compile Include="Enums\SkillType.cs" /> <Compile Include="Events\BuildingListChangedEventArgs.cs" /> <Compile Include="Events\ButtonPressedEventArgs.cs" /> @@ -123,6 +125,7 @@ <Compile Include="Events\IWorldEvents.cs" /> <Compile Include="Events\LargeTerrainFeatureListChangedEventArgs.cs" /> <Compile Include="Events\LevelChangedEventArgs.cs" /> + <Compile Include="Events\LoadStageChangedEventArgs.cs" /> <Compile Include="Events\LocationEvents.cs" /> <Compile Include="Events\LocationListChangedEventArgs.cs" /> <Compile Include="Events\MenuChangedEventArgs.cs" /> @@ -133,6 +136,8 @@ <Compile Include="Events\MultiplayerEvents.cs" /> <Compile Include="Events\NpcListChangedEventArgs.cs" /> <Compile Include="Events\ObjectListChangedEventArgs.cs" /> + <Compile Include="Events\OneSecondUpdateTickedEventArgs.cs" /> + <Compile Include="Events\OneSecondUpdateTickingEventArgs.cs" /> <Compile Include="Events\PeerContextReceivedEventArgs.cs" /> <Compile Include="Events\PeerDisconnectedEventArgs.cs" /> <Compile Include="Events\PlayerEvents.cs" /> @@ -326,6 +331,7 @@ <Compile Include="Framework\Monitor.cs" /> <Compile Include="Metadata\InstructionMetadata.cs" /> <Compile Include="Mod.cs" /> + <Compile Include="Patches\LoadForNewGamePatch.cs" /> <Compile Include="Patches\ObjectErrorPatch.cs" /> <Compile Include="Patches\DialogueErrorPatch.cs" /> <Compile Include="PatchMode.cs" /> diff --git a/src/SMAPI/Translation.cs b/src/SMAPI/Translation.cs index ce344f81..abcdb336 100644 --- a/src/SMAPI/Translation.cs +++ b/src/SMAPI/Translation.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI public class Translation { /********* - ** Properties + ** Fields *********/ /// <summary>The placeholder text when the translation is <c>null</c> or empty, where <c>{0}</c> is the translation key.</summary> internal const string PlaceholderText = "(no translation:{0})"; diff --git a/src/SMAPI/Utilities/SDate.cs b/src/SMAPI/Utilities/SDate.cs index e589e9a4..ec54f84a 100644 --- a/src/SMAPI/Utilities/SDate.cs +++ b/src/SMAPI/Utilities/SDate.cs @@ -8,7 +8,7 @@ namespace StardewModdingAPI.Utilities public class SDate : IEquatable<SDate> { /********* - ** Properties + ** Fields *********/ /// <summary>The internal season names in order.</summary> private readonly string[] Seasons = { "spring", "summer", "fall", "winter" }; |