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) --- src/SMAPI.Web/Controllers/ModsApiController.cs | 12 ++++++++---- src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs | 7 +++++-- src/SMAPI.Web/appsettings.json | 3 ++- 3 files changed, 15 insertions(+), 7 deletions(-) (limited to 'src') 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) => Critte