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(-) 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(-) 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(-) 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(-) 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 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 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(-) 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(+) 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(-) 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 da49c7c13bbb0e47aa07f9c793640559c123ee3c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 17 Feb 2020 23:48:57 -0500 Subject: move hardcoded SMAPI update check logic into config --- src/SMAPI.Web/Controllers/ModsApiController.cs | 34 ++++++++++------------ .../Framework/ConfigModels/ModOverrideConfig.cs | 15 ++++++++++ .../Framework/ConfigModels/ModUpdateCheckConfig.cs | 3 ++ src/SMAPI.Web/appsettings.json | 9 +++++- 4 files changed, 41 insertions(+), 20 deletions(-) create mode 100644 src/SMAPI.Web/Framework/ConfigModels/ModOverrideConfig.cs diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs index 8e65b6b9..8370fe09 100644 --- a/src/SMAPI.Web/Controllers/ModsApiController.cs +++ b/src/SMAPI.Web/Controllers/ModsApiController.cs @@ -41,11 +41,8 @@ namespace StardewModdingAPI.Web.Controllers /// The cache in which to store mod data. private readonly IModCacheRepository ModCache; - /// The number of minutes successful update checks should be cached before refetching them. - private readonly int SuccessCacheMinutes; - - /// The number of minutes failed update checks should be cached before refetching them. - private readonly int ErrorCacheMinutes; + /// The config settings for mod update checks. + private readonly IOptions Config; /// The internal mod metadata list. private readonly ModDatabase ModDatabase; @@ -58,21 +55,19 @@ namespace StardewModdingAPI.Web.Controllers /// The web hosting environment. /// The cache in which to store wiki data. /// The cache in which to store mod metadata. - /// The config settings for mod update checks. + /// The config settings for mod update checks. /// The Chucklefish API client. /// The CurseForge API client. /// The GitHub API client. /// The ModDrop API client. /// The Nexus API client. - public ModsApiController(IHostingEnvironment environment, IWikiCacheRepository wikiCache, IModCacheRepository modCache, IOptions configProvider, IChucklefishClient chucklefish, ICurseForgeClient curseForge, IGitHubClient github, IModDropClient modDrop, INexusClient nexus) + public ModsApiController(IHostingEnvironment environment, IWikiCacheRepository wikiCache, IModCacheRepository modCache, IOptions config, IChucklefishClient chucklefish, ICurseForgeClient curseForge, IGitHubClient github, IModDropClient modDrop, INexusClient nexus) { this.ModDatabase = new ModToolkit().GetModDatabase(Path.Combine(environment.WebRootPath, "SMAPI.metadata.json")); - ModUpdateCheckConfig config = configProvider.Value; this.WikiCache = wikiCache; this.ModCache = modCache; - this.SuccessCacheMinutes = config.SuccessCacheMinutes; - this.ErrorCacheMinutes = config.ErrorCacheMinutes; + this.Config = config; this.Repositories = new IModRepository[] { @@ -133,7 +128,8 @@ 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"; + ModOverrideConfig overrides = this.Config.Value.ModOverrides.FirstOrDefault(p => p.ID.Equals(search.ID?.Trim(), StringComparison.InvariantCultureIgnoreCase)); + bool allowNonStandardVersions = overrides?.AllowNonStandardVersions ?? false; // get latest versions ModEntryModel result = new ModEntryModel { ID = search.ID }; @@ -152,7 +148,7 @@ namespace StardewModdingAPI.Web.Controllers } // fetch data - ModInfoModel data = await this.GetInfoForUpdateKeyAsync(updateKey, allowNonStandardVersions: isSmapi); + ModInfoModel data = await this.GetInfoForUpdateKeyAsync(updateKey, allowNonStandardVersions); if (data.Error != null) { errors.Add(data.Error); @@ -162,7 +158,7 @@ namespace StardewModdingAPI.Web.Controllers // handle main version if (data.Version != null) { - ISemanticVersion version = this.GetMappedVersion(data.Version, wikiEntry?.MapRemoteVersions, allowNonStandard: isSmapi); + ISemanticVersion version = this.GetMappedVersion(data.Version, wikiEntry?.MapRemoteVersions, allowNonStandardVersions); if (version == null) { errors.Add($"The update key '{updateKey}' matches a mod with invalid semantic version '{data.Version}'."); @@ -176,7 +172,7 @@ namespace StardewModdingAPI.Web.Controllers // handle optional version if (data.PreviewVersion != null) { - ISemanticVersion version = this.GetMappedVersion(data.PreviewVersion, wikiEntry?.MapRemoteVersions, allowNonStandard: isSmapi); + ISemanticVersion version = this.GetMappedVersion(data.PreviewVersion, wikiEntry?.MapRemoteVersions, allowNonStandardVersions); if (version == null) { errors.Add($"The update key '{updateKey}' matches a mod with invalid optional semantic version '{data.PreviewVersion}'."); @@ -216,16 +212,16 @@ namespace StardewModdingAPI.Web.Controllers } // special cases - if (isSmapi) + if (overrides?.SetUrl != null) { if (main != null) - main.Url = "https://smapi.io/"; + main.Url = overrides.SetUrl; if (optional != null) - optional.Url = "https://smapi.io/"; + optional.Url = overrides.SetUrl; } // get recommended update (if any) - ISemanticVersion installedVersion = this.GetMappedVersion(search.InstalledVersion?.ToString(), wikiEntry?.MapLocalVersions, allowNonStandard: isSmapi); + ISemanticVersion installedVersion = this.GetMappedVersion(search.InstalledVersion?.ToString(), wikiEntry?.MapLocalVersions, allowNonStandard: allowNonStandardVersions); if (apiVersion != null && installedVersion != null) { // get newer versions @@ -288,7 +284,7 @@ namespace StardewModdingAPI.Web.Controllers 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)) + if (!this.ModCache.TryGetMod(updateKey.Repository, updateKey.ID, out CachedMod mod) || this.ModCache.IsStale(mod.LastUpdated, mod.FetchStatus == RemoteModStatus.TemporaryError ? this.Config.Value.ErrorCacheMinutes : this.Config.Value.SuccessCacheMinutes)) { // get site if (!this.Repositories.TryGetValue(updateKey.Repository, out IModRepository repository)) diff --git a/src/SMAPI.Web/Framework/ConfigModels/ModOverrideConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/ModOverrideConfig.cs new file mode 100644 index 00000000..f382d7b5 --- /dev/null +++ b/src/SMAPI.Web/Framework/ConfigModels/ModOverrideConfig.cs @@ -0,0 +1,15 @@ +namespace StardewModdingAPI.Web.Framework.ConfigModels +{ + /// Override update-check metadata for a mod. + internal class ModOverrideConfig + { + /// The unique ID from the mod's manifest. + public string ID { get; set; } + + /// Whether to allow non-standard versions. + public bool AllowNonStandardVersions { get; set; } + + /// The mod page URL to use regardless of which site has the update, or null to use the site URL. + public string SetUrl { get; set; } + } +} diff --git a/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs index 46073eb8..bd58dba0 100644 --- a/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs +++ b/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs @@ -11,5 +11,8 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels /// The number of minutes failed update checks should be cached before refetching them. public int ErrorCacheMinutes { get; set; } + + /// Update-check metadata to override. + public ModOverrideConfig[] ModOverrides { get; set; } } } diff --git a/src/SMAPI.Web/appsettings.json b/src/SMAPI.Web/appsettings.json index caeb381f..e1316c97 100644 --- a/src/SMAPI.Web/appsettings.json +++ b/src/SMAPI.Web/appsettings.json @@ -64,6 +64,13 @@ "ModUpdateCheck": { "SuccessCacheMinutes": 60, - "ErrorCacheMinutes": 5 + "ErrorCacheMinutes": 5, + "ModOverrides": [ + { + "ID": "Pathoschild.SMAPI", + "AllowNonStandardVersions": true, + "SetUrl": "https://smapi.io" + } + ] } } -- cgit From 383ebab1af5cfa6537c2eabd7a990fc26cb6dd67 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 17 Feb 2020 23:49:35 -0500 Subject: add overrides for SMAPI on Android --- src/SMAPI.Web/appsettings.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/SMAPI.Web/appsettings.json b/src/SMAPI.Web/appsettings.json index e1316c97..9cd1efc8 100644 --- a/src/SMAPI.Web/appsettings.json +++ b/src/SMAPI.Web/appsettings.json @@ -70,6 +70,10 @@ "ID": "Pathoschild.SMAPI", "AllowNonStandardVersions": true, "SetUrl": "https://smapi.io" + }, + { + "ID": "MartyrPher.SMAPI-Android-Installer", + "AllowNonStandardVersions": true } ] } -- 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(+) 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(-) 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 7f63e065917f8879f42dbac92748702bfb6d044d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 18 Feb 2020 00:31:36 -0500 Subject: fix anchor position for new sticky header --- src/SMAPI.Web/Views/Mods/Index.cshtml | 4 ++-- src/SMAPI.Web/wwwroot/Content/js/mods.js | 24 +++++++++++++++++------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/SMAPI.Web/Views/Mods/Index.cshtml b/src/SMAPI.Web/Views/Mods/Index.cshtml index 5b310d55..b1d9ae2c 100644 --- a/src/SMAPI.Web/Views/Mods/Index.cshtml +++ b/src/SMAPI.Web/Views/Mods/Index.cshtml @@ -8,11 +8,11 @@ TimeSpan staleAge = DateTimeOffset.UtcNow - Model.LastUpdated; } @section Head { - + - +