From bd959442ea4d349ea710a163f2dd23140f360fe0 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 1 Feb 2020 18:10:56 -0500 Subject: + credit in release notes --- docs/release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index f1981218..926f4058 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -23,7 +23,7 @@ Released 01 February 2020 for Stardew Valley 1.4.1 or later. * Fixed Android issue where game files were backed up. * For modders: - * Added support for `.tmx` map files. + * Added support for `.tmx` map files. (Thanks to [Platonymous for the underlying library](https://github.com/Platonymous/TMXTile)!) * Added special handling for `Vector2` values in `.json` files, so they work consistently crossplatform. * Reworked the order that asset editors/loaders are called between multiple mods to support some framework mods like Content Patcher and Json Assets. Note that the order is undefined and should not be depended on. * Fixed incorrect warning about mods adding invalid schedules in some cases. The validation was unreliable, and has been removed. -- cgit From 0a2b15d3c36a0a5db4d8fb6e48b592507dc74c14 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 2 Feb 2020 14:20:41 -0500 Subject: add support for self-broadcasts, optimize network messages --- docs/release-notes.md | 5 ++ src/SMAPI/Framework/Networking/ModMessageModel.cs | 2 +- src/SMAPI/Framework/SMultiplayer.cs | 84 +++++++++++++++-------- 3 files changed, 63 insertions(+), 28 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 926f4058..fe21cdb7 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,6 +1,11 @@ ← [README](README.md) # Release notes +## Upcoming release +* For modders: + * Added support for self-broadcasts through the multiplayer API. (Mods can now send messages to the current machine. That enables simple integrations between mods without needing an API, and lets mods notify a host mod without needing different code depending on whether the current player is the host or a farmhand.) + * Eliminated unneeded network messages when broadcasting to a peer who can't handle the message (e.g. because they don't have SMAPI or don't have the target mod). + ## 3.2 Released 01 February 2020 for Stardew Valley 1.4.1 or later. diff --git a/src/SMAPI/Framework/Networking/ModMessageModel.cs b/src/SMAPI/Framework/Networking/ModMessageModel.cs index 7ee39863..4f694f9c 100644 --- a/src/SMAPI/Framework/Networking/ModMessageModel.cs +++ b/src/SMAPI/Framework/Networking/ModMessageModel.cs @@ -21,7 +21,7 @@ namespace StardewModdingAPI.Framework.Networking /**** ** Destination ****/ - /// The players who should receive the message, or null for all players. + /// The players who should receive the message. public long[] ToPlayerIDs { get; set; } /// The mods which should receive the message, or null for all mods. diff --git a/src/SMAPI/Framework/SMultiplayer.cs b/src/SMAPI/Framework/SMultiplayer.cs index e04205c8..821c343f 100644 --- a/src/SMAPI/Framework/SMultiplayer.cs +++ b/src/SMAPI/Framework/SMultiplayer.cs @@ -338,64 +338,94 @@ namespace StardewModdingAPI.Framework /// The values for the players who should receive the message, or null for all players. If you don't need to broadcast to all players, specifying player IDs is recommended to reduce latency. public void BroadcastModMessage(TMessage message, string messageType, string fromModID, string[] toModIDs, long[] toPlayerIDs) { - // validate + // validate input if (message == null) throw new ArgumentNullException(nameof(message)); if (string.IsNullOrWhiteSpace(messageType)) throw new ArgumentNullException(nameof(messageType)); if (string.IsNullOrWhiteSpace(fromModID)) throw new ArgumentNullException(nameof(fromModID)); - if (!this.Peers.Any()) + + // get target players + long curPlayerId = Game1.player.UniqueMultiplayerID; + bool sendToSelf = false; + List sendToPeers = new List(); + if (toPlayerIDs == null) { - this.Monitor.VerboseLog($"Ignored '{messageType}' broadcast from mod {fromModID}: not connected to any players."); - return; + sendToSelf = true; + sendToPeers.AddRange(this.Peers.Values); } - - // filter player IDs - HashSet playerIDs = null; - if (toPlayerIDs != null && toPlayerIDs.Any()) + else { - playerIDs = new HashSet(toPlayerIDs); - playerIDs.RemoveWhere(id => !this.Peers.ContainsKey(id)); - if (!playerIDs.Any()) + foreach (long id in toPlayerIDs.Distinct()) { - this.Monitor.VerboseLog($"Ignored '{messageType}' broadcast from mod {fromModID}: none of the specified player IDs are connected."); - return; + if (id == curPlayerId) + sendToSelf = true; + else if (this.Peers.TryGetValue(id, out MultiplayerPeer peer) && peer.HasSmapi) + sendToPeers.Add(peer); } } + // filter by mod ID + if (toModIDs != null) + { + HashSet sendToMods = new HashSet(toModIDs, StringComparer.InvariantCultureIgnoreCase); + if (sendToSelf && toModIDs.All(id => this.ModRegistry.Get(id) == null)) + sendToSelf = false; + + sendToPeers.RemoveAll(peer => peer.Mods.All(mod => !sendToMods.Contains(mod.ID))); + } + + // validate recipients + if (!sendToSelf && !sendToPeers.Any()) + { + this.Monitor.VerboseLog($"Ignored '{messageType}' broadcast from mod {fromModID}: none of the specified player IDs can receive this message."); + return; + } + // get data to send ModMessageModel model = new ModMessageModel( fromPlayerID: Game1.player.UniqueMultiplayerID, fromModID: fromModID, toModIDs: toModIDs, - toPlayerIDs: playerIDs?.ToArray(), + toPlayerIDs: sendToPeers.Select(p => p.PlayerID).ToArray(), type: messageType, data: JToken.FromObject(message) ); string data = JsonConvert.SerializeObject(model, Formatting.None); - // log message - if (this.LogNetworkTraffic) - this.Monitor.Log($"Broadcasting '{messageType}' message: {data}.", LogLevel.Trace); + // send self-message + if (sendToSelf) + { + if (this.LogNetworkTraffic) + this.Monitor.Log($"Broadcasting '{messageType}' message to self: {data}.", LogLevel.Trace); + + this.OnModMessageReceived(model); + } - // send message - if (Context.IsMainPlayer) + // send message to peers + if (sendToPeers.Any()) { - foreach (MultiplayerPeer peer in this.Peers.Values) + if (Context.IsMainPlayer) { - if (playerIDs == null || playerIDs.Contains(peer.PlayerID)) + foreach (MultiplayerPeer peer in sendToPeers) { - model.ToPlayerIDs = new[] { peer.PlayerID }; + if (this.LogNetworkTraffic) + this.Monitor.Log($"Broadcasting '{messageType}' message to farmhand {peer.PlayerID}: {data}.", LogLevel.Trace); + peer.SendMessage(new OutgoingMessage((byte)MessageType.ModMessage, peer.PlayerID, data)); } } - } - else if (this.HostPeer != null && this.HostPeer.HasSmapi) - this.HostPeer.SendMessage(new OutgoingMessage((byte)MessageType.ModMessage, this.HostPeer.PlayerID, data)); - else - this.Monitor.VerboseLog(" Can't send message because no valid connections were found."); + else if (this.HostPeer?.HasSmapi == true) + { + if (this.LogNetworkTraffic) + this.Monitor.Log($"Broadcasting '{messageType}' message to host {this.HostPeer.PlayerID}: {data}.", LogLevel.Trace); + this.HostPeer.SendMessage(new OutgoingMessage((byte)MessageType.ModMessage, this.HostPeer.PlayerID, data)); + } + else + this.Monitor.VerboseLog(" Can't send message because no valid connections were found."); + } } -- cgit From 4991b4d6afea97dc6c9aa5174d438cac72e7d116 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 2 Feb 2020 15:01:03 -0500 Subject: prefix OS name in log on Android --- docs/release-notes.md | 3 +++ src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs | 14 +++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index fe21cdb7..56d4e7c3 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -6,6 +6,9 @@ * Added support for self-broadcasts through the multiplayer API. (Mods can now send messages to the current machine. That enables simple integrations between mods without needing an API, and lets mods notify a host mod without needing different code depending on whether the current player is the host or a farmhand.) * Eliminated unneeded network messages when broadcasting to a peer who can't handle the message (e.g. because they don't have SMAPI or don't have the target mod). +* For SMAPI/tool developers: + * The SMAPI log now prefixes the OS name with `Android` on Android. + ## 3.2 Released 01 February 2020 for Stardew Valley 1.4.1 or later. diff --git a/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs b/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs index 2a01fe4b..c45448f3 100644 --- a/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs +++ b/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs @@ -53,7 +53,19 @@ namespace StardewModdingAPI.Toolkit.Utilities } catch { } #endif - return (platform == Platform.Mac ? "MacOS " : "") + Environment.OSVersion; + + string name = Environment.OSVersion.ToString(); + switch (platform) + { + case Platform.Android: + name = $"Android {name}"; + break; + + case Platform.Mac: + name = $"MacOS {name}"; + break; + } + return name; } /// Get the name of the Stardew Valley executable. -- cgit From 65180f86d81736327a5c8179ebf76a3cb4aff32c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 3 Feb 2020 19:12:39 -0500 Subject: update CP schema for .tmx support --- docs/release-notes.md | 3 +++ src/SMAPI.Web/wwwroot/schemas/content-patcher.json | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 56d4e7c3..a365b153 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -6,6 +6,9 @@ * Added support for self-broadcasts through the multiplayer API. (Mods can now send messages to the current machine. That enables simple integrations between mods without needing an API, and lets mods notify a host mod without needing different code depending on whether the current player is the host or a farmhand.) * Eliminated unneeded network messages when broadcasting to a peer who can't handle the message (e.g. because they don't have SMAPI or don't have the target mod). +* For the web UI: + * Updated the JSON validator and Content Patcher schema for `.tmx` support. + * For SMAPI/tool developers: * The SMAPI log now prefixes the OS name with `Android` on Android. diff --git a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json index 7e00c28e..e6cd4e65 100644 --- a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json +++ b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json @@ -142,7 +142,7 @@ }, "FromFile": { "title": "Source file", - "description": "The relative file path in your content pack folder to load instead (like 'assets/dinosaur.png'). This can be a .json (data), .png (image), .tbin (map), or .xnb file. This field supports tokens and capitalization doesn't matter.", + "description": "The relative file path in your content pack folder to load instead (like 'assets/dinosaur.png'). This can be a .json (data), .png (image), .tbin or .tmx (map), or .xnb file. This field supports tokens and capitalization doesn't matter.", "type": "string", "allOf": [ { @@ -151,12 +151,12 @@ } }, { - "pattern": "\\.(json|png|tbin|xnb) *$" + "pattern": "\\.(json|png|tbin|tmx|xnb) *$" } ], "@errorMessages": { "allOf:indexes: 0": "Invalid value; must not contain directory climbing (like '../').", - "allOf:indexes: 1": "Invalid value; must be a file path ending with .json, .png, .tbin, or .xnb." + "allOf:indexes: 1": "Invalid value; must be a file path ending with .json, .png, .tbin, .tmx, or .xnb." } }, "FromArea": { @@ -325,7 +325,7 @@ "then": { "properties": { "FromFile": { - "description": "The relative path to the map in your content pack folder from which to copy (like assets/town.tbin). This can be a .tbin or .xnb file. This field supports tokens and capitalization doesn't matter.\nContent Patcher will handle tilesheets referenced by the FromFile map for you:\n - If a tilesheet isn't referenced by the target map, Content Patcher will add it for you (with a z_ ID prefix to avoid conflicts with hardcoded game logic). If the source map has a custom version of a tilesheet that's already referenced, it'll be added as a separate tilesheet only used by your tiles.\n - If you include the tilesheet file in your mod folder, Content Patcher will use that one automatically; otherwise it will be loaded from the game's Content/Maps folder." + "description": "The relative path to the map in your content pack folder from which to copy (like assets/town.tbin). This can be a .tbin, .tmx, or .xnb file. This field supports tokens and capitalization doesn't matter.\nContent Patcher will handle tilesheets referenced by the FromFile map for you:\n - If a tilesheet isn't referenced by the target map, Content Patcher will add it for you (with a z_ ID prefix to avoid conflicts with hardcoded game logic). If the source map has a custom version of a tilesheet that's already referenced, it'll be added as a separate tilesheet only used by your tiles.\n - If you include the tilesheet file in your mod folder, Content Patcher will use that one automatically; otherwise it will be loaded from the game's Content/Maps folder." }, "FromArea": { "description": "The part of the source map to copy. Defaults to the whole source map." -- cgit From f3acc0b07c1d126b03fbf1ea48f45be1308dce97 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 8 Feb 2020 23:34:43 -0500 Subject: add Italian translations --- docs/release-notes.md | 3 +++ src/SMAPI/i18n/it.json | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 src/SMAPI/i18n/it.json (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index a365b153..78b0d132 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,9 @@ # Release notes ## Upcoming release +* For players: + * Updated translations. Thanks to xCarloC (added Italian)! + * For modders: * Added support for self-broadcasts through the multiplayer API. (Mods can now send messages to the current machine. That enables simple integrations between mods without needing an API, and lets mods notify a host mod without needing different code depending on whether the current player is the host or a farmhand.) * Eliminated unneeded network messages when broadcasting to a peer who can't handle the message (e.g. because they don't have SMAPI or don't have the target mod). diff --git a/src/SMAPI/i18n/it.json b/src/SMAPI/i18n/it.json new file mode 100644 index 00000000..43493018 --- /dev/null +++ b/src/SMAPI/i18n/it.json @@ -0,0 +1,3 @@ +{ + "warn.invalid-content-removed": "Contenuto non valido rimosso per prevenire un crash (Guarda la console di SMAPI per maggiori informazioni)." +} -- cgit From 136773678e1ce623bd23f170dca265d31030d200 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 9 Feb 2020 01:04:55 -0500 Subject: add helper.Input.GetStatus method --- docs/release-notes.md | 1 + src/SMAPI/Framework/Input/InputStatus.cs | 29 --------------------------- src/SMAPI/Framework/Input/SInputState.cs | 7 +++++++ src/SMAPI/Framework/ModHelpers/InputHelper.cs | 7 +++++++ src/SMAPI/IInputHelper.cs | 4 ++++ src/SMAPI/InputStatus.cs | 29 +++++++++++++++++++++++++++ 6 files changed, 48 insertions(+), 29 deletions(-) delete mode 100644 src/SMAPI/Framework/Input/InputStatus.cs create mode 100644 src/SMAPI/InputStatus.cs (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 78b0d132..4b7c359a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -7,6 +7,7 @@ * For modders: * Added support for self-broadcasts through the multiplayer API. (Mods can now send messages to the current machine. That enables simple integrations between mods without needing an API, and lets mods notify a host mod without needing different code depending on whether the current player is the host or a farmhand.) + * Added `helper.Input.GetStatus` method to get the low-level status of a button. * Eliminated unneeded network messages when broadcasting to a peer who can't handle the message (e.g. because they don't have SMAPI or don't have the target mod). * For the web UI: diff --git a/src/SMAPI/Framework/Input/InputStatus.cs b/src/SMAPI/Framework/Input/InputStatus.cs deleted file mode 100644 index 99b0006c..00000000 --- a/src/SMAPI/Framework/Input/InputStatus.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace StardewModdingAPI.Framework.Input -{ - /// The input status for a button during an update frame. - internal enum InputStatus - { - /// The button was neither pressed, held, nor released. - None, - - /// The button was pressed in this frame. - Pressed, - - /// The button has been held since the last frame. - Held, - - /// The button was released in this frame. - Released - } - - /// Extension methods for . - internal static class InputStatusExtensions - { - /// Whether the button was pressed or held. - /// The button status. - public static bool IsDown(this InputStatus status) - { - return status == InputStatus.Held || status == InputStatus.Pressed; - } - } -} diff --git a/src/SMAPI/Framework/Input/SInputState.cs b/src/SMAPI/Framework/Input/SInputState.cs index 84cea36c..f54d3124 100644 --- a/src/SMAPI/Framework/Input/SInputState.cs +++ b/src/SMAPI/Framework/Input/SInputState.cs @@ -169,6 +169,13 @@ namespace StardewModdingAPI.Framework.Input return buttons.Any(button => this.IsDown(button.ToSButton())); } + /// Get the status of a button. + /// The button to check. + public InputStatus GetStatus(SButton button) + { + return this.GetStatus(this.ActiveButtons, button); + } + /********* ** Private methods diff --git a/src/SMAPI/Framework/ModHelpers/InputHelper.cs b/src/SMAPI/Framework/ModHelpers/InputHelper.cs index f4cd12b6..5858cddd 100644 --- a/src/SMAPI/Framework/ModHelpers/InputHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/InputHelper.cs @@ -50,5 +50,12 @@ namespace StardewModdingAPI.Framework.ModHelpers { this.InputState.SuppressButtons.Add(button); } + + /// Get the status of a button. + /// The button to check. + public InputStatus GetStatus(SButton button) + { + return this.InputState.GetStatus(button); + } } } diff --git a/src/SMAPI/IInputHelper.cs b/src/SMAPI/IInputHelper.cs index 328f504b..ba26773d 100644 --- a/src/SMAPI/IInputHelper.cs +++ b/src/SMAPI/IInputHelper.cs @@ -17,5 +17,9 @@ namespace StardewModdingAPI /// Prevent the game from handling a button press. This doesn't prevent other mods from receiving the event. /// The button to suppress. void Suppress(SButton button); + + /// Get the status of a button. + /// The button to check. + InputStatus GetStatus(SButton button); } } diff --git a/src/SMAPI/InputStatus.cs b/src/SMAPI/InputStatus.cs new file mode 100644 index 00000000..d9cdd6b2 --- /dev/null +++ b/src/SMAPI/InputStatus.cs @@ -0,0 +1,29 @@ +namespace StardewModdingAPI +{ + /// The input status for a button during an update frame. + public enum InputStatus + { + /// The button was neither pressed, held, nor released. + None, + + /// The button was pressed in this frame. + Pressed, + + /// The button has been held since the last frame. + Held, + + /// The button was released in this frame. + Released + } + + /// Extension methods for . + internal static class InputStatusExtensions + { + /// Whether the button was pressed or held. + /// The button status. + public static bool IsDown(this InputStatus status) + { + return status == InputStatus.Held || status == InputStatus.Pressed; + } + } +} -- cgit From 801eaa70871a9ca86dab8022abfd87d558fa1db3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 10 Feb 2020 19:14:06 -0500 Subject: improve save backup logic --- docs/release-notes.md | 4 +++ src/SMAPI.Mods.SaveBackup/ModEntry.cs | 46 ++++++++++++++++++++++------------- 2 files changed, 33 insertions(+), 17 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 4b7c359a..fb66ea1c 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -5,6 +5,10 @@ * For players: * Updated translations. Thanks to xCarloC (added Italian)! +* For the Save Backup mod: + * Fixed warning on MacOS when you have no saves yet. + * Reduced log messages. + * For modders: * Added support for self-broadcasts through the multiplayer API. (Mods can now send messages to the current machine. That enables simple integrations between mods without needing an API, and lets mods notify a host mod without needing different code depending on whether the current player is the host or a farmhand.) * Added `helper.Input.GetStatus` method to get the low-level status of a button. diff --git a/src/SMAPI.Mods.SaveBackup/ModEntry.cs b/src/SMAPI.Mods.SaveBackup/ModEntry.cs index 8b139d8f..b8d3be1c 100644 --- a/src/SMAPI.Mods.SaveBackup/ModEntry.cs +++ b/src/SMAPI.Mods.SaveBackup/ModEntry.cs @@ -66,29 +66,37 @@ namespace StardewModdingAPI.Mods.SaveBackup FileInfo targetFile = new FileInfo(Path.Combine(backupFolder.FullName, this.FileName)); DirectoryInfo fallbackDir = new DirectoryInfo(Path.Combine(backupFolder.FullName, this.BackupLabel)); if (targetFile.Exists || fallbackDir.Exists) + { + this.Monitor.Log("Already backed up today."); return; + } // copy saves to fallback directory (ignore non-save files/folders) - this.Monitor.Log($"Backing up saves to {fallbackDir.FullName}...", LogLevel.Trace); DirectoryInfo savesDir = new DirectoryInfo(Constants.SavesPath); - this.RecursiveCopy(savesDir, fallbackDir, entry => this.MatchSaveFolders(savesDir, entry), copyRoot: false); + if (!this.RecursiveCopy(savesDir, fallbackDir, entry => this.MatchSaveFolders(savesDir, entry), copyRoot: false)) + { + this.Monitor.Log("No saves found."); + return; + } // compress backup if possible - this.Monitor.Log("Compressing backup if possible...", LogLevel.Trace); if (!this.TryCompress(fallbackDir.FullName, targetFile, out Exception compressError)) { - if (Constants.TargetPlatform != GamePlatform.Android) // expected to fail on Android - this.Monitor.Log($"Couldn't compress backup, leaving it uncompressed.\n{compressError}", LogLevel.Trace); + this.Monitor.Log(Constants.TargetPlatform != GamePlatform.Android + ? $"Backed up to {fallbackDir.FullName}." // expected to fail on Android + : $"Backed up to {fallbackDir.FullName}. Couldn't compress backup:\n{compressError}" + ); } else + { + this.Monitor.Log($"Backed up to {targetFile.FullName}."); fallbackDir.Delete(recursive: true); - - this.Monitor.Log("Backup done!", LogLevel.Trace); + } } catch (Exception ex) { - this.Monitor.Log("Couldn't back up save files (see log file for details).", LogLevel.Warn); - this.Monitor.Log(ex.ToString(), LogLevel.Trace); + this.Monitor.Log("Couldn't back up saves (see log file for details).", LogLevel.Warn); + this.Monitor.Log(ex.ToString()); } } @@ -108,7 +116,7 @@ namespace StardewModdingAPI.Mods.SaveBackup { try { - this.Monitor.Log($"Deleting {entry.Name}...", LogLevel.Trace); + this.Monitor.Log($"Deleting {entry.Name}..."); if (entry is DirectoryInfo folder) folder.Delete(recursive: true); else @@ -123,7 +131,7 @@ namespace StardewModdingAPI.Mods.SaveBackup catch (Exception ex) { this.Monitor.Log("Couldn't remove old backups (see log file for details).", LogLevel.Warn); - this.Monitor.Log(ex.ToString(), LogLevel.Trace); + this.Monitor.Log(ex.ToString()); } } @@ -199,29 +207,33 @@ namespace StardewModdingAPI.Mods.SaveBackup /// Whether to copy the root folder itself, or false to only copy its contents. /// A filter which matches the files or directories to copy, or null to copy everything. /// Derived from the SMAPI installer code. - private void RecursiveCopy(FileSystemInfo source, DirectoryInfo targetFolder, Func filter, bool copyRoot = true) + /// Returns whether any files were copied. + private bool RecursiveCopy(FileSystemInfo source, DirectoryInfo targetFolder, Func filter, bool copyRoot = true) { - if (!targetFolder.Exists) - targetFolder.Create(); + if (!source.Exists || filter?.Invoke(source) == false) + return false; - if (filter?.Invoke(source) == false) - return; + bool anyCopied = false; switch (source) { case FileInfo sourceFile: + targetFolder.Create(); sourceFile.CopyTo(Path.Combine(targetFolder.FullName, sourceFile.Name)); + anyCopied = true; break; case DirectoryInfo sourceDir: DirectoryInfo targetSubfolder = copyRoot ? new DirectoryInfo(Path.Combine(targetFolder.FullName, sourceDir.Name)) : targetFolder; foreach (var entry in sourceDir.EnumerateFileSystemInfos()) - this.RecursiveCopy(entry, targetSubfolder, filter); + anyCopied = this.RecursiveCopy(entry, targetSubfolder, filter) || anyCopied; break; default: throw new NotSupportedException($"Unknown filesystem info type '{source.GetType().FullName}'."); } + + return anyCopied; } /// A copy filter which matches save folders. -- cgit From c649572db8f2f57f5f39ed6d842529b188601206 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 10 Feb 2020 19:37:59 -0500 Subject: fix dialogue propagation clearing marriage dialogue --- docs/release-notes.md | 1 + src/SMAPI/Metadata/CoreAssetPropagator.cs | 7 +++++++ 2 files changed, 8 insertions(+) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index fb66ea1c..a14f6175 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -13,6 +13,7 @@ * Added support for self-broadcasts through the multiplayer API. (Mods can now send messages to the current machine. That enables simple integrations between mods without needing an API, and lets mods notify a host mod without needing different code depending on whether the current player is the host or a farmhand.) * Added `helper.Input.GetStatus` method to get the low-level status of a button. * Eliminated unneeded network messages when broadcasting to a peer who can't handle the message (e.g. because they don't have SMAPI or don't have the target mod). + * Fixed marriage dialogue cleared when propagating dialogue changes. * For the web UI: * Updated the JSON validator and Content Patcher schema for `.tmx` support. diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 7a58d52c..8d5ad3ab 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -886,10 +886,17 @@ namespace StardewModdingAPI.Metadata return false; // update dialogue + // Note that marriage dialogue isn't reloaded after reset, but it doesn't need to be + // propagated anyway since marriage dialogue keys can't be added/removed and the field + // doesn't store the text itself. foreach (NPC villager in villagers) { + MarriageDialogueReference[] marriageDialogue = villager.currentMarriageDialogue.ToArray(); + villager.resetSeasonalDialogue(); // doesn't only affect seasonal dialogue villager.resetCurrentDialogue(); + + villager.currentMarriageDialogue.Set(marriageDialogue); } return true; -- cgit From 6294b2731786d9110cd998d6ce503f11e0cfd167 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 13 Feb 2020 21:03:04 -0500 Subject: fix update-check error for SMAPI on Android with four-part versions --- docs/release-notes.md | 3 +- src/SMAPI.Web/Controllers/ModsApiController.cs | 44 ++++++++++++++++++-------- 2 files changed, 32 insertions(+), 15 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index a14f6175..03a41627 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,7 @@ ## Upcoming release * For players: * Updated translations. Thanks to xCarloC (added Italian)! + * Fixed update-check errors for recent versions of SMAPI on Android. * For the Save Backup mod: * Fixed warning on MacOS when you have no saves yet. @@ -13,7 +14,7 @@ * Added support for self-broadcasts through the multiplayer API. (Mods can now send messages to the current machine. That enables simple integrations between mods without needing an API, and lets mods notify a host mod without needing different code depending on whether the current player is the host or a farmhand.) * Added `helper.Input.GetStatus` method to get the low-level status of a button. * Eliminated unneeded network messages when broadcasting to a peer who can't handle the message (e.g. because they don't have SMAPI or don't have the target mod). - * Fixed marriage dialogue cleared when propagating dialogue changes. + * Fixed dialogue propagation clearing marriage dialogue. * For the web UI: * Updated the JSON validator and Content Patcher schema for `.tmx` support. diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs index f194b4d0..8e65b6b9 100644 --- a/src/SMAPI.Web/Controllers/ModsApiController.cs +++ b/src/SMAPI.Web/Controllers/ModsApiController.cs @@ -133,6 +133,7 @@ namespace StardewModdingAPI.Web.Controllers ModDataRecord record = this.ModDatabase.Get(search.ID); WikiModEntry wikiEntry = wikiData.FirstOrDefault(entry => entry.ID.Contains(search.ID.Trim(), StringComparer.InvariantCultureIgnoreCase)); UpdateKey[] updateKeys = this.GetUpdateKeys(search.UpdateKeys, record, wikiEntry).ToArray(); + bool isSmapi = search.ID == "Pathoschild.SMAPI"; // get latest versions ModEntryModel result = new ModEntryModel { ID = search.ID }; @@ -151,7 +152,7 @@ namespace StardewModdingAPI.Web.Controllers } // fetch data - ModInfoModel data = await this.GetInfoForUpdateKeyAsync(updateKey); + ModInfoModel data = await this.GetInfoForUpdateKeyAsync(updateKey, allowNonStandardVersions: isSmapi); if (data.Error != null) { errors.Add(data.Error); @@ -161,7 +162,7 @@ namespace StardewModdingAPI.Web.Controllers // handle main version if (data.Version != null) { - ISemanticVersion version = this.GetMappedVersion(data.Version, wikiEntry?.MapRemoteVersions); + ISemanticVersion version = this.GetMappedVersion(data.Version, wikiEntry?.MapRemoteVersions, allowNonStandard: isSmapi); if (version == null) { errors.Add($"The update key '{updateKey}' matches a mod with invalid semantic version '{data.Version}'."); @@ -175,7 +176,7 @@ namespace StardewModdingAPI.Web.Controllers // handle optional version if (data.PreviewVersion != null) { - ISemanticVersion version = this.GetMappedVersion(data.PreviewVersion, wikiEntry?.MapRemoteVersions); + ISemanticVersion version = this.GetMappedVersion(data.PreviewVersion, wikiEntry?.MapRemoteVersions, allowNonStandard: isSmapi); if (version == null) { errors.Add($"The update key '{updateKey}' matches a mod with invalid optional semantic version '{data.PreviewVersion}'."); @@ -215,7 +216,7 @@ namespace StardewModdingAPI.Web.Controllers } // special cases - if (result.ID == "Pathoschild.SMAPI") + if (isSmapi) { if (main != null) main.Url = "https://smapi.io/"; @@ -224,7 +225,7 @@ namespace StardewModdingAPI.Web.Controllers } // get recommended update (if any) - ISemanticVersion installedVersion = this.GetMappedVersion(search.InstalledVersion?.ToString(), wikiEntry?.MapLocalVersions); + ISemanticVersion installedVersion = this.GetMappedVersion(search.InstalledVersion?.ToString(), wikiEntry?.MapLocalVersions, allowNonStandard: isSmapi); if (apiVersion != null && installedVersion != null) { // get newer versions @@ -283,7 +284,8 @@ namespace StardewModdingAPI.Web.Controllers /// Get the mod info for an update key. /// The namespaced update key. - private async Task GetInfoForUpdateKeyAsync(UpdateKey updateKey) + /// Whether to allow non-standard versions. + private async Task GetInfoForUpdateKeyAsync(UpdateKey updateKey, bool allowNonStandardVersions) { // get mod if (!this.ModCache.TryGetMod(updateKey.Repository, updateKey.ID, out CachedMod mod) || this.ModCache.IsStale(mod.LastUpdated, mod.FetchStatus == RemoteModStatus.TemporaryError ? this.ErrorCacheMinutes : this.SuccessCacheMinutes)) @@ -298,7 +300,7 @@ namespace StardewModdingAPI.Web.Controllers { if (result.Version == null) result.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with no version number."); - else if (!SemanticVersion.TryParse(result.Version, out _)) + else if (!this.TryParseVersion(result.Version, allowNonStandardVersions, out _)) result.SetError(RemoteModStatus.InvalidData, $"The update key '{updateKey}' matches a mod with invalid semantic version '{result.Version}'."); } @@ -357,15 +359,16 @@ namespace StardewModdingAPI.Web.Controllers /// Get a semantic local version for update checks. /// The version to parse. /// A map of version replacements. - private ISemanticVersion GetMappedVersion(string version, IDictionary map) + /// Whether to allow non-standard versions. + private ISemanticVersion GetMappedVersion(string version, IDictionary map, bool allowNonStandard) { // try mapped version - string rawNewVersion = this.GetRawMappedVersion(version, map); - if (SemanticVersion.TryParse(rawNewVersion, out ISemanticVersion parsedNew)) + string rawNewVersion = this.GetRawMappedVersion(version, map, allowNonStandard); + if (this.TryParseVersion(rawNewVersion, allowNonStandard, out ISemanticVersion parsedNew)) return parsedNew; // return original version - return SemanticVersion.TryParse(version, out ISemanticVersion parsedOld) + return this.TryParseVersion(version, allowNonStandard, out ISemanticVersion parsedOld) ? parsedOld : null; } @@ -373,7 +376,8 @@ namespace StardewModdingAPI.Web.Controllers /// Get a semantic local version for update checks. /// The version to map. /// A map of version replacements. - private string GetRawMappedVersion(string version, IDictionary map) + /// Whether to allow non-standard versions. + private string GetRawMappedVersion(string version, IDictionary map, bool allowNonStandard) { if (version == null || map == null || !map.Any()) return version; @@ -383,19 +387,31 @@ namespace StardewModdingAPI.Web.Controllers return map[version]; // match parsed version - if (SemanticVersion.TryParse(version, out ISemanticVersion parsed)) + if (this.TryParseVersion(version, allowNonStandard, out ISemanticVersion parsed)) { if (map.ContainsKey(parsed.ToString())) return map[parsed.ToString()]; foreach (var pair in map) { - if (SemanticVersion.TryParse(pair.Key, out ISemanticVersion target) && parsed.Equals(target) && SemanticVersion.TryParse(pair.Value, out ISemanticVersion newVersion)) + if (this.TryParseVersion(pair.Key, allowNonStandard, out ISemanticVersion target) && parsed.Equals(target) && this.TryParseVersion(pair.Value, allowNonStandard, out ISemanticVersion newVersion)) return newVersion.ToString(); } } return version; } + + /// Try to parse a version string. + /// The version string. + /// Whether to allow non-standard versions. + /// The parsed representation. + /// Returns whether parsing the version succeeded. + public bool TryParseVersion(string version, bool allowNonStandard, out ISemanticVersion parsed) + { + return allowNonStandard + ? SemanticVersion.TryParseNonStandard(version, out parsed) + : SemanticVersion.TryParse(version, out parsed); + } } } -- cgit From 2319c8e19e323552112d99eb440a76771cffd3de Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 17 Feb 2020 23:55:28 -0500 Subject: make compatibility list header sticky --- docs/release-notes.md | 1 + src/SMAPI.Web/wwwroot/Content/css/mods.css | 5 +++++ 2 files changed, 6 insertions(+) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 03a41627..52e25478 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -18,6 +18,7 @@ * For the web UI: * Updated the JSON validator and Content Patcher schema for `.tmx` support. + * The mod compatibility page now has a sticky table header. * For SMAPI/tool developers: * The SMAPI log now prefixes the OS name with `Android` on Android. diff --git a/src/SMAPI.Web/wwwroot/Content/css/mods.css b/src/SMAPI.Web/wwwroot/Content/css/mods.css index 1c2b8056..697ba514 100644 --- a/src/SMAPI.Web/wwwroot/Content/css/mods.css +++ b/src/SMAPI.Web/wwwroot/Content/css/mods.css @@ -86,6 +86,11 @@ table.wikitable > caption { font-size: 0.9em; } +#mod-list thead tr { + position: sticky; + top: 0; +} + #mod-list th.header { background-repeat: no-repeat; background-position: center right; -- cgit From 4ce27475675ddeb9a5fcdec0e460553f0f608be7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 18 Feb 2020 00:12:24 -0500 Subject: update compatibility list --- docs/release-notes.md | 1 + src/SMAPI.Web/wwwroot/SMAPI.metadata.json | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 52e25478..bf8c7917 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -5,6 +5,7 @@ * For players: * Updated translations. Thanks to xCarloC (added Italian)! * Fixed update-check errors for recent versions of SMAPI on Android. + * Updated compatibility list. * For the Save Backup mod: * Fixed warning on MacOS when you have no saves yet. diff --git a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json index 78918bac..eda15bf3 100644 --- a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json +++ b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json @@ -129,7 +129,7 @@ "Bee House Flower Range Fix": { "ID": "kirbylink.beehousefix", "~ | Status": "Obsolete", - "~ | StatusReasonPhrase": "the bee house flower range was fixed in Stardew Valley 1.4." + "~ | StatusReasonPhrase": "the bee house flower range was fixed in Stardew Valley 1.4." }, "Colored Chests": { @@ -153,9 +153,9 @@ /********* ** Broke in SDV 1.4 *********/ - "Fix Dice": { - "ID": "ashley.fixdice", - "~1.1.2 | Status": "AssumeBroken" // crashes game on startup + "Auto Quality Patch": { + "ID": "SilentOak.AutoQualityPatch", + "~2.1.3-unofficial.7 | Status": "AssumeBroken" // runtime errors }, "Fix Dice": { -- cgit From 2e9807a034a84d7e1ce821e92671a655ce13b199 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 19 Feb 2020 23:20:55 -0500 Subject: rework tilesheet loading to improve errors, allow future validation, and drop support for legacy content files --- docs/release-notes.md | 10 +- src/SMAPI/Framework/ContentCoordinator.cs | 4 +- .../Framework/ContentManagers/ModContentManager.cs | 138 +++++++++++---------- src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 8 +- 4 files changed, 86 insertions(+), 74 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index bf8c7917..8b22b95a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -3,18 +3,20 @@ # Release notes ## Upcoming release * For players: - * Updated translations. Thanks to xCarloC (added Italian)! + * Reduced network traffic for mod broadcasts to players who can't process them. * Fixed update-check errors for recent versions of SMAPI on Android. * Updated compatibility list. + * Updated translations. Thanks to xCarloC (added Italian)! * For the Save Backup mod: * Fixed warning on MacOS when you have no saves yet. * Reduced log messages. * For modders: - * Added support for self-broadcasts through the multiplayer API. (Mods can now send messages to the current machine. That enables simple integrations between mods without needing an API, and lets mods notify a host mod without needing different code depending on whether the current player is the host or a farmhand.) - * Added `helper.Input.GetStatus` method to get the low-level status of a button. - * Eliminated unneeded network messages when broadcasting to a peer who can't handle the message (e.g. because they don't have SMAPI or don't have the target mod). + * Added support for [message sending](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations#Message_sending) to mods on the current computer. + * Added `helper.Input.GetStatus` to get the low-level status of a button. + * **[Breaking change]** Map tilesheets are no loaded from `Content` if they can't be found in `Content/Maps`. This reflects an upcoming change in the game to delete map tilesheets under `Content`. + * Improved map tilesheet errors so they provide more info. * Fixed dialogue propagation clearing marriage dialogue. * For the web UI: diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 2fd31263..0b1ccc3c 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -112,9 +112,10 @@ namespace StardewModdingAPI.Framework /// Get a new content manager which handles reading files from a SMAPI mod folder with support for unpacked files. /// A name for the mod manager. Not guaranteed to be unique. + /// The mod display name to show in errors. /// The root directory to search for content (or null for the default). /// The game content manager used for map tilesheets not provided by the mod. - public ModContentManager CreateModContentManager(string name, string rootDirectory, IContentManager gameContentManager) + public ModContentManager CreateModContentManager(string name, string modName, string rootDirectory, IContentManager gameContentManager) { return this.ContentManagerLock.InWriteLock(() => { @@ -123,6 +124,7 @@ namespace StardewModdingAPI.Framework gameContentManager: gameContentManager, serviceProvider: this.MainContentManager.ServiceProvider, rootDirectory: rootDirectory, + modName: modName, currentCulture: this.MainContentManager.CurrentCulture, coordinator: this, monitor: this.Monitor, diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 0a526fc8..6b463424 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -26,6 +26,9 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Encapsulates SMAPI's JSON file parsing. private readonly JsonHelper JsonHelper; + /// The mod display name to show in errors. + private readonly string ModName; + /// The game content manager used for map tilesheets not provided by the mod. private readonly IContentManager GameContentManager; @@ -40,6 +43,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// A name for the mod manager. Not guaranteed to be unique. /// The game content manager used for map tilesheets not provided by the mod. /// The service provider to use to locate services. + /// The mod display name to show in errors. /// The root directory to search for content. /// The current culture for which to localize content. /// The central coordinator which manages content managers. @@ -47,11 +51,12 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Simplifies access to private code. /// Encapsulates SMAPI's JSON file parsing. /// A callback to invoke when the content manager is being disposed. - public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onDisposing) + public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onDisposing) : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: true) { this.GameContentManager = gameContentManager; this.JsonHelper = jsonHelper; + this.ModName = modName; } /// Load an asset that has been processed by the content pipeline. @@ -297,98 +302,99 @@ namespace StardewModdingAPI.Framework.ContentManagers foreach (TileSheet tilesheet in map.TileSheets) { string imageSource = tilesheet.ImageSource; + string errorPrefix = $"{this.ModName} loaded map '{relativeMapPath}' with invalid tilesheet path '{imageSource}'."; // validate tilesheet path if (Path.IsPathRooted(imageSource) || PathUtilities.GetSegments(imageSource).Contains("..")) - throw new ContentLoadException($"The '{imageSource}' tilesheet couldn't be loaded. Tilesheet paths must be a relative path without directory climbing (../)."); - - // get seasonal name (if applicable) - string seasonalImageSource = null; - if (isOutdoors && Context.IsSaveLoaded && Game1.currentSeason != null) - { - string filename = Path.GetFileName(imageSource) ?? throw new InvalidOperationException($"The '{imageSource}' tilesheet couldn't be loaded: filename is unexpectedly null."); - bool hasSeasonalPrefix = - filename.StartsWith("spring_", StringComparison.CurrentCultureIgnoreCase) - || filename.StartsWith("summer_", StringComparison.CurrentCultureIgnoreCase) - || filename.StartsWith("fall_", StringComparison.CurrentCultureIgnoreCase) - || filename.StartsWith("winter_", StringComparison.CurrentCultureIgnoreCase); - if (hasSeasonalPrefix && !filename.StartsWith(Game1.currentSeason + "_")) - { - string dirPath = imageSource.Substring(0, imageSource.LastIndexOf(filename, StringComparison.CurrentCultureIgnoreCase)); - seasonalImageSource = $"{dirPath}{Game1.currentSeason}_{filename.Substring(filename.IndexOf("_", StringComparison.CurrentCultureIgnoreCase) + 1)}"; - } - } + throw new SContentLoadException($"{errorPrefix} Tilesheet paths must be a relative path without directory climbing (../)."); // load best match try { - string key = - this.GetTilesheetAssetName(relativeMapFolder, seasonalImageSource) - ?? this.GetTilesheetAssetName(relativeMapFolder, imageSource); - if (key != null) - { - tilesheet.ImageSource = key; - continue; - } + if (!this.TryGetTilesheetAssetName(relativeMapFolder, imageSource, isOutdoors, out string assetName, out string error)) + throw new SContentLoadException($"{errorPrefix} {error}"); + + tilesheet.ImageSource = assetName; } - catch (Exception ex) + catch (Exception ex) when (!(ex is SContentLoadException)) { - throw new ContentLoadException($"The '{imageSource}' tilesheet couldn't be loaded relative to either map file or the game's content folder.", ex); + throw new SContentLoadException($"{errorPrefix} The tilesheet couldn't be loaded.", ex); } - - // none found - throw new ContentLoadException($"The '{imageSource}' tilesheet couldn't be loaded relative to either map file or the game's content folder."); } } /// Get the actual asset name for a tilesheet. /// The folder path containing the map, relative to the mod folder. - /// The tilesheet image source to load. - /// Returns the asset name. + /// The tilesheet path to load. + /// Whether the game will apply seasonal logic to the tilesheet. + /// The found asset name. + /// A message indicating why the file couldn't be loaded. + /// Returns whether the asset name was found. /// See remarks on . - private string GetTilesheetAssetName(string modRelativeMapFolder, string imageSource) + private bool TryGetTilesheetAssetName(string modRelativeMapFolder, string originalPath, bool willSeasonalize, out string assetName, out string error) { - if (imageSource == null) - return null; + assetName = null; + error = null; + + // nothing to do + if (string.IsNullOrWhiteSpace(originalPath)) + { + assetName = originalPath; + return true; + } - // check relative to map file + // parse path + string filename = Path.GetFileName(originalPath); + bool isSeasonal = filename.StartsWith("spring_", StringComparison.CurrentCultureIgnoreCase) + || filename.StartsWith("summer_", StringComparison.CurrentCultureIgnoreCase) + || filename.StartsWith("fall_", StringComparison.CurrentCultureIgnoreCase) + || filename.StartsWith("winter_", StringComparison.CurrentCultureIgnoreCase); + string relativePath = originalPath; + if (willSeasonalize && isSeasonal) { - string localKey = Path.Combine(modRelativeMapFolder, imageSource); - FileInfo localFile = this.GetModFile(localKey); - if (localFile.Exists) - return this.GetInternalAssetKey(localKey); + string dirPath = Path.GetDirectoryName(originalPath); + relativePath = Path.Combine(dirPath, $"{Game1.currentSeason}_{filename.Substring(filename.IndexOf("_", StringComparison.CurrentCultureIgnoreCase) + 1)}"); } - // check relative to content folder + // get relative to map file { - foreach (string candidateKey in new[] { imageSource, Path.Combine("Maps", imageSource) }) + string localKey = Path.Combine(modRelativeMapFolder, relativePath); + if (this.GetModFile(localKey).Exists) + { + assetName = this.GetInternalAssetKey(localKey); + return true; + } + } + + // get from game assets + { + string contentKey = Path.Combine("Maps", relativePath); + if (contentKey.EndsWith(".png")) + contentKey = contentKey.Substring(0, contentKey.Length - 4); + + try + { + this.GameContentManager.Load(contentKey, this.Language, useCache: true); // no need to bypass cache here, since we're not storing the asset + assetName = contentKey; + return true; + } + catch { - string contentKey = candidateKey.EndsWith(".png") - ? candidateKey.Substring(0, candidateKey.Length - 4) - : candidateKey; - - try - { - this.GameContentManager.Load(contentKey, this.Language, useCache: true); // no need to bypass cache here, since we're not storing the asset - return contentKey; - } - catch - { - // ignore file-not-found errors - // TODO: while it's useful to suppress an asset-not-found error here to avoid - // confusion, this is a pretty naive approach. Even if the file doesn't exist, - // the file may have been loaded through an IAssetLoader which failed. So even - // if the content file doesn't exist, that doesn't mean the error here is a - // content-not-found error. Unfortunately XNA doesn't provide a good way to - // detect the error type. - if (this.GetContentFolderFileExists(contentKey)) - throw; - } + // ignore file-not-found errors + // TODO: while it's useful to suppress an asset-not-found error here to avoid + // confusion, this is a pretty naive approach. Even if the file doesn't exist, + // the file may have been loaded through an IAssetLoader which failed. So even + // if the content file doesn't exist, that doesn't mean the error here is a + // content-not-found error. Unfortunately XNA doesn't provide a good way to + // detect the error type. + if (this.GetContentFolderFileExists(contentKey)) + throw; } } // not found - return null; + error = "The tilesheet couldn't be found relative to either map file or the game's content folder."; + return false; } /// Get whether a file from the game's content folder exists. diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index 043ae376..e9b70845 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -32,7 +32,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The friendly mod name for use in errors. private readonly string ModName; - /// Encapsulates monitoring and logging for a given module. + /// Encapsulates monitoring and logging. private readonly IMonitor Monitor; @@ -70,9 +70,11 @@ namespace StardewModdingAPI.Framework.ModHelpers public ContentHelper(ContentCoordinator contentCore, string modFolderPath, string modID, string modName, IMonitor monitor) : base(modID) { + string managedAssetPrefix = contentCore.GetManagedAssetPrefix(modID); + this.ContentCore = contentCore; - this.GameContentManager = contentCore.CreateGameContentManager(this.ContentCore.GetManagedAssetPrefix(modID) + ".content"); - this.ModContentManager = contentCore.CreateModContentManager(this.ContentCore.GetManagedAssetPrefix(modID), modFolderPath, this.GameContentManager); + this.GameContentManager = contentCore.CreateGameContentManager(managedAssetPrefix + ".content"); + this.ModContentManager = contentCore.CreateModContentManager(managedAssetPrefix, modName, modFolderPath, this.GameContentManager); this.ModName = modName; this.Monitor = monitor; } -- cgit From 2cc786907b2a6cd62691ba2d6514f45a7b46c03c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 19 Feb 2020 23:42:44 -0500 Subject: call IAssetEditor with actual type if applicable --- docs/release-notes.md | 1 + .../Framework/ContentManagers/GameContentManager.cs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 8b22b95a..f4067226 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -18,6 +18,7 @@ * **[Breaking change]** Map tilesheets are no loaded from `Content` if they can't be found in `Content/Maps`. This reflects an upcoming change in the game to delete map tilesheets under `Content`. * Improved map tilesheet errors so they provide more info. * Fixed dialogue propagation clearing marriage dialogue. + * Fixed issue where SMAPI didn't call `IAssetEditor` with the actual type if a mod loaded an asset using `content.Load`. * For the web UI: * Updated the JSON validator and Content Patcher schema for `.tmx` support. diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index eecdda74..eaaf0e6f 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -2,12 +2,15 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Reflection; using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Utilities; using StardewValley; +using xTile; namespace StardewModdingAPI.Framework.ContentManagers { @@ -337,6 +340,20 @@ namespace StardewModdingAPI.Framework.ContentManagers { IAssetData GetNewData(object data) => new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName); + // special case: if the asset was loaded with a more general type like 'object', call editors with the actual type instead. + { + Type actualType = asset.Data.GetType(); + Type actualOpenType = actualType.IsGenericType ? actualType.GetGenericTypeDefinition() : null; + + if (typeof(T) != actualType && (actualOpenType == typeof(Dictionary<,>) || actualOpenType == typeof(List<>) || actualType == typeof(Texture2D) || actualType == typeof(Map))) + { + return (IAssetData)this.GetType() + .GetMethod(nameof(this.ApplyEditors), BindingFlags.NonPublic | BindingFlags.Instance) + .MakeGenericMethod(actualType) + .Invoke(this, new object[] { info, asset }); + } + } + // edit asset foreach (var entry in this.Editors) { -- cgit From eff29d94fbf41f0992ffac7bb5a04a08b71f3453 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 20 Feb 2020 19:52:52 -0500 Subject: don't premultiply fully opaque pixels --- docs/release-notes.md | 1 + src/SMAPI/Framework/ContentManagers/ModContentManager.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index f4067226..ea1f0bfb 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -3,6 +3,7 @@ # Release notes ## Upcoming release * For players: + * Improved performance for mods which load a large number of images. * Reduced network traffic for mod broadcasts to players who can't process them. * Fixed update-check errors for recent versions of SMAPI on Android. * Updated compatibility list. diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 6b463424..7d274eb7 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -253,8 +253,8 @@ namespace StardewModdingAPI.Framework.ContentManagers texture.GetData(data); for (int i = 0; i < data.Length; i++) { - if (data[i].A == 0) - continue; // no need to change fully transparent pixels + if (data[i].A == byte.MinValue || data[i].A == byte.MaxValue) + continue; // no need to change fully transparent/opaque pixels data[i] = Color.FromNonPremultiplied(data[i].ToVector4()); } -- cgit From 6a9bf10a81f4557d44668666117d99440d99d873 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 20 Feb 2020 21:12:00 -0500 Subject: migrate to new method in SMAPI 3.3 --- docs/release-notes.md | 1 + src/SMAPI/Framework/Content/AssetDataForImage.cs | 17 +++++++++++++++++ src/SMAPI/IAssetDataForImage.cs | 6 ++++++ 3 files changed, 24 insertions(+) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index ea1f0bfb..f258caf8 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -15,6 +15,7 @@ * For modders: * Added support for [message sending](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations#Message_sending) to mods on the current computer. + * Added `ExtendImage` method to content API when editing files to resize textures. * Added `helper.Input.GetStatus` to get the low-level status of a button. * **[Breaking change]** Map tilesheets are no loaded from `Content` if they can't be found in `Content/Maps`. This reflects an upcoming change in the game to delete map tilesheets under `Content`. * Improved map tilesheet errors so they provide more info. diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index aa615a0b..44a97136 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -1,6 +1,7 @@ using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using StardewValley; namespace StardewModdingAPI.Framework.Content { @@ -102,5 +103,21 @@ namespace StardewModdingAPI.Framework.Content // patch target texture target.SetData(0, targetArea, sourceData, 0, pixelCount); } + + /// Extend the image if needed to fit the given size. Note that this is an expensive operation, creates a new texture instance, and that extending a spritesheet horizontally may cause game errors or bugs. + /// The minimum texture width. + /// The minimum texture height. + /// Whether the texture was resized. + public bool ExtendImage(int minWidth, int minHeight) + { + if (this.Data.Width >= minWidth && this.Data.Height >= minHeight) + return false; + + Texture2D original = this.Data; + Texture2D texture = new Texture2D(Game1.graphics.GraphicsDevice, Math.Max(original.Width, minWidth), Math.Max(original.Height, minHeight)); + this.ReplaceWith(texture); + this.PatchImage(original); + return true; + } } } diff --git a/src/SMAPI/IAssetDataForImage.cs b/src/SMAPI/IAssetDataForImage.cs index 1109194f..27ed9267 100644 --- a/src/SMAPI/IAssetDataForImage.cs +++ b/src/SMAPI/IAssetDataForImage.cs @@ -19,5 +19,11 @@ namespace StardewModdingAPI /// The is outside the bounds of the spritesheet. /// The content being read isn't an image. void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace); + + /// Extend the image if needed to fit the given size. Note that this is an expensive operation, creates a new texture instance, and that extending a spritesheet horizontally may cause game errors or bugs. + /// The minimum texture width. + /// The minimum texture height. + /// Whether the texture was resized. + bool ExtendImage(int minWidth, int minHeight); } } -- cgit From f19722e021c277551d10561116d0b772a8f8d6c4 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 20 Feb 2020 21:54:39 -0500 Subject: tweak release notes --- docs/release-notes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index f258caf8..ee052b63 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -14,9 +14,9 @@ * Reduced log messages. * For modders: - * Added support for [message sending](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations#Message_sending) to mods on the current computer. + * Added support for [message sending](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations#Message_sending) to mods on the current computer (in addition to remote computers). * Added `ExtendImage` method to content API when editing files to resize textures. - * Added `helper.Input.GetStatus` to get the low-level status of a button. + * Added `helper.Input.GetState` to get the low-level state of a button. * **[Breaking change]** Map tilesheets are no loaded from `Content` if they can't be found in `Content/Maps`. This reflects an upcoming change in the game to delete map tilesheets under `Content`. * Improved map tilesheet errors so they provide more info. * Fixed dialogue propagation clearing marriage dialogue. -- cgit From 03c3ab3711c18f6b2bedfd02932b47bf368e1b82 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 22 Feb 2020 10:39:54 -0500 Subject: update SMAPI/game version mapping, add older versions --- docs/release-notes.md | 1 + src/SMAPI/Constants.cs | 53 ++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 44 insertions(+), 10 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index ee052b63..8ea6a9ea 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -7,6 +7,7 @@ * Reduced network traffic for mod broadcasts to players who can't process them. * Fixed update-check errors for recent versions of SMAPI on Android. * Updated compatibility list. + * Updated SMAPI/game version map. * Updated translations. Thanks to xCarloC (added Italian)! * For the Save Backup mod: diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 201d3166..443ca7a6 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -115,26 +115,59 @@ namespace StardewModdingAPI /// Returns the compatible SMAPI version, or null if none was found. internal static ISemanticVersion GetCompatibleApiVersion(ISemanticVersion version) { + // This covers all officially supported public game updates. It might seem like version + // ranges would be better, but the given SMAPI versions may not be compatible with + // intermediate unlisted versions (e.g. private beta updates). + // + // Nonstandard versions are normalized by GameVersion (e.g. 1.07 => 1.0.7). switch (version.ToString()) { + case "1.4.1": + case "1.4.0": + return new SemanticVersion("3.0.1"); + case "1.3.36": - return new SemanticVersion(2, 11, 2); + return new SemanticVersion("2.11.2"); - case "1.3.32": case "1.3.33": - return new SemanticVersion(2, 10, 2); + case "1.3.32": + return new SemanticVersion("2.10.2"); case "1.3.28": - return new SemanticVersion(2, 7, 0); + return new SemanticVersion("2.7.0"); - case "1.2.30": - case "1.2.31": - case "1.2.32": case "1.2.33": - return new SemanticVersion(2, 5, 5); - } + case "1.2.32": + case "1.2.31": + case "1.2.30": + return new SemanticVersion("2.5.5"); + + case "1.2.29": + case "1.2.28": + case "1.2.27": + case "1.2.26": + return new SemanticVersion("1.13.1"); + + case "1.1.1": + case "1.1.0": + return new SemanticVersion("1.9.0"); + + case "1.0.7.1": + case "1.0.7": + case "1.0.6": + case "1.0.5.2": + case "1.0.5.1": + case "1.0.5": + case "1.0.4": + case "1.0.3": + case "1.0.2": + case "1.0.1": + case "1.0.0": + return new SemanticVersion("0.40.0"); - return null; + default: + return null; + } } /// Get metadata for mapping assemblies to the current platform. -- cgit From d3ec98fec8077026d3a97a74d4efc471ab37ad67 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 22 Feb 2020 11:26:05 -0500 Subject: update packages --- docs/release-notes.md | 1 + .../SMAPI.ModBuildConfig.Analyzer.Tests.csproj | 2 +- src/SMAPI.Toolkit/SMAPI.Toolkit.csproj | 2 +- src/SMAPI.Web/SMAPI.Web.csproj | 6 +++--- src/SMAPI/SMAPI.csproj | 4 ++-- 5 files changed, 8 insertions(+), 7 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 8ea6a9ea..fe43412a 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -20,6 +20,7 @@ * Added `helper.Input.GetState` to get the low-level state of a button. * **[Breaking change]** Map tilesheets are no loaded from `Content` if they can't be found in `Content/Maps`. This reflects an upcoming change in the game to delete map tilesheets under `Content`. * Improved map tilesheet errors so they provide more info. + * Updated dependencies (including Mono.Cecil 0.11.1 → 0.11.2). * Fixed dialogue propagation clearing marriage dialogue. * Fixed issue where SMAPI didn't call `IAssetEditor` with the actual type if a mod loaded an asset using `content.Load`. diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj b/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj index c1d5626f..e2be66d9 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/SMAPI.ModBuildConfig.Analyzer.Tests.csproj @@ -7,7 +7,7 @@ - + all diff --git a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj index 16a97dbf..a7de7166 100644 --- a/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj +++ b/src/SMAPI.Toolkit/SMAPI.Toolkit.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/SMAPI.Web/SMAPI.Web.csproj b/src/SMAPI.Web/SMAPI.Web.csproj index 148631a9..97bea0fb 100644 --- a/src/SMAPI.Web/SMAPI.Web.csproj +++ b/src/SMAPI.Web/SMAPI.Web.csproj @@ -12,11 +12,11 @@ - + - + @@ -25,7 +25,7 @@ - + diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index c5d0f247..c17f13c4 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -15,9 +15,9 @@ - + - + -- cgit From b8636fdf2fb341029e16f0b9b51c07735d2a71d8 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 22 Feb 2020 11:59:10 -0500 Subject: update draw logic for recent game updates --- docs/release-notes.md | 1 + src/SMAPI/Framework/SGame.cs | 160 +++++++++++++++++++++++++++---------------- 2 files changed, 103 insertions(+), 58 deletions(-) (limited to 'docs') diff --git a/docs/release-notes.md b/docs/release-notes.md index fe43412a..05bb2c60 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -6,6 +6,7 @@ * Improved performance for mods which load a large number of images. * Reduced network traffic for mod broadcasts to players who can't process them. * Fixed update-check errors for recent versions of SMAPI on Android. + * Updated draw logic to match recent game updates. * Updated compatibility list. * Updated SMAPI/game version map. * Updated translations. Thanks to xCarloC (added Italian)! diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 6a472e3c..6b9c1365 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -893,6 +893,7 @@ namespace StardewModdingAPI.Framework { var events = this.Events; + Game1.showingHealthBar = false; if (Game1._newDayTask != null) { this.GraphicsDevice.Clear(Game1.bgColor); @@ -934,7 +935,7 @@ namespace StardewModdingAPI.Framework else { this.GraphicsDevice.Clear(Game1.bgColor); - if (Game1.activeClickableMenu != null && Game1.options.showMenuBackground && Game1.activeClickableMenu.showWithoutTransparencyIfOptionIsSet()) + if (Game1.activeClickableMenu != null && Game1.options.showMenuBackground && (Game1.activeClickableMenu.showWithoutTransparencyIfOptionIsSet() && !this.takingMapScreenshot)) { Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); @@ -1081,6 +1082,7 @@ namespace StardewModdingAPI.Framework { byte batchOpens = 0; // used for rendering event + Microsoft.Xna.Framework.Rectangle rectangle; Viewport viewport; if (Game1.gameMode == (byte)0) { @@ -1097,21 +1099,20 @@ namespace StardewModdingAPI.Framework Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); if (++batchOpens == 1) events.Rendering.RaiseEmpty(); - Microsoft.Xna.Framework.Color color = !Game1.currentLocation.Name.StartsWith("UndergroundMine") || !(Game1.currentLocation is MineShaft) ? (Game1.ambientLight.Equals(Microsoft.Xna.Framework.Color.White) || Game1.isRaining && (bool)((NetFieldBase)Game1.currentLocation.isOutdoors) ? Game1.outdoorLight : Game1.ambientLight) : (Game1.currentLocation as MineShaft).getLightingColor(gameTime); + Microsoft.Xna.Framework.Color color = !Game1.currentLocation.Name.StartsWith("UndergroundMine") || !(Game1.currentLocation is MineShaft) ? (Game1.ambientLight.Equals(Microsoft.Xna.Framework.Color.White) || Game1.isRaining && (bool)(NetFieldBase)Game1.currentLocation.isOutdoors ? Game1.outdoorLight : Game1.ambientLight) : (Game1.currentLocation as MineShaft).getLightingColor(gameTime); Game1.spriteBatch.Draw(Game1.staminaRect, Game1.lightmap.Bounds, color); - for (int index = 0; index < Game1.currentLightSources.Count; ++index) + foreach (LightSource currentLightSource in Game1.currentLightSources) { - LightSource lightSource = Game1.currentLightSources.ElementAt(index); - if (!Game1.isRaining && !Game1.isDarkOut() || lightSource.lightContext.Value != LightSource.LightContext.WindowLight) + if (!Game1.isRaining && !Game1.isDarkOut() || currentLightSource.lightContext.Value != LightSource.LightContext.WindowLight) { - if (lightSource.PlayerID != 0L && lightSource.PlayerID != Game1.player.UniqueMultiplayerID) + if (currentLightSource.PlayerID != 0L && currentLightSource.PlayerID != Game1.player.UniqueMultiplayerID) { - Farmer farmerMaybeOffline = Game1.getFarmerMaybeOffline(lightSource.PlayerID); - if (farmerMaybeOffline == null || farmerMaybeOffline.currentLocation != null && farmerMaybeOffline.currentLocation.Name != Game1.currentLocation.Name || (bool)((NetFieldBase)farmerMaybeOffline.hidden)) + Farmer farmerMaybeOffline = Game1.getFarmerMaybeOffline(currentLightSource.PlayerID); + if (farmerMaybeOffline == null || farmerMaybeOffline.currentLocation != null && farmerMaybeOffline.currentLocation.Name != Game1.currentLocation.Name || (bool)(NetFieldBase)farmerMaybeOffline.hidden) continue; } - if (Utility.isOnScreen((Vector2)((NetFieldBase)Game1.currentLightSources.ElementAt(index).position), (int)((double)(float)((NetFieldBase)Game1.currentLightSources.ElementAt(index).radius) * 64.0 * 4.0))) - Game1.spriteBatch.Draw(Game1.currentLightSources.ElementAt(index).lightTexture, Game1.GlobalToLocal(Game1.viewport, (Vector2)((NetFieldBase)Game1.currentLightSources.ElementAt(index).position)) / (float)(Game1.options.lightingQuality / 2), new Microsoft.Xna.Framework.Rectangle?(Game1.currentLightSources.ElementAt(index).lightTexture.Bounds), (Microsoft.Xna.Framework.Color)((NetFieldBase)Game1.currentLightSources.ElementAt(index).color), 0.0f, new Vector2((float)Game1.currentLightSources.ElementAt(index).lightTexture.Bounds.Center.X, (float)Game1.currentLightSources.ElementAt(index).lightTexture.Bounds.Center.Y), (float)((NetFieldBase)Game1.currentLightSources.ElementAt(index).radius) / (float)(Game1.options.lightingQuality / 2), SpriteEffects.None, 0.9f); + if (Utility.isOnScreen((Vector2)(NetFieldBase)currentLightSource.position, (int)((double)(float)(NetFieldBase)currentLightSource.radius * 64.0 * 4.0))) + Game1.spriteBatch.Draw(currentLightSource.lightTexture, Game1.GlobalToLocal(Game1.viewport, (Vector2)(NetFieldBase)currentLightSource.position) / (float)(Game1.options.lightingQuality / 2), new Microsoft.Xna.Framework.Rectangle?(currentLightSource.lightTexture.Bounds), (Microsoft.Xna.Framework.Color)(NetFieldBase)currentLightSource.color, 0.0f, new Vector2((float)currentLightSource.lightTexture.Bounds.Center.X, (float)currentLightSource.lightTexture.Bounds.Center.Y), (float)(NetFieldBase)currentLightSource.radius / (float)(Game1.options.lightingQuality / 2), SpriteEffects.None, 0.9f); } } Game1.spriteBatch.End(); @@ -1134,7 +1135,7 @@ namespace StardewModdingAPI.Framework { foreach (Farmer farmerActor in Game1.currentLocation.currentEvent.farmerActors) { - if (farmerActor.IsLocalPlayer && Game1.displayFarmer || !(bool)((NetFieldBase)farmerActor.hidden)) + if (farmerActor.IsLocalPlayer && Game1.displayFarmer || !(bool)(NetFieldBase)farmerActor.hidden) this._farmerShadows.Add(farmerActor); } } @@ -1142,7 +1143,7 @@ namespace StardewModdingAPI.Framework { foreach (Farmer farmer in Game1.currentLocation.farmers) { - if (farmer.IsLocalPlayer && Game1.displayFarmer || !(bool)((NetFieldBase)farmer.hidden)) + if (farmer.IsLocalPlayer && Game1.displayFarmer || !(bool)(NetFieldBase)farmer.hidden) this._farmerShadows.Add(farmer); } } @@ -1152,26 +1153,39 @@ namespace StardewModdingAPI.Framework { foreach (NPC character in Game1.currentLocation.characters) { - if (!(bool)((NetFieldBase)character.swimming) && !character.HideShadow && (!character.IsInvisible && !Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(character.getTileLocation()))) - Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, character.Position + new Vector2((float)(character.Sprite.SpriteWidth * 4) / 2f, (float)(character.GetBoundingBox().Height + (character.IsMonster ? 0 : 12)))), new Microsoft.Xna.Framework.Rectangle?(Game1.shadowTexture.Bounds), Microsoft.Xna.Framework.Color.White, 0.0f, new Vector2((float)Game1.shadowTexture.Bounds.Center.X, (float)Game1.shadowTexture.Bounds.Center.Y), (float)(4.0 + (double)character.yJumpOffset / 40.0) * (float)((NetFieldBase)character.scale), SpriteEffects.None, Math.Max(0.0f, (float)character.getStandingY() / 10000f) - 1E-06f); + if (!(bool)(NetFieldBase)character.swimming && !character.HideShadow && (!character.IsInvisible && !Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(character.getTileLocation()))) + Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, character.Position + new Vector2((float)(character.Sprite.SpriteWidth * 4) / 2f, (float)(character.GetBoundingBox().Height + (character.IsMonster ? 0 : 12)))), new Microsoft.Xna.Framework.Rectangle?(Game1.shadowTexture.Bounds), Microsoft.Xna.Framework.Color.White, 0.0f, new Vector2((float)Game1.shadowTexture.Bounds.Center.X, (float)Game1.shadowTexture.Bounds.Center.Y), (float)(4.0 + (double)character.yJumpOffset / 40.0) * (float)(NetFieldBase)character.scale, SpriteEffects.None, Math.Max(0.0f, (float)character.getStandingY() / 10000f) - 1E-06f); } } else { foreach (NPC actor in Game1.CurrentEvent.actors) { - if (!(bool)((NetFieldBase)actor.swimming) && !actor.HideShadow && !Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(actor.getTileLocation())) - Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, actor.Position + new Vector2((float)(actor.Sprite.SpriteWidth * 4) / 2f, (float)(actor.GetBoundingBox().Height + (actor.IsMonster ? 0 : (actor.Sprite.SpriteHeight <= 16 ? -4 : 12))))), new Microsoft.Xna.Framework.Rectangle?(Game1.shadowTexture.Bounds), Microsoft.Xna.Framework.Color.White, 0.0f, new Vector2((float)Game1.shadowTexture.Bounds.Center.X, (float)Game1.shadowTexture.Bounds.Center.Y), (float)(4.0 + (double)actor.yJumpOffset / 40.0) * (float)((NetFieldBase)actor.scale), SpriteEffects.None, Math.Max(0.0f, (float)actor.getStandingY() / 10000f) - 1E-06f); + if (!(bool)(NetFieldBase)actor.swimming && !actor.HideShadow && !Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(actor.getTileLocation())) + Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, actor.Position + new Vector2((float)(actor.Sprite.SpriteWidth * 4) / 2f, (float)(actor.GetBoundingBox().Height + (actor.IsMonster ? 0 : (actor.Sprite.SpriteHeight <= 16 ? -4 : 12))))), new Microsoft.Xna.Framework.Rectangle?(Game1.shadowTexture.Bounds), Microsoft.Xna.Framework.Color.White, 0.0f, new Vector2((float)Game1.shadowTexture.Bounds.Center.X, (float)Game1.shadowTexture.Bounds.Center.Y), (float)(4.0 + (double)actor.yJumpOffset / 40.0) * (float)(NetFieldBase)actor.scale, SpriteEffects.None, Math.Max(0.0f, (float)actor.getStandingY() / 10000f) - 1E-06f); } } foreach (Farmer farmerShadow in this._farmerShadows) { - if (!Game1.multiplayer.isDisconnecting(farmerShadow.UniqueMultiplayerID) && !(bool)((NetFieldBase)farmerShadow.swimming) && !farmerShadow.isRidingHorse() && (Game1.currentLocation == null || !Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(farmerShadow.getTileLocation()))) - Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(farmerShadow.Position + new Vector2(32f, 24f)), new Microsoft.Xna.Framework.Rectangle?(Game1.shadowTexture.Bounds), Microsoft.Xna.Framework.Color.White, 0.0f, new Vector2((float)Game1.shadowTexture.Bounds.Center.X, (float)Game1.shadowTexture.Bounds.Center.Y), (float)(4.0 - (!farmerShadow.running && !farmerShadow.UsingTool || farmerShadow.FarmerSprite.currentAnimationIndex <= 1 ? 0.0 : (double)Math.Abs(FarmerRenderer.featureYOffsetPerFrame[farmerShadow.FarmerSprite.CurrentFrame]) * 0.5)), SpriteEffects.None, 0.0f); + if (!Game1.multiplayer.isDisconnecting(farmerShadow.UniqueMultiplayerID) && !(bool)(NetFieldBase)farmerShadow.swimming && !farmerShadow.isRidingHorse() && (Game1.currentLocation == null || !Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(farmerShadow.getTileLocation()))) + { + SpriteBatch spriteBatch = Game1.spriteBatch; + Texture2D shadowTexture = Game1.shadowTexture; + Vector2 local = Game1.GlobalToLocal(farmerShadow.Position + new Vector2(32f, 24f)); + Microsoft.Xna.Framework.Rectangle? sourceRectangle = new Microsoft.Xna.Framework.Rectangle?(Game1.shadowTexture.Bounds); + Microsoft.Xna.Framework.Color white = Microsoft.Xna.Framework.Color.White; + Microsoft.Xna.Framework.Rectangle bounds = Game1.shadowTexture.Bounds; + double x = (double)bounds.Center.X; + bounds = Game1.shadowTexture.Bounds; + double y = (double)bounds.Center.Y; + Vector2 origin = new Vector2((float)x, (float)y); + double num = 4.0 - (!farmerShadow.running && !farmerShadow.UsingTool || farmerShadow.FarmerSprite.currentAnimationIndex <= 1 ? 0.0 : (double)Math.Abs(FarmerRenderer.featureYOffsetPerFrame[farmerShadow.FarmerSprite.CurrentFrame]) * 0.5); + spriteBatch.Draw(shadowTexture, local, sourceRectangle, white, 0.0f, origin, (float)num, SpriteEffects.None, 0.0f); + } } } - Layer layer = Game1.currentLocation.Map.GetLayer("Buildings"); - layer.Draw(Game1.mapDisplayDevice, Game1.viewport, Location.Origin, false, 4); + Layer layer1 = Game1.currentLocation.Map.GetLayer("Buildings"); + layer1.Draw(Game1.mapDisplayDevice, Game1.viewport, Location.Origin, false, 4); Game1.mapDisplayDevice.EndScene(); Game1.spriteBatch.End(); Game1.spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); @@ -1181,23 +1195,37 @@ namespace StardewModdingAPI.Framework { foreach (NPC character in Game1.currentLocation.characters) { - if (!(bool)((NetFieldBase)character.swimming) && !character.HideShadow && (!(bool)((NetFieldBase)character.isInvisible) && Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(character.getTileLocation()))) - Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, character.Position + new Vector2((float)(character.Sprite.SpriteWidth * 4) / 2f, (float)(character.GetBoundingBox().Height + (character.IsMonster ? 0 : 12)))), new Microsoft.Xna.Framework.Rectangle?(Game1.shadowTexture.Bounds), Microsoft.Xna.Framework.Color.White, 0.0f, new Vector2((float)Game1.shadowTexture.Bounds.Center.X, (float)Game1.shadowTexture.Bounds.Center.Y), (float)(4.0 + (double)character.yJumpOffset / 40.0) * (float)((NetFieldBase)character.scale), SpriteEffects.None, Math.Max(0.0f, (float)character.getStandingY() / 10000f) - 1E-06f); + if (!(bool)(NetFieldBase)character.swimming && !character.HideShadow && (!(bool)(NetFieldBase)character.isInvisible && Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(character.getTileLocation()))) + Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, character.Position + new Vector2((float)(character.Sprite.SpriteWidth * 4) / 2f, (float)(character.GetBoundingBox().Height + (character.IsMonster ? 0 : 12)))), new Microsoft.Xna.Framework.Rectangle?(Game1.shadowTexture.Bounds), Microsoft.Xna.Framework.Color.White, 0.0f, new Vector2((float)Game1.shadowTexture.Bounds.Center.X, (float)Game1.shadowTexture.Bounds.Center.Y), (float)(4.0 + (double)character.yJumpOffset / 40.0) * (float)(NetFieldBase)character.scale, SpriteEffects.None, Math.Max(0.0f, (float)character.getStandingY() / 10000f) - 1E-06f); } } else { foreach (NPC actor in Game1.CurrentEvent.actors) { - if (!(bool)((NetFieldBase)actor.swimming) && !actor.HideShadow && Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(actor.getTileLocation())) - Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, actor.Position + new Vector2((float)(actor.Sprite.SpriteWidth * 4) / 2f, (float)(actor.GetBoundingBox().Height + (actor.IsMonster ? 0 : 12)))), new Microsoft.Xna.Framework.Rectangle?(Game1.shadowTexture.Bounds), Microsoft.Xna.Framework.Color.White, 0.0f, new Vector2((float)Game1.shadowTexture.Bounds.Center.X, (float)Game1.shadowTexture.Bounds.Center.Y), (float)(4.0 + (double)actor.yJumpOffset / 40.0) * (float)((NetFieldBase)actor.scale), SpriteEffects.None, Math.Max(0.0f, (float)actor.getStandingY() / 10000f) - 1E-06f); + if (!(bool)(NetFieldBase)actor.swimming && !actor.HideShadow && Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(actor.getTileLocation())) + Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, actor.Position + new Vector2((float)(actor.Sprite.SpriteWidth * 4) / 2f, (float)(actor.GetBoundingBox().Height + (actor.IsMonster ? 0 : 12)))), new Microsoft.Xna.Framework.Rectangle?(Game1.shadowTexture.Bounds), Microsoft.Xna.Framework.Color.White, 0.0f, new Vector2((float)Game1.shadowTexture.Bounds.Center.X, (float)Game1.shadowTexture.Bounds.Center.Y), (float)(4.0 + (double)actor.yJumpOffset / 40.0) * (float)(NetFieldBase)actor.scale, SpriteEffects.None, Math.Max(0.0f, (float)actor.getStandingY() / 10000f) - 1E-06f); } } foreach (Farmer farmerShadow in this._farmerShadows) { - float layerDepth = Math.Max(0.0001f, farmerShadow.getDrawLayer() + 0.00011f) - 0.0001f; - if (!(bool)((NetFieldBase)farmerShadow.swimming) && !farmerShadow.isRidingHorse() && (Game1.currentLocation != null && Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(farmerShadow.getTileLocation()))) - Game1.spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(farmerShadow.Position + new Vector2(32f, 24f)), new Microsoft.Xna.Framework.Rectangle?(Game1.shadowTexture.Bounds), Microsoft.Xna.Framework.Color.White, 0.0f, new Vector2((float)Game1.shadowTexture.Bounds.Center.X, (float)Game1.shadowTexture.Bounds.Center.Y), (float)(4.0 - (!farmerShadow.running && !farmerShadow.UsingTool || farmerShadow.FarmerSprite.currentAnimationIndex <= 1 ? 0.0 : (double)Math.Abs(FarmerRenderer.featureYOffsetPerFrame[farmerShadow.FarmerSprite.CurrentFrame]) * 0.5)), SpriteEffects.None, layerDepth); + float num1 = Math.Max(0.0001f, farmerShadow.getDrawLayer() + 0.00011f) - 0.0001f; + if (!(bool)(NetFieldBase)farmerShadow.swimming && !farmerShadow.isRidingHorse() && (Game1.currentLocation != null && Game1.currentLocation.shouldShadowBeDrawnAboveBuildingsLayer(farmerShadow.getTileLocation()))) + { + SpriteBatch spriteBatch = Game1.spriteBatch; + Texture2D shadowTexture = Game1.shadowTexture; + Vector2 local = Game1.GlobalToLocal(farmerShadow.Position + new Vector2(32f, 24f)); + Microsoft.Xna.Framework.Rectangle? sourceRectangle = new Microsoft.Xna.Framework.Rectangle?(Game1.shadowTexture.Bounds); + Microsoft.Xna.Framework.Color white = Microsoft.Xna.Framework.Color.White; + Microsoft.Xna.Framework.Rectangle bounds = Game1.shadowTexture.Bounds; + double x = (double)bounds.Center.X; + bounds = Game1.shadowTexture.Bounds; + double y = (double)bounds.Center.Y; + Vector2 origin = new Vector2((float)x, (float)y); + double num2 = 4.0 - (!farmerShadow.running && !farmerShadow.UsingTool || farmerShadow.FarmerSprite.currentAnimationIndex <= 1 ? 0.0 : (double)Math.Abs(FarmerRenderer.featureYOffsetPerFrame[farmerShadow.FarmerSprite.CurrentFrame]) * 0.5); + double num3 = (double)num1; + spriteBatch.Draw(shadowTexture, local, sourceRectangle, white, 0.0f, origin, (float)num2, SpriteEffects.None, (float)num3); + } } } if ((Game1.eventUp || Game1.killScreen) && (!Game1.killScreen && Game1.currentLocation.currentEvent != null)) @@ -1207,7 +1235,7 @@ namespace StardewModdingAPI.Framework Game1.currentLocation.draw(Game1.spriteBatch); foreach (Vector2 key in Game1.crabPotOverlayTiles.Keys) { - Tile tile = layer.Tiles[(int)key.X, (int)key.Y]; + Tile tile = layer1.Tiles[(int)key.X, (int)key.Y]; if (tile != null) { Vector2 local = Game1.GlobalToLocal(Game1.viewport, key * 64f); @@ -1237,10 +1265,31 @@ namespace StardewModdingAPI.Framework Game1.currentLocation.drawAboveFrontLayer(Game1.spriteBatch); Game1.spriteBatch.End(); Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); - if (Game1.displayFarmer && Game1.player.ActiveObject != null && ((bool)((NetFieldBase)Game1.player.ActiveObject.bigCraftable) && this.checkBigCraftableBoundariesForFrontLayer()) && Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location(Game1.player.getStandingX(), Game1.player.getStandingY()), Game1.viewport.Size) == null) + if (Game1.displayFarmer && Game1.player.ActiveObject != null && ((bool)(NetFieldBase)Game1.player.ActiveObject.bigCraftable && this.checkBigCraftableBoundariesForFrontLayer()) && Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location(Game1.player.getStandingX(), Game1.player.getStandingY()), Game1.viewport.Size) == null) Game1.drawPlayerHeldObject(Game1.player); - else if (Game1.displayFarmer && Game1.player.ActiveObject != null && (Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location((int)Game1.player.Position.X, (int)Game1.player.Position.Y - 38), Game1.viewport.Size) != null && !Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location((int)Game1.player.Position.X, (int)Game1.player.Position.Y - 38), Game1.viewport.Size).TileIndexProperties.ContainsKey("FrontAlways") || Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location(Game1.player.GetBoundingBox().Right, (int)Game1.player.Position.Y - 38), Game1.viewport.Size) != null && !Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location(Game1.player.GetBoundingBox().Right, (int)Game1.player.Position.Y - 38), Game1.viewport.Size).TileIndexProperties.ContainsKey("FrontAlways"))) + else if (Game1.displayFarmer && Game1.player.ActiveObject != null) + { + if (Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location((int)Game1.player.Position.X, (int)Game1.player.Position.Y - 38), Game1.viewport.Size) == null || Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location((int)Game1.player.Position.X, (int)Game1.player.Position.Y - 38), Game1.viewport.Size).TileIndexProperties.ContainsKey("FrontAlways")) + { + Layer layer2 = Game1.currentLocation.Map.GetLayer("Front"); + rectangle = Game1.player.GetBoundingBox(); + Location mapDisplayLocation1 = new Location(rectangle.Right, (int)Game1.player.Position.Y - 38); + xTile.Dimensions.Size size1 = Game1.viewport.Size; + if (layer2.PickTile(mapDisplayLocation1, size1) != null) + { + Layer layer3 = Game1.currentLocation.Map.GetLayer("Front"); + rectangle = Game1.player.GetBoundingBox(); + Location mapDisplayLocation2 = new Location(rectangle.Right, (int)Game1.player.Position.Y - 38); + xTile.Dimensions.Size size2 = Game1.viewport.Size; + if (layer3.PickTile(mapDisplayLocation2, size2).TileIndexProperties.ContainsKey("FrontAlways")) + goto label_139; + } + else + goto label_139; + } Game1.drawPlayerHeldObject(Game1.player); + } + label_139: if ((Game1.player.UsingTool || Game1.pickingTool) && Game1.player.CurrentTool != null && ((!Game1.player.CurrentTool.Name.Equals("Seeds") || Game1.pickingTool) && (Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location(Game1.player.getStandingX(), (int)Game1.player.Position.Y - 38), Game1.viewport.Size) != null && Game1.currentLocation.Map.GetLayer("Front").PickTile(new Location(Game1.player.getStandingX(), Game1.player.getStandingY()), Game1.viewport.Size) == null))) Game1.drawTool(Game1.player); if (Game1.currentLocation.Map.GetLayer("AlwaysFront") != null) @@ -1274,23 +1323,9 @@ namespace StardewModdingAPI.Framework if (Game1.farmEvent != null) Game1.farmEvent.draw(Game1.spriteBatch); if ((double)Game1.currentLocation.LightLevel > 0.0 && Game1.timeOfDay < 2000) - { - SpriteBatch spriteBatch = Game1.spriteBatch; - Texture2D fadeToBlackRect = Game1.fadeToBlackRect; - viewport = Game1.graphics.GraphicsDevice.Viewport; - Microsoft.Xna.Framework.Rectangle bounds = viewport.Bounds; - Microsoft.Xna.Framework.Color color = Microsoft.Xna.Framework.Color.Black * Game1.currentLocation.LightLevel; - spriteBatch.Draw(fadeToBlackRect, bounds, color); - } + Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Microsoft.Xna.Framework.Color.Black * Game1.currentLocation.LightLevel); if (Game1.screenGlow) - { - SpriteBatch spriteBatch = Game1.spriteBatch; - Texture2D fadeToBlackRect = Game1.fadeToBlackRect; - viewport = Game1.graphics.GraphicsDevice.Viewport; - Microsoft.Xna.Framework.Rectangle bounds = viewport.Bounds; - Microsoft.Xna.Framework.Color color = Game1.screenGlowColor * Game1.screenGlowAlpha; - spriteBatch.Draw(fadeToBlackRect, bounds, color); - } + Game1.spriteBatch.Draw(Game1.fadeToBlackRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Game1.screenGlowColor * Game1.screenGlowAlpha); Game1.currentLocation.drawAboveAlwaysFrontLayer(Game1.spriteBatch); if (Game1.player.CurrentTool != null && Game1.player.CurrentTool is FishingRod && ((Game1.player.CurrentTool as FishingRod).isTimingCast || (double)(Game1.player.CurrentTool as FishingRod).castingChosenCountdown > 0.0 || ((Game1.player.CurrentTool as FishingRod).fishCaught || (Game1.player.CurrentTool as FishingRod).showingTreasure))) Game1.player.CurrentTool.draw(Game1.spriteBatch); @@ -1317,15 +1352,8 @@ namespace StardewModdingAPI.Framework { Game1.spriteBatch.Begin(SpriteSortMode.Deferred, this.lightingBlend, SamplerState.LinearClamp, (DepthStencilState)null, (RasterizerState)null); Game1.spriteBatch.Draw((Texture2D)Game1.lightmap, Vector2.Zero, new Microsoft.Xna.Framework.Rectangle?(Game1.lightmap.Bounds), Microsoft.Xna.Framework.Color.White, 0.0f, Vector2.Zero, (float)(Game1.options.lightingQuality / 2), SpriteEffects.None, 1f); - if (Game1.isRaining && (bool)((NetFieldBase)Game1.currentLocation.isOutdoors) && !(Game1.currentLocation is Desert)) - { - SpriteBatch spriteBatch = Game1.spriteBatch; - Texture2D staminaRect = Game1.staminaRect; - viewport = Game1.graphics.GraphicsDevice.Viewport; - Microsoft.Xna.Framework.Rectangle bounds = viewport.Bounds; - Microsoft.Xna.Framework.Color color = Microsoft.Xna.Framework.Color.OrangeRed * 0.45f; - spriteBatch.Draw(staminaRect, bounds, color); - } + if (Game1.isRaining && (bool)(NetFieldBase)Game1.currentLocation.isOutdoors && !(Game1.currentLocation is Desert)) + Game1.spriteBatch.Draw(Game1.staminaRect, Game1.graphics.GraphicsDevice.Viewport.Bounds, Microsoft.Xna.Framework.Color.OrangeRed * 0.45f); Game1.spriteBatch.End(); } Game1.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, (DepthStencilState)null, (RasterizerState)null); @@ -1424,12 +1452,28 @@ namespace StardewModdingAPI.Framework this.drawDialogueBox(); if (Game1.progressBar && !this.takingMapScreenshot) { - Game1.spriteBatch.Draw(Game1.fadeToBlackRect, new Microsoft.Xna.Framework.Rectangle((Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Width - Game1.dialogueWidth) / 2, Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Bottom - 128, Game1.dialogueWidth, 32), Microsoft.Xna.Framework.Color.LightGray); - Game1.spriteBatch.Draw(Game1.staminaRect, new Microsoft.Xna.Framework.Rectangle((Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Width - Game1.dialogueWidth) / 2, Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Bottom - 128, (int)((double)Game1.pauseAccumulator / (double)Game1.pauseTime * (double)Game1.dialogueWidth), 32), Microsoft.Xna.Framework.Color.DimGray); + SpriteBatch spriteBatch1 = Game1.spriteBatch; + Texture2D fadeToBlackRect = Game1.fadeToBlackRect; + int x1 = (Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Width - Game1.dialogueWidth) / 2; + rectangle = Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea(); + int y1 = rectangle.Bottom - 128; + int dialogueWidth = Game1.dialogueWidth; + Microsoft.Xna.Framework.Rectangle destinationRectangle1 = new Microsoft.Xna.Framework.Rectangle(x1, y1, dialogueWidth, 32); + Microsoft.Xna.Framework.Color lightGray = Microsoft.Xna.Framework.Color.LightGray; + spriteBatch1.Draw(fadeToBlackRect, destinationRectangle1, lightGray); + SpriteBatch spriteBatch2 = Game1.spriteBatch; + Texture2D staminaRect = Game1.staminaRect; + int x2 = (Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea().Width - Game1.dialogueWidth) / 2; + rectangle = Game1.graphics.GraphicsDevice.Viewport.GetTitleSafeArea(); + int y2 = rectangle.Bottom - 128; + int width = (int)((double)Game1.pauseAccumulator / (double)Game1.pauseTime * (double)Game1.dialogueWidth); + Microsoft.Xna.Framework.Rectangle destinationRectangle2 = new Microsoft.Xna.Framework.Rectangle(x2, y2, width, 32); + Microsoft.Xna.Framework.Color dimGray = Microsoft.Xna.Framework.Color.DimGray; + spriteBatch2.Draw(staminaRect, destinationRectangle2, dimGray); } if (Game1.eventUp && Game1.currentLocation != null && Game1.currentLocation.currentEvent != null) Game1.currentLocation.currentEvent.drawAfterMap(Game1.spriteBatch); - if (Game1.isRaining && Game1.currentLocation != null && ((bool)((NetFieldBase)Game1.currentLocation.isOutdoors) && !(Game1.currentLocation is Desert))) + if (Game1.isRaining && Game1.currentLocation != null && ((bool)(NetFieldBase)Game1.currentLocation.isOutdoors && !(Game1.currentLocation is Desert))) { SpriteBatch spriteBatch = Game1.spriteBatch; Texture2D staminaRect = Game1.staminaRect; -- cgit From 585b23797e262073e0738069eff61e90819216b7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 22 Feb 2020 12:03:09 -0500 Subject: prepare for release --- build/common.targets | 2 +- docs/release-notes.md | 11 +++++++---- src/SMAPI.Mods.ConsoleCommands/manifest.json | 4 ++-- src/SMAPI.Mods.SaveBackup/manifest.json | 4 ++-- src/SMAPI/Constants.cs | 2 +- 5 files changed, 13 insertions(+), 10 deletions(-) (limited to 'docs') diff --git a/build/common.targets b/build/common.targets index 8b0d1301..626eeef6 100644 --- a/build/common.targets +++ b/build/common.targets @@ -4,7 +4,7 @@ - 3.2.0 + 3.3.0 SMAPI $(AssemblySearchPaths);{GAC} diff --git a/docs/release-notes.md b/docs/release-notes.md index 05bb2c60..26515b61 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,9 +1,11 @@ ← [README](README.md) # Release notes -## Upcoming release +## 3.3 +Released 22 February 2020 for Stardew Valley 1.4.1 or later. + * For players: - * Improved performance for mods which load a large number of images. + * Improved performance for mods which load many images. * Reduced network traffic for mod broadcasts to players who can't process them. * Fixed update-check errors for recent versions of SMAPI on Android. * Updated draw logic to match recent game updates. @@ -19,17 +21,18 @@ * Added support for [message sending](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations#Message_sending) to mods on the current computer (in addition to remote computers). * Added `ExtendImage` method to content API when editing files to resize textures. * Added `helper.Input.GetState` to get the low-level state of a button. - * **[Breaking change]** Map tilesheets are no loaded from `Content` if they can't be found in `Content/Maps`. This reflects an upcoming change in the game to delete map tilesheets under `Content`. + * **[Breaking change]** Map tilesheets are no loaded from `Content` if they can't be found in `Content/Maps`. This reflects an upcoming change in the game to delete duplicate map tilesheets under `Content`. Most mods should be unaffected. * Improved map tilesheet errors so they provide more info. + * When mods load an asset using a more general type like `content.Load`, SMAPI now calls `IAssetEditor` instances with the actual asset type instead of the specified one. * Updated dependencies (including Mono.Cecil 0.11.1 → 0.11.2). * Fixed dialogue propagation clearing marriage dialogue. - * Fixed issue where SMAPI didn't call `IAssetEditor` with the actual type if a mod loaded an asset using `content.Load`. * For the web UI: * Updated the JSON validator and Content Patcher schema for `.tmx` support. * The mod compatibility page now has a sticky table header. * For SMAPI/tool developers: + * Improved support for four-part versions to support SMAPI on Android. * The SMAPI log now prefixes the OS name with `Android` on Android. ## 3.2 diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index 0d0e4901..971c591a 100644 --- a/src/SMAPI.Mods.ConsoleCommands/manifest.json +++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json @@ -1,9 +1,9 @@ { "Name": "Console Commands", "Author": "SMAPI", - "Version": "3.2.0", + "Version": "3.3.0", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.2.0" + "MinimumApiVersion": "3.3.0" } diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index 74256013..4559d1b0 100644 --- a/src/SMAPI.Mods.SaveBackup/manifest.json +++ b/src/SMAPI.Mods.SaveBackup/manifest.json @@ -1,9 +1,9 @@ { "Name": "Save Backup", "Author": "SMAPI", - "Version": "3.2.0", + "Version": "3.3.0", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "3.2.0" + "MinimumApiVersion": "3.3.0" } diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 443ca7a6..670dc494 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -20,7 +20,7 @@ namespace StardewModdingAPI ** Public ****/ /// SMAPI's current semantic version. - public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.2.0"); + public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.3.0"); /// The minimum supported version of Stardew Valley. public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.4.1"); -- cgit