From 436c071ba4ecbe43769b438277d441057d05403e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 15 Mar 2018 19:52:18 -0400 Subject: add support for preview GitHub releases (#457) --- src/SMAPI.Common/Models/ModInfoModel.cs | 12 ++++-- .../Framework/Clients/GitHub/GitHubClient.cs | 50 ++++++++++++++++------ .../Framework/Clients/GitHub/GitRelease.cs | 4 ++ .../Framework/Clients/GitHub/IGitHubClient.cs | 5 ++- .../Framework/ConfigModels/ApiClientsConfig.cs | 7 ++- .../ModRepositories/ChucklefishRepository.cs | 2 +- .../Framework/ModRepositories/GitHubRepository.cs | 19 ++++++-- .../Framework/ModRepositories/NexusRepository.cs | 2 +- src/SMAPI.Web/Startup.cs | 3 +- src/SMAPI.Web/appsettings.json | 3 +- 10 files changed, 78 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/SMAPI.Common/Models/ModInfoModel.cs b/src/SMAPI.Common/Models/ModInfoModel.cs index 48305cb8..48df235a 100644 --- a/src/SMAPI.Common/Models/ModInfoModel.cs +++ b/src/SMAPI.Common/Models/ModInfoModel.cs @@ -9,9 +9,12 @@ namespace StardewModdingAPI.Common.Models /// The mod name. public string Name { get; set; } - /// The mod's semantic version number. + /// The semantic version for the mod's latest release. public string Version { get; set; } + /// The semantic version for the mod's latest preview release, if available and different from . + public string PreviewVersion { get; set; } + /// The mod's web URL. public string Url { get; set; } @@ -28,16 +31,17 @@ namespace StardewModdingAPI.Common.Models // needed for JSON deserialising } - /// Construct an instance. /// The mod name. - /// The mod's semantic version number. + /// The semantic version for the mod's latest release. + /// The semantic version for the mod's latest preview release, if available and different from . /// The mod's web URL. /// The error message indicating why the mod is invalid (if applicable). - public ModInfoModel(string name, string version, string url, string error = null) + public ModInfoModel(string name, string version, string url, string previewVersion = null, string error = null) { this.Name = name; this.Version = version; + this.PreviewVersion = previewVersion; this.Url = url; this.Error = error; // mainly initialised here for the JSON deserialiser } diff --git a/src/SMAPI.Web/Framework/Clients/GitHub/GitHubClient.cs b/src/SMAPI.Web/Framework/Clients/GitHub/GitHubClient.cs index 0b205660..4abe0737 100644 --- a/src/SMAPI.Web/Framework/Clients/GitHub/GitHubClient.cs +++ b/src/SMAPI.Web/Framework/Clients/GitHub/GitHubClient.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Net; using System.Threading.Tasks; using Pathoschild.Http.Client; @@ -11,8 +12,11 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub /********* ** Properties *********/ - /// The URL for a GitHub releases API query excluding the base URL, where {0} is the repository owner and name. - private readonly string ReleaseUrlFormat; + /// The URL for a GitHub API query for the latest stable release, excluding the base URL, where {0} is the organisation and project name. + private readonly string StableReleaseUrlFormat; + + /// The URL for a GitHub API query for the latest release (including prerelease), excluding the base URL, where {0} is the organisation and project name. + private readonly string AnyReleaseUrlFormat; /// The underlying HTTP client. private readonly IClient Client; @@ -23,14 +27,16 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub *********/ /// Construct an instance. /// The base URL for the GitHub API. - /// The URL for a GitHub releases API query excluding the , where {0} is the repository owner and name. + /// The URL for a GitHub API query for the latest stable release, excluding the , where {0} is the organisation and project name. + /// The URL for a GitHub API query for the latest release (including prerelease), excluding the , where {0} is the organisation and project name. /// The user agent for the API client. /// The Accept header value expected by the GitHub API. /// The username with which to authenticate to the GitHub API. /// The password with which to authenticate to the GitHub API. - public GitHubClient(string baseUrl, string releaseUrlFormat, string userAgent, string acceptHeader, string username, string password) + public GitHubClient(string baseUrl, string stableReleaseUrlFormat, string anyReleaseUrlFormat, string userAgent, string acceptHeader, string username, string password) { - this.ReleaseUrlFormat = releaseUrlFormat; + this.StableReleaseUrlFormat = stableReleaseUrlFormat; + this.AnyReleaseUrlFormat = anyReleaseUrlFormat; this.Client = new FluentClient(baseUrl) .SetUserAgent(userAgent) @@ -41,18 +47,23 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub /// Get the latest release for a GitHub repository. /// The repository key (like Pathoschild/SMAPI). - /// Returns the latest release if found, else null. - public async Task GetLatestReleaseAsync(string repo) + /// Whether to return a prerelease version if it's latest. + /// Returns the release if found, else null. + public async Task GetLatestReleaseAsync(string repo, bool includePrerelease = false) { - // validate key format - if (!repo.Contains("/") || repo.IndexOf("/", StringComparison.InvariantCultureIgnoreCase) != repo.LastIndexOf("/", StringComparison.InvariantCultureIgnoreCase)) - throw new ArgumentException($"The value '{repo}' isn't a valid GitHub repository key, must be a username and project name like 'Pathoschild/SMAPI'.", nameof(repo)); - - // fetch info + this.AssetKeyFormat(repo); try { + if (includePrerelease) + { + GitRelease[] results = await this.Client + .GetAsync(string.Format(this.AnyReleaseUrlFormat, repo)) + .AsArray(); + return results.FirstOrDefault(); + } + return await this.Client - .GetAsync(string.Format(this.ReleaseUrlFormat, repo)) + .GetAsync(string.Format(this.StableReleaseUrlFormat, repo)) .As(); } catch (ApiException ex) when (ex.Status == HttpStatusCode.NotFound) @@ -66,5 +77,18 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub { this.Client?.Dispose(); } + + + /********* + ** Private methods + *********/ + /// Assert that a repository key is formatted correctly. + /// The repository key (like Pathoschild/SMAPI). + /// The repository key is invalid. + private void AssetKeyFormat(string repo) + { + if (repo == null || !repo.Contains("/") || repo.IndexOf("/", StringComparison.InvariantCultureIgnoreCase) != repo.LastIndexOf("/", StringComparison.InvariantCultureIgnoreCase)) + throw new ArgumentException($"The value '{repo}' isn't a valid GitHub repository key, must be a username and project name like 'Pathoschild/SMAPI'.", nameof(repo)); + } } } diff --git a/src/SMAPI.Web/Framework/Clients/GitHub/GitRelease.cs b/src/SMAPI.Web/Framework/Clients/GitHub/GitRelease.cs index b944088d..827374fb 100644 --- a/src/SMAPI.Web/Framework/Clients/GitHub/GitRelease.cs +++ b/src/SMAPI.Web/Framework/Clients/GitHub/GitRelease.cs @@ -19,6 +19,10 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub /// The Markdown description for the release. public string Body { get; set; } + /// Whether this is a prerelease version. + [JsonProperty("prerelease")] + public bool IsPrerelease { get; set; } + /// The attached files. public GitAsset[] Assets { get; set; } } diff --git a/src/SMAPI.Web/Framework/Clients/GitHub/IGitHubClient.cs b/src/SMAPI.Web/Framework/Clients/GitHub/IGitHubClient.cs index 6e8eadff..9519c26f 100644 --- a/src/SMAPI.Web/Framework/Clients/GitHub/IGitHubClient.cs +++ b/src/SMAPI.Web/Framework/Clients/GitHub/IGitHubClient.cs @@ -11,7 +11,8 @@ namespace StardewModdingAPI.Web.Framework.Clients.GitHub *********/ /// Get the latest release for a GitHub repository. /// The repository key (like Pathoschild/SMAPI). - /// Returns the latest release if found, else null. - Task GetLatestReleaseAsync(string repo); + /// Whether to return a prerelease version if it's latest. + /// Returns the release if found, else null. + Task GetLatestReleaseAsync(string repo, bool includePrerelease = false); } } diff --git a/src/SMAPI.Web/Framework/ConfigModels/ApiClientsConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/ApiClientsConfig.cs index 61219414..de6c024a 100644 --- a/src/SMAPI.Web/Framework/ConfigModels/ApiClientsConfig.cs +++ b/src/SMAPI.Web/Framework/ConfigModels/ApiClientsConfig.cs @@ -29,8 +29,11 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels /// The base URL for the GitHub API. public string GitHubBaseUrl { get; set; } - /// The URL for a GitHub API latest-release query excluding the , where {0} is the organisation and project name. - public string GitHubReleaseUrlFormat { get; set; } + /// The URL for a GitHub API query for the latest stable release, excluding the , where {0} is the organisation and project name. + public string GitHubStableReleaseUrlFormat { get; set; } + + /// The URL for a GitHub API query for the latest release (including prerelease), excluding the , where {0} is the organisation and project name. + public string GitHubAnyReleaseUrlFormat { get; set; } /// The Accept header value expected by the GitHub API. public string GitHubAcceptHeader { get; set; } diff --git a/src/SMAPI.Web/Framework/ModRepositories/ChucklefishRepository.cs b/src/SMAPI.Web/Framework/ModRepositories/ChucklefishRepository.cs index 266055a6..3e5a4272 100644 --- a/src/SMAPI.Web/Framework/ModRepositories/ChucklefishRepository.cs +++ b/src/SMAPI.Web/Framework/ModRepositories/ChucklefishRepository.cs @@ -43,7 +43,7 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories return new ModInfoModel("Found no mod with this ID."); // create model - return new ModInfoModel(mod.Name, this.NormaliseVersion(mod.Version), mod.Url); + return new ModInfoModel(name: mod.Name, version: this.NormaliseVersion(mod.Version), url: mod.Url); } catch (Exception ex) { diff --git a/src/SMAPI.Web/Framework/ModRepositories/GitHubRepository.cs b/src/SMAPI.Web/Framework/ModRepositories/GitHubRepository.cs index 7bad6127..59eb8cd1 100644 --- a/src/SMAPI.Web/Framework/ModRepositories/GitHubRepository.cs +++ b/src/SMAPI.Web/Framework/ModRepositories/GitHubRepository.cs @@ -38,10 +38,21 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories // fetch info try { - GitRelease release = await this.Client.GetLatestReleaseAsync(id); - return release != null - ? new ModInfoModel(id, this.NormaliseVersion(release.Tag), $"https://github.com/{id}/releases") - : new ModInfoModel("Found no mod with this ID."); + // get latest release + GitRelease latest = await this.Client.GetLatestReleaseAsync(id, includePrerelease: true); + GitRelease preview = null; + if (latest == null) + return new ModInfoModel("Found no mod with this ID."); + + // get latest stable release (if not latest) + if (latest.IsPrerelease) + { + preview = latest; + latest = await this.Client.GetLatestReleaseAsync(id, includePrerelease: false); + } + + // return data + return new ModInfoModel(name: id, version: this.NormaliseVersion(latest?.Tag), previewVersion: this.NormaliseVersion(preview?.Tag), url: $"https://github.com/{id}/releases"); } catch (Exception ex) { diff --git a/src/SMAPI.Web/Framework/ModRepositories/NexusRepository.cs b/src/SMAPI.Web/Framework/ModRepositories/NexusRepository.cs index e1dc0fcc..6411ad4c 100644 --- a/src/SMAPI.Web/Framework/ModRepositories/NexusRepository.cs +++ b/src/SMAPI.Web/Framework/ModRepositories/NexusRepository.cs @@ -43,7 +43,7 @@ namespace StardewModdingAPI.Web.Framework.ModRepositories return new ModInfoModel("Found no mod with this ID."); if (mod.Error != null) return new ModInfoModel(mod.Error); - return new ModInfoModel(mod.Name, this.NormaliseVersion(mod.Version), mod.Url); + return new ModInfoModel(name: mod.Name, version: this.NormaliseVersion(mod.Version), url: mod.Url); } catch (Exception ex) { diff --git a/src/SMAPI.Web/Startup.cs b/src/SMAPI.Web/Startup.cs index d7d4d074..47102e5c 100644 --- a/src/SMAPI.Web/Startup.cs +++ b/src/SMAPI.Web/Startup.cs @@ -74,7 +74,8 @@ namespace StardewModdingAPI.Web services.AddSingleton(new GitHubClient( baseUrl: api.GitHubBaseUrl, - releaseUrlFormat: api.GitHubReleaseUrlFormat, + stableReleaseUrlFormat: api.GitHubStableReleaseUrlFormat, + anyReleaseUrlFormat: api.GitHubAnyReleaseUrlFormat, userAgent: userAgent, acceptHeader: api.GitHubAcceptHeader, username: api.GitHubUsername, diff --git a/src/SMAPI.Web/appsettings.json b/src/SMAPI.Web/appsettings.json index 3cf72ddb..bfe827fa 100644 --- a/src/SMAPI.Web/appsettings.json +++ b/src/SMAPI.Web/appsettings.json @@ -24,7 +24,8 @@ "ChucklefishModPageUrlFormat": "resources/{0}", "GitHubBaseUrl": "https://api.github.com", - "GitHubReleaseUrlFormat": "repos/{0}/releases/latest", + "GitHubStableReleaseUrlFormat": "repos/{0}/releases/latest", + "GitHubAnyReleaseUrlFormat": "repos/{0}/releases?per_page=1", "GitHubAcceptHeader": "application/vnd.github.v3+json", "GitHubUsername": null, // see top note "GitHubPassword": null, // see top note -- cgit From 7015e4ee8777c4a1c3934c1f3e9c3ceefcc1295d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 15 Mar 2018 20:58:43 -0400 Subject: show prerelease SMAPI updates when updating from an older prerelease of the same version (#457) --- src/SMAPI/Program.cs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 4bd40710..e4b279f7 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -541,8 +541,10 @@ namespace StardewModdingAPI this.Monitor.Log("Couldn't check for a new version of SMAPI. This won't affect your game, but you may not be notified of new versions if this keeps happening.", LogLevel.Warn); this.Monitor.Log($"Error: {response.Error}"); } - else if (new SemanticVersion(response.Version).IsNewerThan(Constants.ApiVersion)) + else if (this.IsValidUpdate(Constants.ApiVersion, new SemanticVersion(response.Version))) this.Monitor.Log($"You can update SMAPI to {response.Version}: {response.Url}", LogLevel.Alert); + else if (response.PreviewVersion != null && this.IsValidUpdate(Constants.ApiVersion, new SemanticVersion(response.PreviewVersion))) + this.Monitor.Log($"You can update SMAPI to {response.PreviewVersion}: {response.Url}", LogLevel.Alert); else this.Monitor.Log(" SMAPI okay.", LogLevel.Trace); } @@ -656,6 +658,27 @@ namespace StardewModdingAPI }).Start(); } + /// Get whether a given version should be offered to the user as an update. + /// The current semantic version. + /// The target semantic version. + private bool IsValidUpdate(ISemanticVersion currentVersion, ISemanticVersion newVersion) + { + // basic eligibility + bool isNewer = newVersion.IsNewerThan(currentVersion); + bool isPrerelease = newVersion.Build != null; + bool isEquallyStable = !isPrerelease || currentVersion.Build != null; // don't update stable => prerelease + if (!isNewer || !isEquallyStable) + return false; + if (!isPrerelease) + return true; + + // prerelease eligible if same version (excluding prerelease tag) + return + newVersion.MajorVersion == currentVersion.MajorVersion + && newVersion.MinorVersion == currentVersion.MinorVersion + && newVersion.PatchVersion == currentVersion.PatchVersion; + } + /// Create a directory path if it doesn't exist. /// The directory path. private void VerifyPath(string path) -- cgit From 90cdbdf7b29ddeee80b213957a02e0e5c691282e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 15 Mar 2018 21:23:36 -0400 Subject: link SMAPI update checks to smapi.io instead of GitHub (#457) --- src/SMAPI/Constants.cs | 4 ++-- src/SMAPI/Program.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index d91fa5fb..7a497a53 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -81,8 +81,8 @@ namespace StardewModdingAPI /**** ** Internal ****/ - /// The GitHub repository to check for updates. - internal const string GitHubRepository = "Pathoschild/SMAPI"; + /// The URL of the SMAPI home page. + internal const string HomePageUrl = "https://smapi.io"; /// The file path for the SMAPI configuration file. internal static string ApiConfigPath => Path.Combine(Constants.ExecutionPath, $"{typeof(Program).Assembly.GetName().Name}.config.json"); diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index e4b279f7..8c1ea238 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -542,9 +542,9 @@ namespace StardewModdingAPI this.Monitor.Log($"Error: {response.Error}"); } else if (this.IsValidUpdate(Constants.ApiVersion, new SemanticVersion(response.Version))) - this.Monitor.Log($"You can update SMAPI to {response.Version}: {response.Url}", LogLevel.Alert); + this.Monitor.Log($"You can update SMAPI to {response.Version}: {Constants.HomePageUrl}", LogLevel.Alert); else if (response.PreviewVersion != null && this.IsValidUpdate(Constants.ApiVersion, new SemanticVersion(response.PreviewVersion))) - this.Monitor.Log($"You can update SMAPI to {response.PreviewVersion}: {response.Url}", LogLevel.Alert); + this.Monitor.Log($"You can update SMAPI to {response.PreviewVersion}: {Constants.HomePageUrl}", LogLevel.Alert); else this.Monitor.Log(" SMAPI okay.", LogLevel.Trace); } -- cgit From ff6df97ae8ca12f45aaba1776472f1dc10234b91 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 15 Mar 2018 21:26:03 -0400 Subject: fix error handling in update check API (#457) --- src/SMAPI.Web/Controllers/ModsApiController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs index abae7db7..c99c87fb 100644 --- a/src/SMAPI.Web/Controllers/ModsApiController.cs +++ b/src/SMAPI.Web/Controllers/ModsApiController.cs @@ -115,9 +115,9 @@ namespace StardewModdingAPI.Web.Controllers if (info.Error == null) { if (info.Version == null) - info = new ModInfoModel(info.Name, info.Version, info.Url, "Mod has no version number."); + info = new ModInfoModel(name: info.Name, version: info.Version, url: info.Url, error: "Mod has no version number."); if (!allowInvalidVersions && !Regex.IsMatch(info.Version, this.VersionRegex, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)) - info = new ModInfoModel(info.Name, info.Version, info.Url, $"Mod has invalid semantic version '{info.Version}'."); + info = new ModInfoModel(name: info.Name, version: info.Version, url: info.Url, error: $"Mod has invalid semantic version '{info.Version}'."); } // cache & return -- cgit From 594d176d39691ff46b2c99fdfaa4299a4ea43616 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 15 Mar 2018 23:36:16 -0400 Subject: prepare home page for upcoming beta (#457) --- src/SMAPI.Web/Controllers/IndexController.cs | 53 +++++++++++++++++++++++---- src/SMAPI.Web/ViewModels/IndexModel.cs | 28 +++++--------- src/SMAPI.Web/ViewModels/IndexVersionModel.cs | 41 +++++++++++++++++++++ src/SMAPI.Web/Views/Index/Index.cshtml | 35 ++++++++++++++---- 4 files changed, 124 insertions(+), 33 deletions(-) create mode 100644 src/SMAPI.Web/ViewModels/IndexVersionModel.cs (limited to 'src') diff --git a/src/SMAPI.Web/Controllers/IndexController.cs b/src/SMAPI.Web/Controllers/IndexController.cs index 5d45118f..0464e50a 100644 --- a/src/SMAPI.Web/Controllers/IndexController.cs +++ b/src/SMAPI.Web/Controllers/IndexController.cs @@ -3,6 +3,7 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Memory; +using StardewModdingAPI.Common; using StardewModdingAPI.Web.Framework.Clients.GitHub; using StardewModdingAPI.Web.ViewModels; @@ -23,7 +24,10 @@ namespace StardewModdingAPI.Web.Controllers private readonly IGitHubClient GitHub; /// The cache time for release info. - private readonly TimeSpan CacheTime = TimeSpan.FromMinutes(5); + private readonly TimeSpan CacheTime = TimeSpan.FromSeconds(1); + + /// The GitHub repository name to check for update. + private readonly string RepositoryName = "Pathoschild/SMAPI"; /********* @@ -42,17 +46,24 @@ namespace StardewModdingAPI.Web.Controllers [HttpGet] public async Task Index() { - // fetch latest SMAPI release - GitRelease release = await this.Cache.GetOrCreateAsync("latest-smapi-release", async entry => + // fetch SMAPI releases + IndexVersionModel stableVersion = await this.Cache.GetOrCreateAsync("stable-version", async entry => + { + entry.AbsoluteExpiration = DateTimeOffset.UtcNow.Add(this.CacheTime); + GitRelease release = await this.GitHub.GetLatestReleaseAsync(this.RepositoryName, includePrerelease: false); + return new IndexVersionModel(release.Name, release.Body, this.GetMainDownloadUrl(release), this.GetDevDownloadUrl(release)); + }); + IndexVersionModel betaVersion = await this.Cache.GetOrCreateAsync("beta-version", async entry => { entry.AbsoluteExpiration = DateTimeOffset.UtcNow.Add(this.CacheTime); - return await this.GitHub.GetLatestReleaseAsync("Pathoschild/SMAPI"); + GitRelease release = await this.GitHub.GetLatestReleaseAsync(this.RepositoryName, includePrerelease: true); + return release.IsPrerelease + ? this.GetBetaDownload(release) + : null; }); - string downloadUrl = this.GetMainDownloadUrl(release); - string devDownloadUrl = this.GetDevDownloadUrl(release); // render view - var model = new IndexModel(release.Name, release.Body, downloadUrl, devDownloadUrl); + var model = new IndexModel(stableVersion, betaVersion); return this.View(model); } @@ -89,5 +100,33 @@ namespace StardewModdingAPI.Web.Controllers // fallback just in case return "https://github.com/pathoschild/SMAPI/releases"; } + + /// Get the latest beta download for a SMAPI release. + /// The SMAPI release. + private IndexVersionModel GetBetaDownload(GitRelease release) + { + // get download with the latest version + SemanticVersionImpl latestVersion = null; + string latestUrl = null; + foreach (GitAsset asset in release.Assets ?? new GitAsset[0]) + { + // parse version + Match versionMatch = Regex.Match(asset.FileName, @"SMAPI-([\d\.]+(?:-.+)?)-installer.zip"); + if (!versionMatch.Success || !SemanticVersionImpl.TryParse(versionMatch.Groups[1].Value, out SemanticVersionImpl version)) + continue; + + // save latest version + if (latestVersion == null || latestVersion.CompareTo(version) < 0) + { + latestVersion = version; + latestUrl = asset.DownloadUrl; + } + } + + // return if prerelease + return latestVersion?.Tag != null + ? new IndexVersionModel(latestVersion.ToString(), release.Body, latestUrl, null) + : null; + } } } diff --git a/src/SMAPI.Web/ViewModels/IndexModel.cs b/src/SMAPI.Web/ViewModels/IndexModel.cs index 6d3da91e..4268c878 100644 --- a/src/SMAPI.Web/ViewModels/IndexModel.cs +++ b/src/SMAPI.Web/ViewModels/IndexModel.cs @@ -6,17 +6,11 @@ namespace StardewModdingAPI.Web.ViewModels /********* ** Accessors *********/ - /// The latest SMAPI version. - public string LatestVersion { get; set; } + /// The latest stable SMAPI version. + public IndexVersionModel StableVersion { get; set; } - /// The Markdown description for the release. - public string Description { get; set; } - - /// The main download URL. - public string DownloadUrl { get; set; } - - /// The for-developers download URL. - public string DevDownloadUrl { get; set; } + /// The latest prerelease SMAPI version (if newer than ). + public IndexVersionModel BetaVersion { get; set; } /********* @@ -26,16 +20,12 @@ namespace StardewModdingAPI.Web.ViewModels public IndexModel() { } /// Construct an instance. - /// The latest SMAPI version. - /// The Markdown description for the release. - /// The main download URL. - /// The for-developers download URL. - internal IndexModel(string latestVersion, string description, string downloadUrl, string devDownloadUrl) + /// The latest stable SMAPI version. + /// The latest prerelease SMAPI version (if newer than ). + internal IndexModel(IndexVersionModel stableVersion, IndexVersionModel betaVersion) { - this.LatestVersion = latestVersion; - this.Description = description; - this.DownloadUrl = downloadUrl; - this.DevDownloadUrl = devDownloadUrl; + this.StableVersion = stableVersion; + this.BetaVersion = betaVersion; } } } diff --git a/src/SMAPI.Web/ViewModels/IndexVersionModel.cs b/src/SMAPI.Web/ViewModels/IndexVersionModel.cs new file mode 100644 index 00000000..4f63b979 --- /dev/null +++ b/src/SMAPI.Web/ViewModels/IndexVersionModel.cs @@ -0,0 +1,41 @@ +namespace StardewModdingAPI.Web.ViewModels +{ + /// The fields for a SMAPI version. + public class IndexVersionModel + { + /********* + ** Accessors + *********/ + /// The release version. + public string Version { get; set; } + + /// The Markdown description for the release. + public string Description { get; set; } + + /// The main download URL. + public string DownloadUrl { get; set; } + + /// The for-developers download URL (not applicable for prerelease versions). + public string DevDownloadUrl { get; set; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + public IndexVersionModel() { } + + /// Construct an instance. + /// The release number. + /// The Markdown description for the release. + /// The main download URL. + /// The for-developers download URL (not applicable for prerelease versions). + internal IndexVersionModel(string version, string description, string downloadUrl, string devDownloadUrl) + { + this.Version = version; + this.Description = description; + this.DownloadUrl = downloadUrl; + this.DevDownloadUrl = devDownloadUrl; + } + } +} diff --git a/src/SMAPI.Web/Views/Index/Index.cshtml b/src/SMAPI.Web/Views/Index/Index.cshtml index ad58898e..4efb9f8a 100644 --- a/src/SMAPI.Web/Views/Index/Index.cshtml +++ b/src/SMAPI.Web/Views/Index/Index.cshtml @@ -13,7 +13,11 @@

- Download SMAPI @Model.LatestVersion
+ Download SMAPI @Model.StableVersion.Version
+ @if (Model.BetaVersion != null) + { + Download SMAPI @Model.BetaVersion.Version
for Stardew Valley 1.3 beta

+ } Install guide
FAQs
@@ -25,12 +29,29 @@
  • Get help on Discord or in the forums
  • -

    What's new in SMAPI @Model.LatestVersion?

    -
    - @Html.Raw(Markdig.Markdown.ToHtml(Model.Description)) -
    +@if (Model.BetaVersion == null) +{ +

    What's new in SMAPI @Model.StableVersion.Version?

    +
    + @Html.Raw(Markdig.Markdown.ToHtml(Model.StableVersion.Description)) +
    +

    See the release notes and mod compatibility list for more info.

    +} +else +{ +

    What's new in...

    +

    SMAPI @Model.StableVersion.Version?

    +
    + @Html.Raw(Markdig.Markdown.ToHtml(Model.StableVersion.Description)) +
    +

    See the release notes and mod compatibility list for more info.

    -

    See the release notes and mod compatibility list for more info.

    +

    SMAPI @Model.BetaVersion.Version?

    +
    + @Html.Raw(Markdig.Markdown.ToHtml(Model.BetaVersion.Description)) +
    +

    See the release notes and mod compatibility list for more info.

    +}

    Donate to support SMAPI ♥

    @@ -62,7 +83,7 @@

    For mod creators

    -- cgit From ada351b163d928b5c01787e3ac3ad25ee6fe1ce4 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 16 Mar 2018 20:28:16 -0400 Subject: reduce cache time for failed update checks to 5 minutes (#454) --- docs/release-notes.md | 1 + src/SMAPI.Web/Controllers/ModsApiController.cs | 12 ++++++++---- src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs | 7 +++++-- src/SMAPI.Web/appsettings.json | 3 ++- 4 files changed, 16 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 393090f2..9d654133 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -21,6 +21,7 @@ * Fixed rare crash with some combinations of manifest fields and internal mod data. * Fixed update checks failing for Nexus Mods due to a change in their API. * Fixed update checks failing for some older mods with non-standard versions. + * Fixed failed update checks being cached for an hour (now cached 5 minutes). * Fixed error when a content pack needs a mod that couldn't be loaded. * Fixed Linux ["magic number is wrong" errors](https://github.com/mono/mono/issues/6752) by changing default terminal order. * Updated compatibility list and added update checks for more mods. diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs index c99c87fb..24517263 100644 --- a/src/SMAPI.Web/Controllers/ModsApiController.cs +++ b/src/SMAPI.Web/Controllers/ModsApiController.cs @@ -29,8 +29,11 @@ namespace StardewModdingAPI.Web.Controllers /// The cache in which to store mod metadata. private readonly IMemoryCache Cache; - /// The number of minutes update checks should be cached before refetching them. - private readonly int CacheMinutes; + /// 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; /// A regex which matches SMAPI-style semantic version. private readonly string VersionRegex; @@ -50,7 +53,8 @@ namespace StardewModdingAPI.Web.Controllers ModUpdateCheckConfig config = configProvider.Value; this.Cache = cache; - this.CacheMinutes = config.CacheMinutes; + this.SuccessCacheMinutes = config.SuccessCacheMinutes; + this.ErrorCacheMinutes = config.ErrorCacheMinutes; this.VersionRegex = config.SemanticVersionRegex; this.Repositories = new IModRepository[] @@ -121,7 +125,7 @@ namespace StardewModdingAPI.Web.Controllers } // cache & return - entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(this.CacheMinutes); + entry.AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(info.Error == null ? this.SuccessCacheMinutes : this.ErrorCacheMinutes); return info; }); } diff --git a/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs index 58c3a100..fc3b7dc2 100644 --- a/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs +++ b/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs @@ -6,8 +6,11 @@ namespace StardewModdingAPI.Web.Framework.ConfigModels /********* ** Accessors *********/ - /// The number of minutes update checks should be cached before refetching them. - public int CacheMinutes { get; set; } + /// The number of minutes successful update checks should be cached before refetching them. + public int SuccessCacheMinutes { get; set; } + + /// The number of minutes failed update checks should be cached before refetching them. + public int ErrorCacheMinutes { get; set; } /// A regex which matches SMAPI-style semantic version. /// Derived from SMAPI's SemanticVersion implementation. diff --git a/src/SMAPI.Web/appsettings.json b/src/SMAPI.Web/appsettings.json index bfe827fa..03ca31ed 100644 --- a/src/SMAPI.Web/appsettings.json +++ b/src/SMAPI.Web/appsettings.json @@ -40,7 +40,8 @@ }, "ModUpdateCheck": { - "CacheMinutes": 60, + "SuccessCacheMinutes": 60, + "ErrorCacheMinutes": 5, "SemanticVersionRegex": "^(?>(?0|[1-9]\\d*))\\.(?>(?0|[1-9]\\d*))(?>(?:\\.(?0|[1-9]\\d*))?)(?:-(?(?>[a-z0-9]+[\\-\\.]?)+))?$", "ChucklefishKey": "Chucklefish", -- cgit From ae061165442ecb93e3a5a81bb918fd4c29e85e3a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 20 Mar 2018 00:49:14 -0400 Subject: fix minimum Stardew Valley 1.2 version mistakenly raised in 2.5.3 --- src/SMAPI/Constants.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 7a497a53..1279f8e1 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -49,7 +49,7 @@ namespace StardewModdingAPI #if STARDEW_VALLEY_1_3 new GameVersion("1.3.0.4"); #else - new SemanticVersion("1.2.33"); + new SemanticVersion("1.2.30"); #endif /// The maximum supported version of Stardew Valley. -- cgit From 5be3e5af5a08f9e72a969191dca07597d9c7c1f7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 20 Mar 2018 19:45:45 -0400 Subject: rename class to better match usage (#459) --- src/SMAPI/Framework/ContentCore.cs | 4 +- src/SMAPI/Metadata/CoreAssetPropagator.cs | 221 ++++++++++++++++++++++++++++++ src/SMAPI/Metadata/CoreAssets.cs | 220 ----------------------------- src/SMAPI/StardewModdingAPI.csproj | 2 +- 4 files changed, 224 insertions(+), 223 deletions(-) create mode 100644 src/SMAPI/Metadata/CoreAssetPropagator.cs delete mode 100644 src/SMAPI/Metadata/CoreAssets.cs (limited to 'src') diff --git a/src/SMAPI/Framework/ContentCore.cs b/src/SMAPI/Framework/ContentCore.cs index 85b8db8f..d5848d7b 100644 --- a/src/SMAPI/Framework/ContentCore.cs +++ b/src/SMAPI/Framework/ContentCore.cs @@ -51,7 +51,7 @@ namespace StardewModdingAPI.Framework private readonly IDictionary LanguageCodes; /// Provides metadata for core game assets. - private readonly CoreAssets CoreAssets; + private readonly CoreAssetPropagator CoreAssets; /// The assets currently being intercepted by instances. This is used to prevent infinite loops when a loader loads a new asset. private readonly ContextHash AssetsBeingLoaded = new ContextHash(); @@ -103,7 +103,7 @@ namespace StardewModdingAPI.Framework this.ModContentPrefix = this.GetAssetNameFromFilePath(Constants.ModPath); // get asset data - this.CoreAssets = new CoreAssets(this.NormaliseAssetName, reflection); + this.CoreAssets = new CoreAssetPropagator(this.NormaliseAssetName, reflection); this.Locales = this.GetKeyLocales(reflection); this.LanguageCodes = this.Locales.ToDictionary(p => p.Value, p => p.Key, StringComparer.InvariantCultureIgnoreCase); } diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs new file mode 100644 index 00000000..d6a731cd --- /dev/null +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -0,0 +1,221 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Xna.Framework.Graphics; +using StardewModdingAPI.Framework.Reflection; +using StardewValley; +using StardewValley.BellsAndWhistles; +using StardewValley.Buildings; +using StardewValley.Locations; +using StardewValley.Menus; +using StardewValley.Objects; +using StardewValley.Projectiles; +using StardewValley.TerrainFeatures; + +namespace StardewModdingAPI.Metadata +{ + /// Handles updating the game when a mod changes core assets. + /// This implementation only handles the core assets used by the game itself, and doesn't update any custom references to the changed textures. + internal class CoreAssetPropagator + { + /********* + ** Properties + *********/ + /// Normalises an asset key to match the cache key. + protected readonly Func GetNormalisedPath; + + /// Setters which update static or singleton texture fields indexed by normalised asset key. + private readonly IDictionary> SingletonSetters; + + + /********* + ** Public methods + *********/ + /// Initialise the core asset data. + /// Normalises an asset key to match the cache key. + /// Simplifies access to private code. + public CoreAssetPropagator(Func getNormalisedPath, Reflector reflection) + { + this.GetNormalisedPath = getNormalisedPath; + this.SingletonSetters = + new Dictionary> + { + // from CraftingRecipe.InitShared + ["Data\\CraftingRecipes"] = (content, key) => CraftingRecipe.craftingRecipes = content.Load>(key), + ["Data\\CookingRecipes"] = (content, key) => CraftingRecipe.cookingRecipes = content.Load>(key), + + // from Game1.loadContent + ["LooseSprites\\daybg"] = (content, key) => Game1.daybg = content.Load(key), + ["LooseSprites\\nightbg"] = (content, key) => Game1.nightbg = content.Load(key), + ["Maps\\MenuTiles"] = (content, key) => Game1.menuTexture = content.Load(key), + ["LooseSprites\\Lighting\\lantern"] = (content, key) => Game1.lantern = content.Load(key), + ["LooseSprites\\Lighting\\windowLight"] = (content, key) => Game1.windowLight = content.Load(key), + ["LooseSprites\\Lighting\\sconceLight"] = (content, key) => Game1.sconceLight = content.Load(key), + ["LooseSprites\\Lighting\\greenLight"] = (content, key) => Game1.cauldronLight = content.Load(key), + ["LooseSprites\\Lighting\\indoorWindowLight"] = (content, key) => Game1.indoorWindowLight = content.Load(key), + ["LooseSprites\\shadow"] = (content, key) => Game1.shadowTexture = content.Load(key), + ["LooseSprites\\Cursors"] = (content, key) => Game1.mouseCursors = content.Load(key), + ["LooseSprites\\ControllerMaps"] = (content, key) => Game1.controllerMaps = content.Load(key), + ["TileSheets\\animations"] = (content, key) => Game1.animations = content.Load(key), + ["Data\\Achievements"] = (content, key) => Game1.achievements = content.Load>(key), + ["Data\\NPCGiftTastes"] = (content, key) => Game1.NPCGiftTastes = content.Load>(key), + ["Fonts\\SpriteFont1"] = (content, key) => Game1.dialogueFont = content.Load(key), + ["Fonts\\SmallFont"] = (content, key) => Game1.smallFont = content.Load(key), + ["Fonts\\tinyFont"] = (content, key) => Game1.tinyFont = content.Load(key), + ["Fonts\\tinyFontBorder"] = (content, key) => Game1.tinyFontBorder = content.Load(key), + ["Maps\\springobjects"] = (content, key) => Game1.objectSpriteSheet = content.Load(key), + ["TileSheets\\crops"] = (content, key) => Game1.cropSpriteSheet = content.Load(key), + ["TileSheets\\emotes"] = (content, key) => Game1.emoteSpriteSheet = content.Load(key), + ["TileSheets\\debris"] = (content, key) => Game1.debrisSpriteSheet = content.Load(key), + ["TileSheets\\Craftables"] = (content, key) => Game1.bigCraftableSpriteSheet = content.Load(key), + ["TileSheets\\rain"] = (content, key) => Game1.rainTexture = content.Load(key), + ["TileSheets\\BuffsIcons"] = (content, key) => Game1.buffsIcons = content.Load(key), + ["Data\\ObjectInformation"] = (content, key) => Game1.objectInformation = content.Load>(key), + ["Data\\BigCraftablesInformation"] = (content, key) => Game1.bigCraftablesInformation = content.Load>(key), + ["Characters\\Farmer\\hairstyles"] = (content, key) => FarmerRenderer.hairStylesTexture = content.Load(key), + ["Characters\\Farmer\\shirts"] = (content, key) => FarmerRenderer.shirtsTexture = content.Load(key), + ["Characters\\Farmer\\hats"] = (content, key) => FarmerRenderer.hatsTexture = content.Load(key), + ["Characters\\Farmer\\accessories"] = (content, key) => FarmerRenderer.accessoriesTexture = content.Load(key), + ["TileSheets\\furniture"] = (content, key) => Furniture.furnitureTexture = content.Load(key), + ["LooseSprites\\font_bold"] = (content, key) => SpriteText.spriteTexture = content.Load(key), + ["LooseSprites\\font_colored"] = (content, key) => SpriteText.coloredTexture = content.Load(key), + ["TileSheets\\weapons"] = (content, key) => Tool.weaponsTexture = content.Load(key), + ["TileSheets\\Projectiles"] = (content, key) => Projectile.projectileSheet = content.Load(key), + + // from Game1.ResetToolSpriteSheet + ["TileSheets\\tools"] = (content, key) => Game1.ResetToolSpriteSheet(), + +#if STARDEW_VALLEY_1_3 + // from Bush + ["TileSheets\\bushes"] = (content, key) => reflection.GetField>(typeof(Bush), "texture").SetValue(new Lazy(() => content.Load(key))), + + // from Farm + ["Buildings\\houses"] = (content, key) => reflection.GetField(typeof(Farm), nameof(Farm.houseTextures)).SetValue(content.Load(key)), + + // from Farmer + ["Characters\\Farmer\\farmer_base"] = (content, key) => + { + if (Game1.player != null && Game1.player.isMale) + Game1.player.FarmerRenderer = new FarmerRenderer(key); + }, + ["Characters\\Farmer\\farmer_girl_base"] = (content, key) => + { + if (Game1.player != null && !Game1.player.isMale) + Game1.player.FarmerRenderer = new FarmerRenderer(key); + }, +#else + // from Bush + ["TileSheets\\bushes"] = (content, key) => Bush.texture = content.Load(key), + + // from Critter + ["TileSheets\\critters"] = (content, key) => Critter.critterTexture = content.Load(key), + + // from Farm + ["Buildings\\houses"] = (content, key) => + { + Farm farm = Game1.getFarm(); + if (farm != null) + farm.houseTextures = content.Load(key); + }, + + // from Farmer + ["Characters\\Farmer\\farmer_base"] = (content, key) => + { + if (Game1.player != null && Game1.player.isMale) + Game1.player.FarmerRenderer = new FarmerRenderer(content.Load(key)); + }, + ["Characters\\Farmer\\farmer_girl_base"] = (content, key) => + { + if (Game1.player != null && !Game1.player.isMale) + Game1.player.FarmerRenderer = new FarmerRenderer(content.Load(key)); + }, +#endif + + // from Flooring + ["TerrainFeatures\\Flooring"] = (content, key) => Flooring.floorsTexture = content.Load(key), + + // from FruitTree + ["TileSheets\\fruitTrees"] = (content, key) => FruitTree.texture = content.Load(key), + + // from HoeDirt + ["TerrainFeatures\\hoeDirt"] = (content, key) => HoeDirt.lightTexture = content.Load(key), + ["TerrainFeatures\\hoeDirtDark"] = (content, key) => HoeDirt.darkTexture = content.Load(key), + ["TerrainFeatures\\hoeDirtSnow"] = (content, key) => HoeDirt.snowTexture = content.Load(key), + + // from TitleMenu + ["Minigames\\Clouds"] = (content, key) => + { + if (Game1.activeClickableMenu is TitleMenu) + reflection.GetField(Game1.activeClickableMenu, "cloudsTexture").SetValue(content.Load(key)); + }, + ["Minigames\\TitleButtons"] = (content, key) => + { + if (Game1.activeClickableMenu is TitleMenu titleMenu) + { + reflection.GetField(titleMenu, "titleButtonsTexture").SetValue(content.Load(key)); + foreach (TemporaryAnimatedSprite bird in reflection.GetField>(titleMenu, "birds").GetValue()) +#if STARDEW_VALLEY_1_3 + bird.texture = content.Load(key); +#else + bird.Texture = content.Load(key); +#endif + } + }, + + // from Wallpaper + ["Maps\\walls_and_floors"] = (content, key) => Wallpaper.wallpaperTexture = content.Load(key) + } + .ToDictionary(p => getNormalisedPath(p.Key), p => p.Value); + } + + /// Reload one of the game's core assets (if applicable). + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// Returns whether an asset was reloaded. + public bool ReloadForKey(LocalizedContentManager content, string key) + { + // static assets + if (this.SingletonSetters.TryGetValue(key, out Action reload)) + { + reload(content, key); + return true; + } + + // building textures + if (key.StartsWith(this.GetNormalisedPath("Buildings\\"))) + { + Building[] buildings = this.GetAllBuildings().Where(p => key == this.GetNormalisedPath($"Buildings\\{p.buildingType}")).ToArray(); + if (buildings.Any()) + { +#if STARDEW_VALLEY_1_3 + foreach (Building building in buildings) + building.texture = new Lazy(() => content.Load(key)); +#else + Texture2D texture = content.Load(key); + foreach (Building building in buildings) + building.texture = texture; +#endif + + return true; + } + return false; + } + + return false; + } + + + /********* + ** Private methods + *********/ + /// Get all player-constructed buildings in the world. + private IEnumerable GetAllBuildings() + { + foreach (BuildableGameLocation location in Game1.locations.OfType()) + { + foreach (Building building in location.buildings) + yield return building; + } + } + } +} diff --git a/src/SMAPI/Metadata/CoreAssets.cs b/src/SMAPI/Metadata/CoreAssets.cs deleted file mode 100644 index 87629682..00000000 --- a/src/SMAPI/Metadata/CoreAssets.cs +++ /dev/null @@ -1,220 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Xna.Framework.Graphics; -using StardewModdingAPI.Framework.Reflection; -using StardewValley; -using StardewValley.BellsAndWhistles; -using StardewValley.Buildings; -using StardewValley.Locations; -using StardewValley.Menus; -using StardewValley.Objects; -using StardewValley.Projectiles; -using StardewValley.TerrainFeatures; - -namespace StardewModdingAPI.Metadata -{ - /// Provides metadata about core assets in the game. - internal class CoreAssets - { - /********* - ** Properties - *********/ - /// Normalises an asset key to match the cache key. - protected readonly Func GetNormalisedPath; - - /// Setters which update static or singleton texture fields indexed by normalised asset key. - private readonly IDictionary> SingletonSetters; - - - /********* - ** Public methods - *********/ - /// Initialise the core asset data. - /// Normalises an asset key to match the cache key. - /// Simplifies access to private code. - public CoreAssets(Func getNormalisedPath, Reflector reflection) - { - this.GetNormalisedPath = getNormalisedPath; - this.SingletonSetters = - new Dictionary> - { - // from CraftingRecipe.InitShared - ["Data\\CraftingRecipes"] = (content, key) => CraftingRecipe.craftingRecipes = content.Load>(key), - ["Data\\CookingRecipes"] = (content, key) => CraftingRecipe.cookingRecipes = content.Load>(key), - - // from Game1.loadContent - ["LooseSprites\\daybg"] = (content, key) => Game1.daybg = content.Load(key), - ["LooseSprites\\nightbg"] = (content, key) => Game1.nightbg = content.Load(key), - ["Maps\\MenuTiles"] = (content, key) => Game1.menuTexture = content.Load(key), - ["LooseSprites\\Lighting\\lantern"] = (content, key) => Game1.lantern = content.Load(key), - ["LooseSprites\\Lighting\\windowLight"] = (content, key) => Game1.windowLight = content.Load(key), - ["LooseSprites\\Lighting\\sconceLight"] = (content, key) => Game1.sconceLight = content.Load(key), - ["LooseSprites\\Lighting\\greenLight"] = (content, key) => Game1.cauldronLight = content.Load(key), - ["LooseSprites\\Lighting\\indoorWindowLight"] = (content, key) => Game1.indoorWindowLight = content.Load(key), - ["LooseSprites\\shadow"] = (content, key) => Game1.shadowTexture = content.Load(key), - ["LooseSprites\\Cursors"] = (content, key) => Game1.mouseCursors = content.Load(key), - ["LooseSprites\\ControllerMaps"] = (content, key) => Game1.controllerMaps = content.Load(key), - ["TileSheets\\animations"] = (content, key) => Game1.animations = content.Load(key), - ["Data\\Achievements"] = (content, key) => Game1.achievements = content.Load>(key), - ["Data\\NPCGiftTastes"] = (content, key) => Game1.NPCGiftTastes = content.Load>(key), - ["Fonts\\SpriteFont1"] = (content, key) => Game1.dialogueFont = content.Load(key), - ["Fonts\\SmallFont"] = (content, key) => Game1.smallFont = content.Load(key), - ["Fonts\\tinyFont"] = (content, key) => Game1.tinyFont = content.Load(key), - ["Fonts\\tinyFontBorder"] = (content, key) => Game1.tinyFontBorder = content.Load(key), - ["Maps\\springobjects"] = (content, key) => Game1.objectSpriteSheet = content.Load(key), - ["TileSheets\\crops"] = (content, key) => Game1.cropSpriteSheet = content.Load(key), - ["TileSheets\\emotes"] = (content, key) => Game1.emoteSpriteSheet = content.Load(key), - ["TileSheets\\debris"] = (content, key) => Game1.debrisSpriteSheet = content.Load(key), - ["TileSheets\\Craftables"] = (content, key) => Game1.bigCraftableSpriteSheet = content.Load(key), - ["TileSheets\\rain"] = (content, key) => Game1.rainTexture = content.Load(key), - ["TileSheets\\BuffsIcons"] = (content, key) => Game1.buffsIcons = content.Load(key), - ["Data\\ObjectInformation"] = (content, key) => Game1.objectInformation = content.Load>(key), - ["Data\\BigCraftablesInformation"] = (content, key) => Game1.bigCraftablesInformation = content.Load>(key), - ["Characters\\Farmer\\hairstyles"] = (content, key) => FarmerRenderer.hairStylesTexture = content.Load(key), - ["Characters\\Farmer\\shirts"] = (content, key) => FarmerRenderer.shirtsTexture = content.Load(key), - ["Characters\\Farmer\\hats"] = (content, key) => FarmerRenderer.hatsTexture = content.Load(key), - ["Characters\\Farmer\\accessories"] = (content, key) => FarmerRenderer.accessoriesTexture = content.Load(key), - ["TileSheets\\furniture"] = (content, key) => Furniture.furnitureTexture = content.Load(key), - ["LooseSprites\\font_bold"] = (content, key) => SpriteText.spriteTexture = content.Load(key), - ["LooseSprites\\font_colored"] = (content, key) => SpriteText.coloredTexture = content.Load(key), - ["TileSheets\\weapons"] = (content, key) => Tool.weaponsTexture = content.Load(key), - ["TileSheets\\Projectiles"] = (content, key) => Projectile.projectileSheet = content.Load(key), - - // from Game1.ResetToolSpriteSheet - ["TileSheets\\tools"] = (content, key) => Game1.ResetToolSpriteSheet(), - -#if STARDEW_VALLEY_1_3 - // from Bush - ["TileSheets\\bushes"] = (content, key) => reflection.GetField>(typeof(Bush), "texture").SetValue(new Lazy(() => content.Load(key))), - - // from Farm - ["Buildings\\houses"] = (content, key) => reflection.GetField(typeof(Farm), nameof(Farm.houseTextures)).SetValue(content.Load(key)), - - // from Farmer - ["Characters\\Farmer\\farmer_base"] = (content, key) => - { - if (Game1.player != null && Game1.player.isMale) - Game1.player.FarmerRenderer = new FarmerRenderer(key); - }, - ["Characters\\Farmer\\farmer_girl_base"] = (content, key) => - { - if (Game1.player != null && !Game1.player.isMale) - Game1.player.FarmerRenderer = new FarmerRenderer(key); - }, -#else - // from Bush - ["TileSheets\\bushes"] = (content, key) => Bush.texture = content.Load(key), - - // from Critter - ["TileSheets\\critters"] = (content, key) => Critter.critterTexture = content.Load(key), - - // from Farm - ["Buildings\\houses"] = (content, key) => - { - Farm farm = Game1.getFarm(); - if (farm != null) - farm.houseTextures = content.Load(key); - }, - - // from Farmer - ["Characters\\Farmer\\farmer_base"] = (content, key) => - { - if (Game1.player != null && Game1.player.isMale) - Game1.player.FarmerRenderer = new FarmerRenderer(content.Load(key)); - }, - ["Characters\\Farmer\\farmer_girl_base"] = (content, key) => - { - if (Game1.player != null && !Game1.player.isMale) - Game1.player.FarmerRenderer = new FarmerRenderer(content.Load(key)); - }, -#endif - - // from Flooring - ["TerrainFeatures\\Flooring"] = (content, key) => Flooring.floorsTexture = content.Load(key), - - // from FruitTree - ["TileSheets\\fruitTrees"] = (content, key) => FruitTree.texture = content.Load(key), - - // from HoeDirt - ["TerrainFeatures\\hoeDirt"] = (content, key) => HoeDirt.lightTexture = content.Load(key), - ["TerrainFeatures\\hoeDirtDark"] = (content, key) => HoeDirt.darkTexture = content.Load(key), - ["TerrainFeatures\\hoeDirtSnow"] = (content, key) => HoeDirt.snowTexture = content.Load(key), - - // from TitleMenu - ["Minigames\\Clouds"] = (content, key) => - { - if (Game1.activeClickableMenu is TitleMenu) - reflection.GetField(Game1.activeClickableMenu, "cloudsTexture").SetValue(content.Load(key)); - }, - ["Minigames\\TitleButtons"] = (content, key) => - { - if (Game1.activeClickableMenu is TitleMenu titleMenu) - { - reflection.GetField(titleMenu, "titleButtonsTexture").SetValue(content.Load(key)); - foreach (TemporaryAnimatedSprite bird in reflection.GetField>(titleMenu, "birds").GetValue()) -#if STARDEW_VALLEY_1_3 - bird.texture = content.Load(key); -#else - bird.Texture = content.Load(key); -#endif - } - }, - - // from Wallpaper - ["Maps\\walls_and_floors"] = (content, key) => Wallpaper.wallpaperTexture = content.Load(key) - } - .ToDictionary(p => getNormalisedPath(p.Key), p => p.Value); - } - - /// Reload one of the game's core assets (if applicable). - /// The content manager through which to reload the asset. - /// The asset key to reload. - /// Returns whether an asset was reloaded. - public bool ReloadForKey(LocalizedContentManager content, string key) - { - // static assets - if (this.SingletonSetters.TryGetValue(key, out Action reload)) - { - reload(content, key); - return true; - } - - // building textures - if (key.StartsWith(this.GetNormalisedPath("Buildings\\"))) - { - Building[] buildings = this.GetAllBuildings().Where(p => key == this.GetNormalisedPath($"Buildings\\{p.buildingType}")).ToArray(); - if (buildings.Any()) - { -#if STARDEW_VALLEY_1_3 - foreach (Building building in buildings) - building.texture = new Lazy(() => content.Load(key)); -#else - Texture2D texture = content.Load(key); - foreach (Building building in buildings) - building.texture = texture; -#endif - - return true; - } - return false; - } - - return false; - } - - - /********* - ** Private methods - *********/ - /// Get all player-constructed buildings in the world. - private IEnumerable GetAllBuildings() - { - foreach (BuildableGameLocation location in Game1.locations.OfType()) - { - foreach (Building building in location.buildings) - yield return building; - } - } - } -} diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj index bffb96e2..82a5602d 100644 --- a/src/SMAPI/StardewModdingAPI.csproj +++ b/src/SMAPI/StardewModdingAPI.csproj @@ -137,7 +137,7 @@ - + -- cgit From de5ee6f928339198d3c3ab0a91e9343863782c59 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 20 Mar 2018 21:22:19 -0400 Subject: rewrite core asset logic for extensibility (#459) --- src/SMAPI/Framework/ContentCore.cs | 2 +- src/SMAPI/Metadata/CoreAssetPropagator.cs | 379 +++++++++++++++++++----------- 2 files changed, 249 insertions(+), 132 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Framework/ContentCore.cs b/src/SMAPI/Framework/ContentCore.cs index d5848d7b..3c7e7b5a 100644 --- a/src/SMAPI/Framework/ContentCore.cs +++ b/src/SMAPI/Framework/ContentCore.cs @@ -368,7 +368,7 @@ namespace StardewModdingAPI.Framework int reloaded = 0; foreach (string key in removeAssetNames) { - if (this.CoreAssets.ReloadForKey(Game1.content, key)) // use an intercepted content manager + if (this.CoreAssets.Propagate(Game1.content, key)) // use an intercepted content manager reloaded++; } diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index d6a731cd..85021727 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -14,18 +14,17 @@ using StardewValley.TerrainFeatures; namespace StardewModdingAPI.Metadata { - /// Handles updating the game when a mod changes core assets. - /// This implementation only handles the core assets used by the game itself, and doesn't update any custom references to the changed textures. + /// Propagates changes to core assets to the game state. internal class CoreAssetPropagator { /********* ** Properties *********/ /// Normalises an asset key to match the cache key. - protected readonly Func GetNormalisedPath; + private readonly Func GetNormalisedPath; - /// Setters which update static or singleton texture fields indexed by normalised asset key. - private readonly IDictionary> SingletonSetters; + /// Simplifies access to private game code. + private readonly Reflector Reflection; /********* @@ -37,154 +36,272 @@ namespace StardewModdingAPI.Metadata public CoreAssetPropagator(Func getNormalisedPath, Reflector reflection) { this.GetNormalisedPath = getNormalisedPath; - this.SingletonSetters = - new Dictionary> - { - // from CraftingRecipe.InitShared - ["Data\\CraftingRecipes"] = (content, key) => CraftingRecipe.craftingRecipes = content.Load>(key), - ["Data\\CookingRecipes"] = (content, key) => CraftingRecipe.cookingRecipes = content.Load>(key), - - // from Game1.loadContent - ["LooseSprites\\daybg"] = (content, key) => Game1.daybg = content.Load(key), - ["LooseSprites\\nightbg"] = (content, key) => Game1.nightbg = content.Load(key), - ["Maps\\MenuTiles"] = (content, key) => Game1.menuTexture = content.Load(key), - ["LooseSprites\\Lighting\\lantern"] = (content, key) => Game1.lantern = content.Load(key), - ["LooseSprites\\Lighting\\windowLight"] = (content, key) => Game1.windowLight = content.Load(key), - ["LooseSprites\\Lighting\\sconceLight"] = (content, key) => Game1.sconceLight = content.Load(key), - ["LooseSprites\\Lighting\\greenLight"] = (content, key) => Game1.cauldronLight = content.Load(key), - ["LooseSprites\\Lighting\\indoorWindowLight"] = (content, key) => Game1.indoorWindowLight = content.Load(key), - ["LooseSprites\\shadow"] = (content, key) => Game1.shadowTexture = content.Load(key), - ["LooseSprites\\Cursors"] = (content, key) => Game1.mouseCursors = content.Load(key), - ["LooseSprites\\ControllerMaps"] = (content, key) => Game1.controllerMaps = content.Load(key), - ["TileSheets\\animations"] = (content, key) => Game1.animations = content.Load(key), - ["Data\\Achievements"] = (content, key) => Game1.achievements = content.Load>(key), - ["Data\\NPCGiftTastes"] = (content, key) => Game1.NPCGiftTastes = content.Load>(key), - ["Fonts\\SpriteFont1"] = (content, key) => Game1.dialogueFont = content.Load(key), - ["Fonts\\SmallFont"] = (content, key) => Game1.smallFont = content.Load(key), - ["Fonts\\tinyFont"] = (content, key) => Game1.tinyFont = content.Load(key), - ["Fonts\\tinyFontBorder"] = (content, key) => Game1.tinyFontBorder = content.Load(key), - ["Maps\\springobjects"] = (content, key) => Game1.objectSpriteSheet = content.Load(key), - ["TileSheets\\crops"] = (content, key) => Game1.cropSpriteSheet = content.Load(key), - ["TileSheets\\emotes"] = (content, key) => Game1.emoteSpriteSheet = content.Load(key), - ["TileSheets\\debris"] = (content, key) => Game1.debrisSpriteSheet = content.Load(key), - ["TileSheets\\Craftables"] = (content, key) => Game1.bigCraftableSpriteSheet = content.Load(key), - ["TileSheets\\rain"] = (content, key) => Game1.rainTexture = content.Load(key), - ["TileSheets\\BuffsIcons"] = (content, key) => Game1.buffsIcons = content.Load(key), - ["Data\\ObjectInformation"] = (content, key) => Game1.objectInformation = content.Load>(key), - ["Data\\BigCraftablesInformation"] = (content, key) => Game1.bigCraftablesInformation = content.Load>(key), - ["Characters\\Farmer\\hairstyles"] = (content, key) => FarmerRenderer.hairStylesTexture = content.Load(key), - ["Characters\\Farmer\\shirts"] = (content, key) => FarmerRenderer.shirtsTexture = content.Load(key), - ["Characters\\Farmer\\hats"] = (content, key) => FarmerRenderer.hatsTexture = content.Load(key), - ["Characters\\Farmer\\accessories"] = (content, key) => FarmerRenderer.accessoriesTexture = content.Load(key), - ["TileSheets\\furniture"] = (content, key) => Furniture.furnitureTexture = content.Load(key), - ["LooseSprites\\font_bold"] = (content, key) => SpriteText.spriteTexture = content.Load(key), - ["LooseSprites\\font_colored"] = (content, key) => SpriteText.coloredTexture = content.Load(key), - ["TileSheets\\weapons"] = (content, key) => Tool.weaponsTexture = content.Load(key), - ["TileSheets\\Projectiles"] = (content, key) => Projectile.projectileSheet = content.Load(key), - - // from Game1.ResetToolSpriteSheet - ["TileSheets\\tools"] = (content, key) => Game1.ResetToolSpriteSheet(), + this.Reflection = reflection; + } -#if STARDEW_VALLEY_1_3 - // from Bush - ["TileSheets\\bushes"] = (content, key) => reflection.GetField>(typeof(Bush), "texture").SetValue(new Lazy(() => content.Load(key))), + /// Reload one of the game's core assets (if applicable). + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// Returns whether an asset was reloaded. + public bool Propagate(LocalizedContentManager content, string key) + { + return this.PropagateImpl(content, key) != null; + } - // from Farm - ["Buildings\\houses"] = (content, key) => reflection.GetField(typeof(Farm), nameof(Farm.houseTextures)).SetValue(content.Load(key)), - // from Farmer - ["Characters\\Farmer\\farmer_base"] = (content, key) => - { - if (Game1.player != null && Game1.player.isMale) - Game1.player.FarmerRenderer = new FarmerRenderer(key); - }, - ["Characters\\Farmer\\farmer_girl_base"] = (content, key) => + /********* + ** Private methods + *********/ + /// Reload one of the game's core assets (if applicable). + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// Returns any non-null value to indicate an asset was loaded.. + private object PropagateImpl(LocalizedContentManager content, string key) + { + Reflector reflection = this.Reflection; + switch (key.ToLower().Replace("/", "\\")) // normalised key so we can compare statically + { + /**** + ** Buildings + ****/ + case "buildings\\houses": // Farm +#if STARDEW_VALLEY_1_3 + reflection.GetField(typeof(Farm), nameof(Farm.houseTextures)).SetValue(content.Load(key)); + return true; +#else { - if (Game1.player != null && !Game1.player.isMale) - Game1.player.FarmerRenderer = new FarmerRenderer(key); - }, + Farm farm = Game1.getFarm(); + if (farm == null) + return null; + return farm.houseTextures = content.Load(key); + } +#endif + + /**** + ** Content\Characters\Farmer + ****/ + case "characters\\farmer\\accessories": // Game1.loadContent + return FarmerRenderer.accessoriesTexture = content.Load(key); + + case "characters\\farmer\\farmer_base": // Farmer + if (Game1.player == null || !Game1.player.isMale) + return null; +#if STARDEW_VALLEY_1_3 + return Game1.player.FarmerRenderer = new FarmerRenderer(key); #else - // from Bush - ["TileSheets\\bushes"] = (content, key) => Bush.texture = content.Load(key), + return Game1.player.FarmerRenderer = new FarmerRenderer(content.Load(key)); +#endif - // from Critter - ["TileSheets\\critters"] = (content, key) => Critter.critterTexture = content.Load(key), + case "characters\\farmer\\farmer_girl_base": // Farmer + if (Game1.player == null || Game1.player.isMale) + return null; +#if STARDEW_VALLEY_1_3 + return Game1.player.FarmerRenderer = new FarmerRenderer(key); +#else + return Game1.player.FarmerRenderer = new FarmerRenderer(content.Load(key)); +#endif - // from Farm - ["Buildings\\houses"] = (content, key) => - { - Farm farm = Game1.getFarm(); - if (farm != null) - farm.houseTextures = content.Load(key); - }, + case "characters\\farmer\\hairstyles": // Game1.loadContent + return FarmerRenderer.hairStylesTexture = content.Load(key); + + case "characters\\farmer\\hats": // Game1.loadContent + return FarmerRenderer.hatsTexture = content.Load(key); + + case "characters\\farmer\\shirts": // Game1.loadContent + return FarmerRenderer.shirtsTexture = content.Load(key); + + /**** + ** Content\Data + ****/ + case "data\\achievements": // Game1.loadContent + return Game1.achievements = content.Load>(key); + + case "data\\bigcraftablesinformation": // Game1.loadContent + return Game1.bigCraftablesInformation = content.Load>(key); + + case "data\\cookingrecipes": // CraftingRecipe.InitShared + return CraftingRecipe.cookingRecipes = content.Load>(key); + + case "data\\craftingrecipes": // CraftingRecipe.InitShared + return CraftingRecipe.craftingRecipes = content.Load>(key); + + case "data\\npcgifttastes": // Game1.loadContent + return Game1.NPCGiftTastes = content.Load>(key); + + case "data\\objectinformation": // Game1.loadContent + return Game1.objectInformation = content.Load>(key); + + /**** + ** Content\Fonts + ****/ + case "fonts\\spritefont1": // Game1.loadContent + return Game1.dialogueFont = content.Load(key); + + case "fonts\\smallfont": // Game1.loadContent + return Game1.smallFont = content.Load(key); + + case "fonts\\tinyfont": // Game1.loadContent + return Game1.tinyFont = content.Load(key); + + case "fonts\\tinyfontborder": // Game1.loadContent + return Game1.tinyFontBorder = content.Load(key); - // from Farmer - ["Characters\\Farmer\\farmer_base"] = (content, key) => + /**** + ** Content\Lighting + ****/ + case "loosesprites\\lighting\\greenlight": // Game1.loadContent + return Game1.cauldronLight = content.Load(key); + + case "loosesprites\\lighting\\indoorwindowlight": // Game1.loadContent + return Game1.indoorWindowLight = content.Load(key); + + case "loosesprites\\lighting\\lantern": // Game1.loadContent + return Game1.lantern = content.Load(key); + + case "loosesprites\\lighting\\sconcelight": // Game1.loadContent + return Game1.sconceLight = content.Load(key); + + case "loosesprites\\lighting\\windowlight": // Game1.loadContent + return Game1.windowLight = content.Load(key); + + /**** + ** Content\LooseSprites + ****/ + case "loosesprites\\controllermaps": // Game1.loadContent + return Game1.controllerMaps = content.Load(key); + + case "loosesprites\\cursors": // Game1.loadContent + return Game1.mouseCursors = content.Load(key); + + case "loosesprites\\daybg": // Game1.loadContent + return Game1.daybg = content.Load(key); + + case "loosesprites\\font_bold": // Game1.loadContent + return SpriteText.spriteTexture = content.Load(key); + + case "loosesprites\\font_colored": // Game1.loadContent + return SpriteText.coloredTexture = content.Load(key); + + case "loosesprites\\nightbg": // Game1.loadContent + return Game1.nightbg = content.Load(key); + + case "loosesprites\\shadow": // Game1.loadContent + return Game1.shadowTexture = content.Load(key); + + /**** + ** Content\Critters + ****/ + case "tilesheets\\critters": // Criter.InitShared + return Critter.critterTexture = content.Load(key); + + case "tilesheets\\crops": // Game1.loadContent + return Game1.cropSpriteSheet = content.Load(key); + + case "tilesheets\\debris": // Game1.loadContent + return Game1.debrisSpriteSheet = content.Load(key); + + case "tilesheets\\emotes": // Game1.loadContent + return Game1.emoteSpriteSheet = content.Load(key); + + case "tilesheets\\furniture": // Game1.loadContent + return Furniture.furnitureTexture = content.Load(key); + + case "tilesheets\\projectiles": // Game1.loadContent + return Projectile.projectileSheet = content.Load(key); + + case "tilesheets\\rain": // Game1.loadContent + return Game1.rainTexture = content.Load(key); + + case "tilesheets\\tools": // Game1.ResetToolSpriteSheet + Game1.ResetToolSpriteSheet(); + return true; + + case "tilesheets\\weapons": // Game1.loadContent + return Tool.weaponsTexture = content.Load(key); + + /**** + ** Content\Maps + ****/ + case "maps\\menutiles": // Game1.loadContent + return Game1.menuTexture = content.Load(key); + + case "maps\\springobjects": // Game1.loadContent + return Game1.objectSpriteSheet = content.Load(key); + + case "maps\\walls_and_floors": // Wallpaper + return Wallpaper.wallpaperTexture = content.Load(key); + + /**** + ** Content\Minigames + ****/ + case "minigames\\clouds": // TitleMenu + if (Game1.activeClickableMenu is TitleMenu) { - if (Game1.player != null && Game1.player.isMale) - Game1.player.FarmerRenderer = new FarmerRenderer(content.Load(key)); - }, - ["Characters\\Farmer\\farmer_girl_base"] = (content, key) => + reflection.GetField(Game1.activeClickableMenu, "cloudsTexture").SetValue(content.Load(key)); + return true; + } + + return null; + + case "minigames\\titlebuttons": // TitleMenu + if (Game1.activeClickableMenu is TitleMenu titleMenu) { - if (Game1.player != null && !Game1.player.isMale) - Game1.player.FarmerRenderer = new FarmerRenderer(content.Load(key)); - }, + Texture2D texture = content.Load(key); + reflection.GetField(titleMenu, "titleButtonsTexture").SetValue(texture); + foreach (TemporaryAnimatedSprite bird in reflection.GetField>(titleMenu, "birds").GetValue()) +#if STARDEW_VALLEY_1_3 + bird.texture = texture; +#else + bird.Texture = texture; #endif + return true; + } - // from Flooring - ["TerrainFeatures\\Flooring"] = (content, key) => Flooring.floorsTexture = content.Load(key), + return null; - // from FruitTree - ["TileSheets\\fruitTrees"] = (content, key) => FruitTree.texture = content.Load(key), + /**** + ** Content\TileSheets + ****/ + case "tilesheets\\animations": // Game1.loadContent + return Game1.animations = content.Load(key); - // from HoeDirt - ["TerrainFeatures\\hoeDirt"] = (content, key) => HoeDirt.lightTexture = content.Load(key), - ["TerrainFeatures\\hoeDirtDark"] = (content, key) => HoeDirt.darkTexture = content.Load(key), - ["TerrainFeatures\\hoeDirtSnow"] = (content, key) => HoeDirt.snowTexture = content.Load(key), + case "tilesheets\\buffsicons": // Game1.loadContent + return Game1.buffsIcons = content.Load(key); - // from TitleMenu - ["Minigames\\Clouds"] = (content, key) => - { - if (Game1.activeClickableMenu is TitleMenu) - reflection.GetField(Game1.activeClickableMenu, "cloudsTexture").SetValue(content.Load(key)); - }, - ["Minigames\\TitleButtons"] = (content, key) => - { - if (Game1.activeClickableMenu is TitleMenu titleMenu) - { - reflection.GetField(titleMenu, "titleButtonsTexture").SetValue(content.Load(key)); - foreach (TemporaryAnimatedSprite bird in reflection.GetField>(titleMenu, "birds").GetValue()) + case "tilesheets\\bushes": // new Bush() #if STARDEW_VALLEY_1_3 - bird.texture = content.Load(key); + reflection.GetField>(typeof(Bush), "texture").SetValue(new Lazy(() => content.Load(key))); + return true; #else - bird.Texture = content.Load(key); + return Bush.texture = content.Load(key); #endif - } - }, - // from Wallpaper - ["Maps\\walls_and_floors"] = (content, key) => Wallpaper.wallpaperTexture = content.Load(key) - } - .ToDictionary(p => getNormalisedPath(p.Key), p => p.Value); - } + case "tilesheets\\craftables": // Game1.loadContent + return Game1.bigCraftableSpriteSheet = content.Load(key); - /// Reload one of the game's core assets (if applicable). - /// The content manager through which to reload the asset. - /// The asset key to reload. - /// Returns whether an asset was reloaded. - public bool ReloadForKey(LocalizedContentManager content, string key) - { - // static assets - if (this.SingletonSetters.TryGetValue(key, out Action reload)) - { - reload(content, key); - return true; + case "tilesheets\\fruittrees": // FruitTree + return FruitTree.texture = content.Load(key); + + /**** + ** Content\TerrainFeatures + ****/ + case "terrainfeatures\\flooring": // Flooring + return Flooring.floorsTexture = content.Load(key); + + case "terrainfeatures\\hoedirt": // from HoeDirt + return HoeDirt.lightTexture = content.Load(key); + + case "Terrainfeatures\\hoedirtdark": // from HoeDirt + return HoeDirt.darkTexture = content.Load(key); + + case "Terrainfeatures\\hoedirtsnow": // from HoeDirt + return HoeDirt.snowTexture = content.Load(key); } // building textures - if (key.StartsWith(this.GetNormalisedPath("Buildings\\"))) + if (key.StartsWith(this.GetNormalisedPath("Buildings\\"), StringComparison.InvariantCultureIgnoreCase)) { - Building[] buildings = this.GetAllBuildings().Where(p => key == this.GetNormalisedPath($"Buildings\\{p.buildingType}")).ToArray(); + Building[] buildings = this.GetAllBuildings().Where(p => key.Equals(this.GetNormalisedPath($"Buildings\\{p.buildingType?.ToLower()}"), StringComparison.InvariantCultureIgnoreCase)).ToArray(); if (buildings.Any()) { #if STARDEW_VALLEY_1_3 @@ -198,10 +315,10 @@ namespace StardewModdingAPI.Metadata return true; } - return false; + return null; } - return false; + return null; } -- cgit From e48f2301423f5177ec875308348fd4a83a071c3b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 21 Mar 2018 00:19:12 -0400 Subject: add unit test mode to mod build config package --- docs/mod-build-config.md | 13 +++++++++++++ src/SMAPI.ModBuildConfig/build/smapi.targets | 17 +++++++++++++++++ src/SMAPI.ModBuildConfig/package.nuspec | 3 ++- 3 files changed, 32 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/docs/mod-build-config.md b/docs/mod-build-config.md index ca750c86..2616d8a5 100644 --- a/docs/mod-build-config.md +++ b/docs/mod-build-config.md @@ -120,6 +120,19 @@ or you have multiple installs, you can specify the path yourself. There's two wa The configuration will check your custom path first, then fall back to the default paths (so it'll still compile on a different computer). +### Unit test projects +**(upcoming in 2.0.3)** + +You can use the package in unit test projects too. Its optional unit test mode... + +1. disables deploying the project as a mod; +2. disables creating a release zip; +2. and copies the referenced DLLs into the build output for unit test frameworks. + +To enable it, add this above the first `` in your `.csproj`: +```xml +True +``` ## Troubleshoot ### "Failed to find the game install path" diff --git a/src/SMAPI.ModBuildConfig/build/smapi.targets b/src/SMAPI.ModBuildConfig/build/smapi.targets index 7e8bbfc3..e27fc2c7 100644 --- a/src/SMAPI.ModBuildConfig/build/smapi.targets +++ b/src/SMAPI.ModBuildConfig/build/smapi.targets @@ -19,9 +19,14 @@ $(MSBuildProjectName) + True $(TargetDir) True True + + + False + False @@ -57,32 +62,40 @@ false + true false + true false + true false + true $(GamePath)\Netcode.dll False + true $(GamePath)\Stardew Valley.exe false + true $(GamePath)\StardewModdingAPI.exe false + true $(GamePath)\xTile.dll false False + true @@ -100,18 +113,22 @@ $(GamePath)\MonoGame.Framework.dll false False + true $(GamePath)\StardewValley.exe false + true $(GamePath)\StardewModdingAPI.exe false + true $(GamePath)\xTile.dll false + true diff --git a/src/SMAPI.ModBuildConfig/package.nuspec b/src/SMAPI.ModBuildConfig/package.nuspec index 8393ab61..6af8fefe 100644 --- a/src/SMAPI.ModBuildConfig/package.nuspec +++ b/src/SMAPI.ModBuildConfig/package.nuspec @@ -2,7 +2,7 @@ Pathoschild.Stardew.ModBuildConfig - 2.0.3-alpha20180307 + 2.0.3-alpha20180321 Build package for SMAPI mods Pathoschild Pathoschild @@ -29,6 +29,7 @@ 2.0.3: - Added support for Stardew Valley 1.3. + - Added support for unit test projects. -- cgit From 5b765849d87a702bc79affb82ecb9d565f57b30c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 22 Mar 2018 20:54:05 -0400 Subject: fix unit test check in build config package --- src/SMAPI.ModBuildConfig/build/smapi.targets | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/SMAPI.ModBuildConfig/build/smapi.targets b/src/SMAPI.ModBuildConfig/build/smapi.targets index e27fc2c7..d2e37101 100644 --- a/src/SMAPI.ModBuildConfig/build/smapi.targets +++ b/src/SMAPI.ModBuildConfig/build/smapi.targets @@ -19,14 +19,14 @@ $(MSBuildProjectName) - True + False $(TargetDir) True True - False - False + False + False -- cgit From 91561eedc7c8247a6179e0158eb9f9affdf65012 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 23 Mar 2018 01:21:50 -0400 Subject: fix log parser errors when log text contains {{tokens}} --- docs/release-notes.md | 4 ++++ src/SMAPI.Web/Views/LogParser/Index.cshtml | 36 +++++++++++++++--------------- 2 files changed, 22 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 9d654133..059105c3 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -15,6 +15,10 @@ * Added support for beta releases on the home page. --> +## 2.5.4 +* For the [log parser][]: + * Fixed error when log text contains certain tokens. + ## 2.5.3 * For players: * Simplified and improved skipped-mod messages. diff --git a/src/SMAPI.Web/Views/LogParser/Index.cshtml b/src/SMAPI.Web/Views/LogParser/Index.cshtml index d2d8004e..9c21c8c0 100644 --- a/src/SMAPI.Web/Views/LogParser/Index.cshtml +++ b/src/SMAPI.Web/Views/LogParser/Index.cshtml @@ -54,23 +54,23 @@ Game info: SMAPI version: - @Model.ParsedLog.ApiVersion + @Model.ParsedLog.ApiVersion Game version: - @Model.ParsedLog.GameVersion + @Model.ParsedLog.GameVersion Platform: - @Model.ParsedLog.OperatingSystem + @Model.ParsedLog.OperatingSystem Mods path: - @Model.ParsedLog.ModPath + @Model.ParsedLog.ModPath Log started: - @Model.ParsedLog.Timestamp.UtcDateTime.ToString("yyyy-MM-dd HH:mm") UTC ({{localTimeStarted}} your time) + @Model.ParsedLog.Timestamp.UtcDateTime.ToString("yyyy-MM-dd HH:mm") UTC ({{localTimeStarted}} your time)
    @@ -85,7 +85,7 @@ { - + @mod.Name @if (contentPacks != null && contentPacks.TryGetValue(mod.Name, out LogModInfo[] contentPackList)) { @@ -97,19 +97,19 @@
    } - @mod.Version - @mod.Author + @mod.Version + @mod.Author @if (mod.Errors == 0) { - no errors + no errors } else if (mod.Errors == 1) { - @mod.Errors error + @mod.Errors error } else { - @mod.Errors errors + @mod.Errors errors } } @@ -130,16 +130,16 @@ string levelStr = message.Level.ToString().ToLower(); - @message.Time - @message.Level.ToString().ToUpper() - @message.Mod - @message.Text + @message.Time + @message.Level.ToString().ToUpper() + @message.Mod + @message.Text if (message.Repeated > 0) { - repeats [@message.Repeated] times. + repeats [@message.Repeated] times. } } @@ -151,11 +151,11 @@ else if (Model.ParsedLog?.IsValid == false)

    Parsed log

    We couldn't parse that file, but you can still share the link.

    -

    Error details: @Model.ParsedLog.Error

    +

    Error details: @Model.ParsedLog.Error

    Raw log

    -
    @Model.ParsedLog.RawText
    +
    @Model.ParsedLog.RawText
    }
    -- cgit From 51368b8afb0c2064a70ed09f41570ca8fac5fdfa Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 23 Mar 2018 20:18:23 -0400 Subject: update tree textures when changeed through the content API (#459) --- docs/release-notes.md | 3 + src/SMAPI/Metadata/CoreAssetPropagator.cs | 120 ++++++++++++++++++++++-------- 2 files changed, 91 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 059105c3..05a37ea8 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -16,6 +16,9 @@ --> ## 2.5.4 +* For modders: + * Added automatic texture update for trees when changed through the content API. + * For the [log parser][]: * Fixed error when log text contains certain tokens. diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 85021727..21aaeb6c 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.Reflection; @@ -45,7 +46,10 @@ namespace StardewModdingAPI.Metadata /// Returns whether an asset was reloaded. public bool Propagate(LocalizedContentManager content, string key) { - return this.PropagateImpl(content, key) != null; + object result = this.PropagateImpl(content, key); + if (result is bool b) + return b; + return result != null; } @@ -55,7 +59,7 @@ namespace StardewModdingAPI.Metadata /// Reload one of the game's core assets (if applicable). /// The content manager through which to reload the asset. /// The asset key to reload. - /// Returns any non-null value to indicate an asset was loaded.. + /// Returns any non-null value to indicate an asset was loaded. private object PropagateImpl(LocalizedContentManager content, string key) { Reflector reflection = this.Reflection; @@ -72,7 +76,7 @@ namespace StardewModdingAPI.Metadata { Farm farm = Game1.getFarm(); if (farm == null) - return null; + return false; return farm.houseTextures = content.Load(key); } #endif @@ -85,7 +89,7 @@ namespace StardewModdingAPI.Metadata case "characters\\farmer\\farmer_base": // Farmer if (Game1.player == null || !Game1.player.isMale) - return null; + return false; #if STARDEW_VALLEY_1_3 return Game1.player.FarmerRenderer = new FarmerRenderer(key); #else @@ -94,7 +98,7 @@ namespace StardewModdingAPI.Metadata case "characters\\farmer\\farmer_girl_base": // Farmer if (Game1.player == null || Game1.player.isMale) - return null; + return false; #if STARDEW_VALLEY_1_3 return Game1.player.FarmerRenderer = new FarmerRenderer(key); #else @@ -240,8 +244,7 @@ namespace StardewModdingAPI.Metadata reflection.GetField(Game1.activeClickableMenu, "cloudsTexture").SetValue(content.Load(key)); return true; } - - return null; + return false; case "minigames\\titlebuttons": // TitleMenu if (Game1.activeClickableMenu is TitleMenu titleMenu) @@ -256,8 +259,7 @@ namespace StardewModdingAPI.Metadata #endif return true; } - - return null; + return false; /**** ** Content\TileSheets @@ -291,48 +293,102 @@ namespace StardewModdingAPI.Metadata case "terrainfeatures\\hoedirt": // from HoeDirt return HoeDirt.lightTexture = content.Load(key); - case "Terrainfeatures\\hoedirtdark": // from HoeDirt + case "terrainfeatures\\hoedirtdark": // from HoeDirt return HoeDirt.darkTexture = content.Load(key); - case "Terrainfeatures\\hoedirtsnow": // from HoeDirt + case "terrainfeatures\\hoedirtsnow": // from HoeDirt return HoeDirt.snowTexture = content.Load(key); + + case "terrainfeatures\\mushroom_tree": // from Tree + return this.ReloadTreeTextures(content, key, Tree.mushroomTree); + + case "terrainfeatures\\tree_palm": // from Tree + return this.ReloadTreeTextures(content, key, Tree.palmTree); + + case "terrainfeatures\\tree1_fall": // from Tree + case "terrainfeatures\\tree1_spring": // from Tree + case "terrainfeatures\\tree1_summer": // from Tree + case "terrainfeatures\\tree1_winter": // from Tree + return this.ReloadTreeTextures(content, key, Tree.bushyTree); + + case "terrainfeatures\\tree2_fall": // from Tree + case "terrainfeatures\\tree2_spring": // from Tree + case "terrainfeatures\\tree2_summer": // from Tree + case "terrainfeatures\\tree2_winter": // from Tree + return this.ReloadTreeTextures(content, key, Tree.leafyTree); + + case "terrainfeatures\\tree3_fall": // from Tree + case "terrainfeatures\\tree3_spring": // from Tree + case "terrainfeatures\\tree3_winter": // from Tree + return this.ReloadTreeTextures(content, key, Tree.pineTree); } // building textures if (key.StartsWith(this.GetNormalisedPath("Buildings\\"), StringComparison.InvariantCultureIgnoreCase)) { - Building[] buildings = this.GetAllBuildings().Where(p => key.Equals(this.GetNormalisedPath($"Buildings\\{p.buildingType?.ToLower()}"), StringComparison.InvariantCultureIgnoreCase)).ToArray(); - if (buildings.Any()) - { -#if STARDEW_VALLEY_1_3 - foreach (Building building in buildings) - building.texture = new Lazy(() => content.Load(key)); -#else - Texture2D texture = content.Load(key); - foreach (Building building in buildings) - building.texture = texture; -#endif - - return true; - } - return null; + string type = Path.GetFileName(key); + return this.ReloadBuildings(content, key, type); } - return null; + return false; } /********* ** Private methods *********/ - /// Get all player-constructed buildings in the world. - private IEnumerable GetAllBuildings() + /// Reload building textures. + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// The type to reload. + /// Returns whether any textures were reloaded. + private bool ReloadBuildings(LocalizedContentManager content, string key, string type) + { + Building[] buildings = Game1.locations + .OfType() + .SelectMany(p => p.buildings) + .Where(p => p.buildingType == type) + .ToArray(); + + if (buildings.Any()) + { + Lazy texture = new Lazy(() => content.Load(key)); + foreach (Building building in buildings) +#if STARDEW_VALLEY_1_3 + building.texture = texture; +#else + building.texture = texture.Value; +#endif + return true; + } + return false; + } + + /// Reload tree textures. + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// The type to reload. + /// Returns whether any textures were reloaded. + private bool ReloadTreeTextures(LocalizedContentManager content, string key, int type) { - foreach (BuildableGameLocation location in Game1.locations.OfType()) + Tree[] trees = Game1.locations + .SelectMany(p => p.terrainFeatures.Values.OfType()) + .Where(tree => tree.treeType == type) + .ToArray(); + + if (trees.Any()) { - foreach (Building building in location.buildings) - yield return building; + Lazy texture = new Lazy(() => content.Load(key)); + foreach (Tree tree in trees) +#if STARDEW_VALLEY_1_3 + this.Reflection.GetField>(tree, "texture").SetValue(texture); +#else + this.Reflection.GetField(tree, "texture").SetValue(texture.Value); +#endif + return true; } + + return false; } } } -- cgit From fad47ff74f6d03652951230eb1c394b896578c48 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 23 Mar 2018 22:30:49 -0400 Subject: fix image overlay bugs on Linux/Mac (#461) --- docs/release-notes.md | 5 +++-- src/SMAPI/Framework/Content/AssetDataForImage.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 05a37ea8..c40e41be 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -16,8 +16,9 @@ --> ## 2.5.4 -* For modders: - * Added automatic texture update for trees when changed through the content API. +* For players: + * Fixed tree textures not updated when changed through the content API. + * Fixed display bugs on Linux/Mac when mods overlay images through the content API. * For the [log parser][]: * Fixed error when log text contains certain tokens. diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index c665484f..fc653bcf 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -58,7 +58,7 @@ namespace StardewModdingAPI.Framework.Content for (int i = 0; i < sourceData.Length; i++) { Color pixel = sourceData[i]; - if (pixel.A != 0) // not transparent + if (pixel.A > 2) // not transparent (note: on Linux/Mac, fully transparent pixels may have an alpha up to 2 for some reason) newData[i] = pixel; } sourceData = newData; -- cgit From 5126d56b3992acd3193aaae35f178bb5e9da1cae Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 23 Mar 2018 22:41:15 -0400 Subject: fix error when a mod removes an asset editor/loader (#460) --- docs/release-notes.md | 3 ++- src/SMAPI/Program.cs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index c40e41be..33e66a14 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -18,7 +18,8 @@ ## 2.5.4 * For players: * Fixed tree textures not updated when changed through the content API. - * Fixed display bugs on Linux/Mac when mods overlay images through the content API. + * Fixed visual bug on Linux/Mac when mods overlay images through the content API. + * Fixed error when a mod removes an asset editor/loader. * For the [log parser][]: * Fixed error when log text contains certain tokens. diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 8c1ea238..1b8cb2ba 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -953,7 +953,7 @@ namespace StardewModdingAPI { helper.ObservableAssetEditors.CollectionChanged += (sender, e) => { - if (e.NewItems.Count > 0) + if (e.NewItems?.Count > 0) { this.Monitor.Log("Invalidating cache entries for new asset editors...", LogLevel.Trace); this.ContentCore.InvalidateCacheFor(e.NewItems.Cast().ToArray(), new IAssetLoader[0]); @@ -961,7 +961,7 @@ namespace StardewModdingAPI }; helper.ObservableAssetLoaders.CollectionChanged += (sender, e) => { - if (e.NewItems.Count > 0) + if (e.NewItems?.Count > 0) { this.Monitor.Log("Invalidating cache entries for new asset loaders...", LogLevel.Trace); this.ContentCore.InvalidateCacheFor(new IAssetEditor[0], e.NewItems.Cast().ToArray()); -- cgit From 34346d8b0911ec949832c037bfd83d4ab706d37a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 24 Mar 2018 19:06:13 -0400 Subject: tweak transparency threshold (#461) --- src/SMAPI/Framework/Content/AssetDataForImage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index fc653bcf..1eef2afb 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -58,7 +58,7 @@ namespace StardewModdingAPI.Framework.Content for (int i = 0; i < sourceData.Length; i++) { Color pixel = sourceData[i]; - if (pixel.A > 2) // not transparent (note: on Linux/Mac, fully transparent pixels may have an alpha up to 2 for some reason) + if (pixel.A > 4) // not transparent (note: on Linux/Mac, fully transparent pixels may have an alpha up to 4 for some reason) newData[i] = pixel; } sourceData = newData; -- cgit From 20b778390051569aa34a9ac42f03cd9b9a051df7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 24 Mar 2018 20:26:33 -0400 Subject: update NPC textures when changed through the content API (#459) --- src/SMAPI/Metadata/CoreAssetPropagator.cs | 122 ++++++++++++++++++++++++++++-- 1 file changed, 115 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 21aaeb6c..4a1d6097 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -323,12 +323,18 @@ namespace StardewModdingAPI.Metadata return this.ReloadTreeTextures(content, key, Tree.pineTree); } - // building textures + // dynamic textures if (key.StartsWith(this.GetNormalisedPath("Buildings\\"), StringComparison.InvariantCultureIgnoreCase)) - { - string type = Path.GetFileName(key); - return this.ReloadBuildings(content, key, type); - } + return this.ReloadBuildings(content, key); + + if (key.StartsWith(this.GetNormalisedPath("Characters\\")) && this.CountSegments(key) == 2) // ignore Characters/Dialogue/*, etc + return this.ReloadNpcSprites(content, key, monster: false); + + if (key.StartsWith(this.GetNormalisedPath("Characters\\Monsters\\"))) + return this.ReloadNpcSprites(content, key, monster: true); + + if (key.StartsWith(this.GetNormalisedPath("Portraits\\"))) + return this.ReloadNpcPortraits(content, key); return false; } @@ -340,16 +346,18 @@ namespace StardewModdingAPI.Metadata /// Reload building textures. /// The content manager through which to reload the asset. /// The asset key to reload. - /// The type to reload. /// Returns whether any textures were reloaded. - private bool ReloadBuildings(LocalizedContentManager content, string key, string type) + private bool ReloadBuildings(LocalizedContentManager content, string key) { + // get buildings + string type = Path.GetFileName(key); Building[] buildings = Game1.locations .OfType() .SelectMany(p => p.buildings) .Where(p => p.buildingType == type) .ToArray(); + // reload buildings if (buildings.Any()) { Lazy texture = new Lazy(() => content.Load(key)); @@ -364,6 +372,61 @@ namespace StardewModdingAPI.Metadata return false; } + /// Reload the sprites for matching NPCs. + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// Whether to match monsters (true) or non-monsters (false). + /// Returns whether any textures were reloaded. + private bool ReloadNpcSprites(LocalizedContentManager content, string key, bool monster) + { + // get NPCs + string name = this.GetNpcNameFromFileName(Path.GetFileName(key)); + NPC[] characters = + ( + from location in this.GetLocations() + from npc in location.characters + where npc.name == name && npc.IsMonster == monster + select npc + ) + .Distinct() + .ToArray(); + if (!characters.Any()) + return false; + + // update portrait + Texture2D texture = content.Load(key); + foreach (NPC character in characters) + character.Sprite.Texture = texture; + return true; + } + + /// Reload the portraits for matching NPCs. + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// Returns whether any textures were reloaded. + private bool ReloadNpcPortraits(LocalizedContentManager content, string key) + { + // get NPCs + string name = this.GetNpcNameFromFileName(Path.GetFileName(key)); + NPC[] villagers = + ( + from location in this.GetLocations() + from npc in location.characters + where npc.name == name && npc.isVillager() + select npc + ) + .Distinct() + .ToArray(); + if (!villagers.Any()) + return false; + + // update portrait + Texture2D texture = content.Load(key); + foreach (NPC villager in villagers) + villager.Portrait = texture; + return true; + } + /// Reload tree textures. /// The content manager through which to reload the asset. /// The asset key to reload. @@ -390,5 +453,50 @@ namespace StardewModdingAPI.Metadata return false; } + + /// Get an NPC name from the name of their file under Content/Characters. + /// The file name. + /// Derived from . + private string GetNpcNameFromFileName(string name) + { + switch (name) + { + case "Mariner": + return "Old Mariner"; + case "DwarfKing": + return "Dwarf King"; + case "MrQi": + return "Mister Qi"; + default: + return name; + } + } + + /// Get all locations in the game. + private IEnumerable GetLocations() + { + foreach (GameLocation location in Game1.locations) + { + yield return location; + + if (location is BuildableGameLocation buildableLocation) + { + foreach (Building building in buildableLocation.buildings) + { + if (building.indoors != null) + yield return building.indoors; + } + } + } + } + + /// Count the number of segments in a path (e.g. 'a/b' is 2). + /// The path to check. + private int CountSegments(string path) + { + if (path == null) + return 0; + return path.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).Length; + } } } -- cgit From 5a0e49827be92d19dfdda7bb15ca15fa8f269ecb Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 25 Mar 2018 00:52:37 -0400 Subject: update fence textures when changed through the content API (#459) --- docs/release-notes.md | 3 ++- src/SMAPI/Metadata/CoreAssetPropagator.cs | 44 ++++++++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index e0e209a1..2a134d13 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -17,9 +17,10 @@ ## 2.5.4 * For players: - * Fixed NPC and tree textures not updated when changed through the content API. + * Fixed fence, NPC, and tree textures not updated when a mod changes them through the content API. * Fixed visual bug on Linux/Mac when mods overlay images through the content API. * Fixed error when a mod removes an asset editor/loader. + * Fixed minimum game version incorrectly changed from 1.2.30 to 1.2.33 in SMAPI 2.5.3. * For the [log parser][]: * Fixed error when log text contains certain tokens. diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 4a1d6097..1702ee26 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -333,6 +333,9 @@ namespace StardewModdingAPI.Metadata if (key.StartsWith(this.GetNormalisedPath("Characters\\Monsters\\"))) return this.ReloadNpcSprites(content, key, monster: true); + if (key.StartsWith(this.GetNormalisedPath("LooseSprites\\Fence"))) + return this.ReloadFenceTextures(content, key); + if (key.StartsWith(this.GetNormalisedPath("Portraits\\"))) return this.ReloadNpcPortraits(content, key); @@ -372,6 +375,34 @@ namespace StardewModdingAPI.Metadata return false; } + /// Reload the sprites for a fence type. + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// Returns whether any textures were reloaded. + private bool ReloadFenceTextures(LocalizedContentManager content, string key) + { + // get fence type + if (!int.TryParse(this.GetSegments(key)[1].Substring("Fence".Length), out int fenceType)) + return false; + + // get fences + Fence[] fences = + ( + from location in this.GetLocations() + from fence in location.Objects.Values.OfType() + where fenceType == 1 + ? fence.isGate + : fence.whichType == fenceType + select fence + ) + .ToArray(); + + // update fence textures + foreach (Fence fence in fences) + fence.reloadSprite(); + return true; + } + /// Reload the sprites for matching NPCs. /// The content manager through which to reload the asset. /// The asset key to reload. @@ -490,13 +521,20 @@ namespace StardewModdingAPI.Metadata } } + /// Get the segments in a path (e.g. 'a/b' is 'a' and 'b'). + /// The path to check. + private string[] GetSegments(string path) + { + if (path == null) + return new string[0]; + return path.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + } + /// Count the number of segments in a path (e.g. 'a/b' is 2). /// The path to check. private int CountSegments(string path) { - if (path == null) - return 0; - return path.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).Length; + return this.GetSegments(path).Length; } } } -- cgit From b1cc6c1d9995db2eccead2a2c99f8f64ddb1da81 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 25 Mar 2018 11:41:56 -0400 Subject: update new asset update logic for Stardew Valley 1.3 (#453) --- src/SMAPI/Metadata/CoreAssetPropagator.cs | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src') diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 1702ee26..277da525 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -195,8 +195,10 @@ namespace StardewModdingAPI.Metadata /**** ** Content\Critters ****/ +#if !STARDEW_VALLEY_1_3 case "tilesheets\\critters": // Criter.InitShared return Critter.critterTexture = content.Load(key); +#endif case "tilesheets\\crops": // Game1.loadContent return Game1.cropSpriteSheet = content.Load(key); @@ -427,7 +429,11 @@ namespace StardewModdingAPI.Metadata // update portrait Texture2D texture = content.Load(key); foreach (NPC character in characters) +#if STARDEW_VALLEY_1_3 + this.Reflection.GetField(character.Sprite, "spriteTexture").SetValue(texture); +#else character.Sprite.Texture = texture; +#endif return true; } -- cgit From 5681c0f98170d5830e7f2ab58c77b92216aa9a60 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 25 Mar 2018 12:01:19 -0400 Subject: update mod build config package --- docs/mod-build-config.md | 10 ++++++++++ src/SMAPI.ModBuildConfig/package.nuspec | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/docs/mod-build-config.md b/docs/mod-build-config.md index 2616d8a5..0d72e4d9 100644 --- a/docs/mod-build-config.md +++ b/docs/mod-build-config.md @@ -140,6 +140,16 @@ That error means the package couldn't find your game. You can specify the game p _[Game path](#game-path)_ above. ## Release notes +### 2.0.3 alpha +* Added support for Stardew Valley 1.3. +* Added support for unit test projects. + +### 2.0.2 +* Fixed compatibility issue on Linux. + +### 2.0.1 +* Fixed mod deploy failing to create subfolders if they don't already exist. + ### 2.0 * Added: mods are now copied into the `Mods` folder automatically (configurable). * Added: release zips are now created automatically in your build output folder (configurable). diff --git a/src/SMAPI.ModBuildConfig/package.nuspec b/src/SMAPI.ModBuildConfig/package.nuspec index 6af8fefe..d24e15be 100644 --- a/src/SMAPI.ModBuildConfig/package.nuspec +++ b/src/SMAPI.ModBuildConfig/package.nuspec @@ -2,7 +2,7 @@ Pathoschild.Stardew.ModBuildConfig - 2.0.3-alpha20180321 + 2.0.3-alpha20180325 Build package for SMAPI mods Pathoschild Pathoschild -- cgit From 4d668eb7022819391a6c1834e1351c9b4fc52104 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 25 Mar 2018 12:17:58 -0400 Subject: update API packages --- src/SMAPI.Web/StardewModdingAPI.Web.csproj | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/SMAPI.Web/StardewModdingAPI.Web.csproj b/src/SMAPI.Web/StardewModdingAPI.Web.csproj index 19198503..e2eee8a8 100644 --- a/src/SMAPI.Web/StardewModdingAPI.Web.csproj +++ b/src/SMAPI.Web/StardewModdingAPI.Web.csproj @@ -10,13 +10,13 @@ - - - - - - - + + + + + + + -- cgit From 04e299aeaa4f9291c15f49d8093831cae93e8329 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 25 Mar 2018 12:32:16 -0400 Subject: update Json.NET package --- .../StardewModdingAPI.Mods.ConsoleCommands.csproj | 4 ++-- src/SMAPI.Mods.ConsoleCommands/packages.config | 2 +- src/SMAPI/StardewModdingAPI.csproj | 3 ++- src/SMAPI/packages.config | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/SMAPI.Mods.ConsoleCommands/StardewModdingAPI.Mods.ConsoleCommands.csproj b/src/SMAPI.Mods.ConsoleCommands/StardewModdingAPI.Mods.ConsoleCommands.csproj index a5b89a33..d1f72c6c 100644 --- a/src/SMAPI.Mods.ConsoleCommands/StardewModdingAPI.Mods.ConsoleCommands.csproj +++ b/src/SMAPI.Mods.ConsoleCommands/StardewModdingAPI.Mods.ConsoleCommands.csproj @@ -37,8 +37,8 @@ - ..\packages\Newtonsoft.Json.11.0.1-beta3\lib\net45\Newtonsoft.Json.dll - False + ..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll + True diff --git a/src/SMAPI.Mods.ConsoleCommands/packages.config b/src/SMAPI.Mods.ConsoleCommands/packages.config index a0f76c34..c8b3ae63 100644 --- a/src/SMAPI.Mods.ConsoleCommands/packages.config +++ b/src/SMAPI.Mods.ConsoleCommands/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj index 82a5602d..edddbd2a 100644 --- a/src/SMAPI/StardewModdingAPI.csproj +++ b/src/SMAPI/StardewModdingAPI.csproj @@ -66,7 +66,8 @@ True - ..\packages\Newtonsoft.Json.11.0.1-beta3\lib\net45\Newtonsoft.Json.dll + ..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll + True diff --git a/src/SMAPI/packages.config b/src/SMAPI/packages.config index 1a0b78fa..3e876922 100644 --- a/src/SMAPI/packages.config +++ b/src/SMAPI/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file -- cgit From 0bcc1f6be95af4a309ff9bb31750f29b4b8338df Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 25 Mar 2018 13:28:38 -0400 Subject: standardise folder checks when reloading assets (#459) --- src/SMAPI/Metadata/CoreAssetPropagator.cs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 277da525..38b205a5 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -326,19 +326,19 @@ namespace StardewModdingAPI.Metadata } // dynamic textures - if (key.StartsWith(this.GetNormalisedPath("Buildings\\"), StringComparison.InvariantCultureIgnoreCase)) + if (this.IsInFolder(key, "Buildings")) return this.ReloadBuildings(content, key); - if (key.StartsWith(this.GetNormalisedPath("Characters\\")) && this.CountSegments(key) == 2) // ignore Characters/Dialogue/*, etc + if (this.IsInFolder(key, "Characters")) return this.ReloadNpcSprites(content, key, monster: false); - if (key.StartsWith(this.GetNormalisedPath("Characters\\Monsters\\"))) + if (this.IsInFolder(key, "Characters\\Monsters")) return this.ReloadNpcSprites(content, key, monster: true); - if (key.StartsWith(this.GetNormalisedPath("LooseSprites\\Fence"))) + if (key.StartsWith(this.GetNormalisedPath("LooseSprites\\Fence"), StringComparison.InvariantCultureIgnoreCase)) return this.ReloadFenceTextures(content, key); - if (key.StartsWith(this.GetNormalisedPath("Portraits\\"))) + if (this.IsInFolder(key, "Portraits")) return this.ReloadNpcPortraits(content, key); return false; @@ -348,6 +348,9 @@ namespace StardewModdingAPI.Metadata /********* ** Private methods *********/ + /**** + ** Reload methods + ****/ /// Reload building textures. /// The content manager through which to reload the asset. /// The asset key to reload. @@ -491,6 +494,9 @@ namespace StardewModdingAPI.Metadata return false; } + /**** + ** Helpers + ****/ /// Get an NPC name from the name of their file under Content/Characters. /// The file name. /// Derived from . @@ -527,6 +533,17 @@ namespace StardewModdingAPI.Metadata } } + /// Get whether a normalised asset key is in the given folder. + /// The normalised asset key (like Animals/cat). + /// The key folder (like Animals); doesn't need to be normalised. + /// Whether to return true if the key is inside a subfolder of the . + private bool IsInFolder(string key, string folder, bool allowSubfolders = false) + { + return + key.StartsWith(this.GetNormalisedPath($"{folder}\\"), StringComparison.InvariantCultureIgnoreCase) + && (allowSubfolders || this.CountSegments(key) == this.CountSegments(folder) + 1); + } + /// Get the segments in a path (e.g. 'a/b' is 'a' and 'b'). /// The path to check. private string[] GetSegments(string path) -- cgit From 60fc4a64886d92e70475af5bbfeb29b2e3ef26cd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 25 Mar 2018 14:59:06 -0400 Subject: update animal textures when changed through the content API (#459) --- docs/release-notes.md | 4 +- src/SMAPI/Metadata/CoreAssetPropagator.cs | 125 ++++++++++++++++++++++++------ 2 files changed, 104 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 2a134d13..c30d3a3b 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -17,8 +17,8 @@ ## 2.5.4 * For players: - * Fixed fence, NPC, and tree textures not updated when a mod changes them through the content API. - * Fixed visual bug on Linux/Mac when mods overlay images through the content API. + * Fixed some textures not updated when a mod changes them (notably animals, fences, NPCs, and trees). + * Fixed visual bug on Linux/Mac when mods overlay textures. * Fixed error when a mod removes an asset editor/loader. * Fixed minimum game version incorrectly changed from 1.2.30 to 1.2.33 in SMAPI 2.5.3. diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 38b205a5..e54e0286 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -7,6 +7,7 @@ using StardewModdingAPI.Framework.Reflection; using StardewValley; using StardewValley.BellsAndWhistles; using StardewValley.Buildings; +using StardewValley.Characters; using StardewValley.Locations; using StardewValley.Menus; using StardewValley.Objects; @@ -65,6 +66,16 @@ namespace StardewModdingAPI.Metadata Reflector reflection = this.Reflection; switch (key.ToLower().Replace("/", "\\")) // normalised key so we can compare statically { + /**** + ** Animals + ****/ + case "animals\\cat": + return this.ReloadPetOrHorseSprites(content, key); + case "animals\\dog": + return this.ReloadPetOrHorseSprites(content, key); + case "animals\\horse": + return this.ReloadPetOrHorseSprites(content, key); + /**** ** Buildings ****/ @@ -326,6 +337,9 @@ namespace StardewModdingAPI.Metadata } // dynamic textures + if (this.IsInFolder(key, "Animals")) + return this.ReloadFarmAnimalSprites(content, key); + if (this.IsInFolder(key, "Buildings")) return this.ReloadBuildings(content, key); @@ -351,6 +365,57 @@ namespace StardewModdingAPI.Metadata /**** ** Reload methods ****/ + /// Reload the sprites for matching pets or horses. + /// The animal type. + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// Returns whether any textures were reloaded. + private bool ReloadPetOrHorseSprites(LocalizedContentManager content, string key) + where TAnimal : NPC + { + // find matches + TAnimal[] animals = this.GetCharacters().OfType().ToArray(); + if (!animals.Any()) + return false; + + // update sprites + Texture2D texture = content.Load(key); + foreach (TAnimal animal in animals) + this.SetSpriteTexture(animal.sprite, texture); + return true; + } + + /// Reload the sprites for matching farm animals. + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// Returns whether any textures were reloaded. + /// Derived from . + private bool ReloadFarmAnimalSprites(LocalizedContentManager content, string key) + { + // find matches + FarmAnimal[] animals = this.GetFarmAnimals().ToArray(); + if (!animals.Any()) + return false; + + // update sprites + Lazy texture = new Lazy(() => content.Load(key)); + foreach (FarmAnimal animal in animals) + { + // get expected key + string expectedKey = animal.age < animal.ageWhenMature + ? $"Baby{(animal.type == "Duck" ? "White Chicken" : animal.type)}" + : animal.type; + if (animal.showDifferentTextureWhenReadyForHarvest && animal.currentProduce <= 0) + expectedKey = $"Sheared{expectedKey}"; + expectedKey = $"Animals\\{expectedKey}"; + + // reload asset + if (expectedKey == key) + this.SetSpriteTexture(animal.sprite, texture.Value); + } + return texture.IsValueCreated; + } + /// Reload building textures. /// The content manager through which to reload the asset. /// The asset key to reload. @@ -417,26 +482,14 @@ namespace StardewModdingAPI.Metadata { // get NPCs string name = this.GetNpcNameFromFileName(Path.GetFileName(key)); - NPC[] characters = - ( - from location in this.GetLocations() - from npc in location.characters - where npc.name == name && npc.IsMonster == monster - select npc - ) - .Distinct() - .ToArray(); + NPC[] characters = this.GetCharacters().Where(npc => npc.name == name && npc.IsMonster == monster).ToArray(); if (!characters.Any()) return false; // update portrait Texture2D texture = content.Load(key); foreach (NPC character in characters) -#if STARDEW_VALLEY_1_3 - this.Reflection.GetField(character.Sprite, "spriteTexture").SetValue(texture); -#else - character.Sprite.Texture = texture; -#endif + this.SetSpriteTexture(character.Sprite, texture); return true; } @@ -448,15 +501,7 @@ namespace StardewModdingAPI.Metadata { // get NPCs string name = this.GetNpcNameFromFileName(Path.GetFileName(key)); - NPC[] villagers = - ( - from location in this.GetLocations() - from npc in location.characters - where npc.name == name && npc.isVillager() - select npc - ) - .Distinct() - .ToArray(); + NPC[] villagers = this.GetCharacters().Where(npc => npc.name == name && npc.isVillager()).ToArray(); if (!villagers.Any()) return false; @@ -497,6 +542,18 @@ namespace StardewModdingAPI.Metadata /**** ** Helpers ****/ + /// Reload the texture for an animated sprite. + /// The animated sprite to update. + /// The texture to set. + private void SetSpriteTexture(AnimatedSprite sprite, Texture2D texture) + { +#if STARDEW_VALLEY_1_3 + this.Reflection.GetField(sprite, "spriteTexture").SetValue(texture); +#else + sprite.Texture = texture; +#endif + } + /// Get an NPC name from the name of their file under Content/Characters. /// The file name. /// Derived from . @@ -515,6 +572,28 @@ namespace StardewModdingAPI.Metadata } } + /// Get all NPCs in the game (excluding farm animals). + private IEnumerable GetCharacters() + { + return this.GetLocations().SelectMany(p => p.characters); + } + + /// Get all farm animals in the game. + private IEnumerable GetFarmAnimals() + { + foreach (GameLocation location in this.GetLocations()) + { + if (location is Farm farm) + { + foreach (FarmAnimal animal in farm.animals.Values) + yield return animal; + } + else if (location is AnimalHouse animalHouse) + foreach (FarmAnimal animal in animalHouse.animals.Values) + yield return animal; + } + } + /// Get all locations in the game. private IEnumerable GetLocations() { -- cgit From 56288e1d0ec072d35040b7954fc7c0f8b086663e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 26 Mar 2018 09:22:45 -0400 Subject: fix log parser timestamp not rendered --- src/SMAPI.Web/Views/LogParser/Index.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/SMAPI.Web/Views/LogParser/Index.cshtml b/src/SMAPI.Web/Views/LogParser/Index.cshtml index 9c21c8c0..7213e286 100644 --- a/src/SMAPI.Web/Views/LogParser/Index.cshtml +++ b/src/SMAPI.Web/Views/LogParser/Index.cshtml @@ -70,7 +70,7 @@ Log started: - @Model.ParsedLog.Timestamp.UtcDateTime.ToString("yyyy-MM-dd HH:mm") UTC ({{localTimeStarted}} your time) + @Model.ParsedLog.Timestamp.UtcDateTime.ToString("yyyy-MM-dd HH:mm") UTC ({{localTimeStarted}} your time)
    -- cgit From 4d68ef3514de7deb357a0042d1af7ccf241ab5ff Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 26 Mar 2018 09:34:45 -0400 Subject: update for 2.5.4 release --- build/GlobalAssemblyInfo.cs | 4 ++-- docs/release-notes.md | 12 +++++++++--- src/SMAPI.Mods.ConsoleCommands/manifest.json | 2 +- src/SMAPI/Constants.cs | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/build/GlobalAssemblyInfo.cs b/build/GlobalAssemblyInfo.cs index e9236498..d2cf8fe7 100644 --- a/build/GlobalAssemblyInfo.cs +++ b/build/GlobalAssemblyInfo.cs @@ -1,5 +1,5 @@ using System.Reflection; [assembly: AssemblyProduct("SMAPI")] -[assembly: AssemblyVersion("2.5.3")] -[assembly: AssemblyFileVersion("2.5.3")] +[assembly: AssemblyVersion("2.5.4")] +[assembly: AssemblyFileVersion("2.5.4")] diff --git a/docs/release-notes.md b/docs/release-notes.md index c30d3a3b..b3300800 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -17,14 +17,20 @@ ## 2.5.4 * For players: - * Fixed some textures not updated when a mod changes them (notably animals, fences, NPCs, and trees). + * Fixed some textures not updated when a mod changes them. * Fixed visual bug on Linux/Mac when mods overlay textures. - * Fixed error when a mod removes an asset editor/loader. - * Fixed minimum game version incorrectly changed from 1.2.30 to 1.2.33 in SMAPI 2.5.3. + * Fixed error when mods remove an asset editor/loader. + * Fixed minimum game version incorrectly increased in SMAPI 2.5.3. * For the [log parser][]: * Fixed error when log text contains certain tokens. +* For modders: + * Updated to Json.NET 11.0.2. + +* For SMAPI developers: + * Added support for beta update track to support upcoming Stardew Valley 1.3 beta. + ## 2.5.3 * For players: * Simplified and improved skipped-mod messages. diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index 785af01a..a56cf66d 100644 --- a/src/SMAPI.Mods.ConsoleCommands/manifest.json +++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json @@ -1,7 +1,7 @@ { "Name": "Console Commands", "Author": "SMAPI", - "Version": "2.5.3", + "Version": "2.5.4", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll" diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 1279f8e1..6270186a 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -41,7 +41,7 @@ namespace StardewModdingAPI #if STARDEW_VALLEY_1_3 new SemanticVersion($"2.6-alpha.{DateTime.UtcNow:yyyyMMddHHmm}"); #else - new SemanticVersion($"2.5.3"); + new SemanticVersion("2.5.4"); #endif /// The minimum supported version of Stardew Valley. -- cgit