diff --git a/build/common.targets b/build/common.targets index 35dcdfc93..ca0438dc8 100644 --- a/build/common.targets +++ b/build/common.targets @@ -7,7 +7,7 @@ repo. It imports the other MSBuild files as needed. - 4.0.1 + 4.0.2 SMAPI latest $(AssemblySearchPaths);{GAC} diff --git a/docs/release-notes.md b/docs/release-notes.md index 0add523cf..73c966961 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,6 +1,16 @@ ← [README](README.md) # Release notes +## 4.0.2 +Released 24 March 2024 for Stardew Valley 1.6.0 or later. + +* For players: + * Updated mod compatibility list. + * Improved status for obsolete mods to be clearer that they can be removed. + * Disabled Extra Map Layers mod. + _Extra Map Layers mod caused visual issues like dark shadows in all locations with extra map layers, since the game now handles them automatically. SMAPI now disables Extra Map Layers and ignores dependencies on it._ + * When using a custom `Mods` folder path, SMAPI now logs the game folder path to simplify troubleshooting. + # 4.0.1 Released 20 March 2024 for Stardew Valley 1.6.0 or later. diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index 0711bc561..26d78aecd 100644 --- a/src/SMAPI.Mods.ConsoleCommands/manifest.json +++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json @@ -1,9 +1,9 @@ { "Name": "Console Commands", "Author": "SMAPI", - "Version": "4.0.1", + "Version": "4.0.2", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "4.0.1" + "MinimumApiVersion": "4.0.2" } diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index 35e5a0bf4..a8852d05a 100644 --- a/src/SMAPI.Mods.SaveBackup/manifest.json +++ b/src/SMAPI.Mods.SaveBackup/manifest.json @@ -1,9 +1,9 @@ { "Name": "Save Backup", "Author": "SMAPI", - "Version": "4.0.1", + "Version": "4.0.2", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "4.0.1" + "MinimumApiVersion": "4.0.2" } diff --git a/src/SMAPI.Tests/Core/ModResolverTests.cs b/src/SMAPI.Tests/Core/ModResolverTests.cs index 70c782ab6..5882febbb 100644 --- a/src/SMAPI.Tests/Core/ModResolverTests.cs +++ b/src/SMAPI.Tests/Core/ModResolverTests.cs @@ -561,7 +561,7 @@ private Mock GetMetadata(IManifest manifest, bool allowStatusChang /// Generate a default mod data record. private ModDataRecord GetModDataRecord() { - return new("Default Display Name", new ModDataModel("Sample ID", null, ModWarning.None)); + return new("Default Display Name", new ModDataModel("Sample ID", null, ModWarning.None, false)); } /// Generate a default mod data versioned fields instance. diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDataModel.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDataModel.cs index 5912fb870..d21e87643 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModDataModel.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModDataModel.cs @@ -28,6 +28,9 @@ internal class ModDataModel /// The mod warnings to suppress, even if they'd normally be shown. public ModWarning SuppressWarnings { get; } + /// Whether to ignore dependencies on this mod ID when it's not loaded. + public bool IgnoreDependencies { get; set; } + /// This field stores properties that aren't mapped to another field before they're parsed into . [JsonExtensionData] public IDictionary ExtensionData { get; } = new Dictionary(); @@ -54,11 +57,13 @@ internal class ModDataModel /// The mod's current unique ID. /// The former mod IDs (if any). /// The mod warnings to suppress, even if they'd normally be shown. - public ModDataModel(string id, string? formerIds, ModWarning suppressWarnings) + /// Whether to ignore dependencies on this mod ID when it's not loaded. + public ModDataModel(string id, string? formerIds, ModWarning suppressWarnings, bool ignoreDependencies) { this.ID = id; this.FormerIDs = formerIds; this.SuppressWarnings = suppressWarnings; + this.IgnoreDependencies = ignoreDependencies; } /// Get a parsed representation of the . diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs index ab0e43775..938e9e5a6 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs @@ -22,6 +22,9 @@ public class ModDataRecord /// The mod warnings to suppress, even if they'd normally be shown. public ModWarning SuppressWarnings { get; } + /// Whether to ignore dependencies on this mod ID when it's not loaded. + public bool IgnoreDependencies { get; set; } + /// The versioned field data. public ModDataField[] Fields { get; } @@ -38,6 +41,7 @@ internal ModDataRecord(string displayName, ModDataModel model) this.ID = model.ID; this.FormerIDs = model.GetFormerIDs().ToArray(); this.SuppressWarnings = model.SuppressWarnings; + this.IgnoreDependencies = model.IgnoreDependencies; this.Fields = model.GetFields().ToArray(); } diff --git a/src/SMAPI.Web/Controllers/IndexController.cs b/src/SMAPI.Web/Controllers/IndexController.cs index 522d77cd2..bea118874 100644 --- a/src/SMAPI.Web/Controllers/IndexController.cs +++ b/src/SMAPI.Web/Controllers/IndexController.cs @@ -62,8 +62,8 @@ public async Task Index() // render view IndexVersionModel stableVersionModel = stableVersion != null - ? new IndexVersionModel(stableVersion.Version.ToString(), stableVersion.Release.Body, stableVersion.Asset.DownloadUrl, stableVersionForDevs?.Asset.DownloadUrl) - : new IndexVersionModel("unknown", "", "https://github.com/Pathoschild/SMAPI/releases", null); // just in case something goes wrong + ? new IndexVersionModel(stableVersion.Version.ToString(), stableVersion.Release.Body, stableVersion.Release.WebUrl, stableVersion.Asset.DownloadUrl, stableVersionForDevs?.Asset.DownloadUrl) + : new IndexVersionModel("unknown", "", "https://github.com/Pathoschild/SMAPI/releases", "https://github.com/Pathoschild/SMAPI/releases", null); // just in case something goes wrong // render view var model = new IndexModel(stableVersionModel, this.SiteConfig.OtherBlurb, this.SiteConfig.SupporterList); diff --git a/src/SMAPI.Web/Framework/Clients/GitHub/GitRelease.cs b/src/SMAPI.Web/Framework/Clients/GitHub/GitRelease.cs index 9de6f020d..79500d936 100644 --- a/src/SMAPI.Web/Framework/Clients/GitHub/GitRelease.cs +++ b/src/SMAPI.Web/Framework/Clients/GitHub/GitRelease.cs @@ -17,6 +17,10 @@ internal class GitRelease [JsonProperty("tag_name")] public string Tag { get; } + /// The URL to the release web page. + [JsonProperty("html_url")] + public string WebUrl { get; } + /// The Markdown description for the release. public string Body { get; internal set; } @@ -38,14 +42,16 @@ internal class GitRelease /// Construct an instance. /// The display name. /// The semantic version string. + /// The URL to the release web page. /// The Markdown description for the release. /// Whether this is a draft version. /// Whether this is a prerelease version. /// The attached files. - public GitRelease(string name, string tag, string? body, bool isDraft, bool isPrerelease, GitAsset[]? assets) + public GitRelease(string name, string tag, string webUrl, string? body, bool isDraft, bool isPrerelease, GitAsset[]? assets) { this.Name = name; this.Tag = tag; + this.WebUrl = webUrl; this.Body = body ?? string.Empty; this.IsDraft = isDraft; this.IsPrerelease = isPrerelease; diff --git a/src/SMAPI.Web/ViewModels/IndexVersionModel.cs b/src/SMAPI.Web/ViewModels/IndexVersionModel.cs index a76a5924b..248102b50 100644 --- a/src/SMAPI.Web/ViewModels/IndexVersionModel.cs +++ b/src/SMAPI.Web/ViewModels/IndexVersionModel.cs @@ -12,10 +12,13 @@ public class IndexVersionModel /// The Markdown description for the release. public string Description { get; } - /// The main download URL. + /// The URL to the download page. + public string WebUrl { get; } + + /// The direct download URL for the main version. public string DownloadUrl { get; } - /// The for-developers download URL (not applicable for prerelease versions). + /// The direct download URL for the for-developers version. Not applicable for prerelease versions. public string? DevDownloadUrl { get; } @@ -25,12 +28,14 @@ public class 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) + /// The URL to the download page. + /// The direct download URL for the main version. + /// The direct download URL for the for-developers version. Not applicable for prerelease versions. + internal IndexVersionModel(string version, string description, string webUrl, string downloadUrl, string? devDownloadUrl) { this.Version = version; this.Description = description; + this.WebUrl = webUrl; 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 29befae65..5651594fd 100644 --- a/src/SMAPI.Web/Views/Index/Index.cshtml +++ b/src/SMAPI.Web/Views/Index/Index.cshtml @@ -28,7 +28,7 @@ diff --git a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json index ff6cc02b3..52a27c83f 100644 --- a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json +++ b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json @@ -109,37 +109,43 @@ "Animal Mood Fix": { "ID": "GPeters-AnimalMoodFix", "~ | Status": "Obsolete", - "~ | StatusReasonPhrase": "the animal mood bugs were fixed in Stardew Valley 1.2." + "~ | StatusReasonPhrase": "the animal mood bugs were fixed in Stardew Valley 1.2. You can delete this mod." }, "Bee House Flower Range Fix": { "ID": "kirbylink.beehousefix", "~ | Status": "Obsolete", - "~ | StatusReasonPhrase": "the bee house flower range was fixed in Stardew Valley 1.4." + "~ | StatusReasonPhrase": "the bee house flower range was fixed in Stardew Valley 1.4. You can delete this mod." }, "Colored Chests": { "ID": "4befde5c-731c-4853-8e4b-c5cdf946805f", "~ | Status": "Obsolete", - "~ | StatusReasonPhrase": "colored chests were added in Stardew Valley 1.1." + "~ | StatusReasonPhrase": "colored chests were added in Stardew Valley 1.1. You can delete this mod." }, "Error Handler": { "ID": "SMAPI.ErrorHandler", "~ | Status": "Obsolete", - "~ | StatusReasonPhrase": "its error handling was integrated into Stardew Valley 1.6." + "~ | StatusReasonPhrase": "its error handling was integrated into Stardew Valley 1.6. You can delete this mod." + }, + "Extra Map Layers": { + "ID": "aedenthorn.ExtraMapLayers", + "~0.3.10 | Status": "Obsolete", + "~0.3.10 | StatusReasonPhrase": "extra map layer support was added in Stardew Valley 1.6. You can delete this mod.", + "IgnoreDependencies": true }, "Modder Serialization Utility": { "ID": "SerializerUtils-0-1", "~ | Status": "Obsolete", - "~ | StatusReasonPhrase": "it's no longer maintained or used." + "~ | StatusReasonPhrase": "it's no longer maintained or used. You can delete this mod." }, "No Debug Mode": { "ID": "NoDebugMode", "~ | Status": "Obsolete", - "~ | StatusReasonPhrase": "debug mode was removed in SMAPI 1.0." + "~ | StatusReasonPhrase": "debug mode was removed in SMAPI 1.0. You can delete this mod." }, "Split Screen": { "ID": "Ilyaki.SplitScreen", "~ | Status": "Obsolete", - "~ | StatusReasonPhrase": "split-screen mode was added in Stardew Valley 1.5" + "~ | StatusReasonPhrase": "split-screen mode was added in Stardew Valley 1.5. You can delete this mod." }, /********* @@ -150,6 +156,11 @@ "~0.3.1 | Status": "AssumeBroken", "~0.3.1 | StatusReasonDetails": "Harmony patches fail at runtime" }, + "Aimon's Fancy Farmhouse": { + "ID": "Aimon111.RedesignedFarmHouseLayoutAlt", + "~2.0.0 | Status": "AssumeBroken", + "~2.0.0 | StatusReasonDetails": "breaks farmhouse layout and causes runtime crashes" + }, "All Chests Menu": { "ID": "aedenthorn.AllChestsMenu", "~0.3.1 | Status": "AssumeBroken", @@ -172,8 +183,8 @@ }, "Better Elevator": { "ID": "aedenthorn.BetterElevator", - "~2.1.0 | Status": "AssumeBroken", - "~2.1.0 | StatusReasonDetails": "Harmony patches fail at runtime" + "~0.2.4 | Status": "AssumeBroken", + "~0.2.4 | StatusReasonDetails": "Harmony patches fail at runtime" }, "Better Crab Pots": { "ID": "EpicBellyFlop45.BetterCrabPots", @@ -505,6 +516,11 @@ "~1.0.0 | Status": "AssumeBroken", "~1.0.0 | StatusReasonDetails": "affected by breaking changes in the Json Assets mod API" }, + "TimeSpeed": { + "ID": "cantorsdust.TimeSpeed", + "~2.7.7-alpha.20240220 | Status": "AssumeBroken", + "~2.7.7-alpha.20240220 | StatusReasonDetails": "causes runtime time issues before 2.7.7" + }, "Wealth is Health": { "ID": "QiTheMysterious.WealthIsHealth", "~0.1.2 | Status": "AssumeBroken", diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 7acc8ca54..c05276c2c 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -49,7 +49,7 @@ internal static class EarlyConstants internal static int? LogScreenId { get; set; } /// SMAPI's current raw semantic version. - internal static string RawApiVersion = "4.0.1"; + internal static string RawApiVersion = "4.0.2"; } /// Contains SMAPI's constants and assumptions. diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index a9901a61d..d625dd540 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -226,7 +226,7 @@ public void LogIntro(string modsPath, IDictionary customSetting // log basic info this.Monitor.Log($"Mods go here: {modsPath}", LogLevel.Info); if (modsPath != Constants.DefaultModsPath) - this.Monitor.Log("(Using custom --mods-path argument.)"); + this.Monitor.Log($"(Using custom --mods-path argument. Game folder: {Constants.GamePath}.)"); this.Monitor.Log($"Log started at {DateTime.UtcNow:s} UTC"); // log custom settings diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index 607bb70dc..8f346a1bd 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -335,6 +335,7 @@ from entry in dependencies // sorted successfully case ModDependencyStatus.Sorted: case ModDependencyStatus.Failed when !dependency.IsRequired: // ignore failed optional dependency + case ModDependencyStatus.Failed when modDatabase.Get(dependency.ID)?.IgnoreDependencies is true: // ignore failed dependency based on SMAPI metadata break; // failed, which means this mod can't be loaded either diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index c138c85c4..46e8f8aff 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1846,15 +1846,21 @@ private bool TryLoadMod(IModMetadata mod, IModMetadata[] mods, AssemblyLoader as // Although dependencies are validated before mods are loaded, a dependency may have failed to load. foreach (IManifestDependency dependency in manifest.Dependencies.Where(p => p.IsRequired)) { - if (this.ModRegistry.Get(dependency.UniqueID) == null) - { - string dependencyName = mods - .FirstOrDefault(otherMod => otherMod.HasID(dependency.UniqueID)) - ?.DisplayName ?? dependency.UniqueID; - errorReasonPhrase = $"it needs the '{dependencyName}' mod, which couldn't be loaded."; - failReason = ModFailReason.MissingDependencies; - return false; - } + // not missing + if (this.ModRegistry.Get(dependency.UniqueID) != null) + continue; + + // ignored in compatibility list (e.g. fully replaced by the game code) + if (modDatabase.Get(dependency.UniqueID)?.IgnoreDependencies is true) + continue; + + // mark failed + string dependencyName = mods + .FirstOrDefault(otherMod => otherMod.HasID(dependency.UniqueID)) + ?.DisplayName ?? dependency.UniqueID; + errorReasonPhrase = $"it needs the '{dependencyName}' mod, which couldn't be loaded."; + failReason = ModFailReason.MissingDependencies; + return false; } // load as content pack