diff options
50 files changed, 746 insertions, 73 deletions
diff --git a/build/common.targets b/build/common.targets index 1e14a86c..41bea8af 100644 --- a/build/common.targets +++ b/build/common.targets @@ -4,7 +4,7 @@ <!--set properties --> <PropertyGroup> - <Version>3.4.1</Version> + <Version>3.5.0</Version> <Product>SMAPI</Product> <AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths> diff --git a/docs/README.md b/docs/README.md index 546ee6b3..4726c190 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ **SMAPI** is an open-source modding framework and API for [Stardew Valley](https://stardewvalley.net/) that lets you play the game with mods. It's safely installed alongside the game's executable, and -doesn't change any of your game files. It serves eight main purposes: +doesn't change any of your game files. It serves seven main purposes: 1. **Load mods into the game.** _SMAPI loads mods when the game is starting up so they can interact with it. (Code mods aren't @@ -10,14 +10,10 @@ doesn't change any of your game files. It serves eight main purposes: _SMAPI provides APIs and events which let mods interact with the game in ways they otherwise couldn't._ -3. **Rewrite mods for crossplatform compatibility.** +3. **Rewrite mods for compatibility.** _SMAPI rewrites mods' compiled code before loading them so they work on Linux/Mac/Windows without the mods needing to handle differences between the Linux/Mac and Windows versions of the - game._ - -4. **Rewrite mods to update them.** - _SMAPI detects when a mod accesses part of the game that changed in a game update which affects - many mods, and rewrites the mod so it's compatible._ + game. In some cases it also rewrites code broken by a game update so the mod doesn't break._ 5. **Intercept errors and automatically fix saves.** _SMAPI intercepts errors, shows the error info in the SMAPI console, and in most cases @@ -37,8 +33,8 @@ doesn't change any of your game files. It serves eight main purposes: they cause problems._ 8. **Back up your save files.** - _SMAPI automatically creates a daily backup of your saves and keeps ten backups, in case - something goes wrong. (Via the bundled SaveBackup mod.)_ + _SMAPI automatically creates a daily backup of your saves and keeps ten backups (via the bundled + Save Backup mod), in case something goes wrong._ ## Documentation Have questions? Come [ask the community](https://smapi.io/community) to get help from SMAPI @@ -69,7 +65,7 @@ German | ✓ [fully translated](../src/SMAPI/i18n/de.json) Hungarian | ✓ [fully translated](../src/SMAPI/i18n/hu.json) Italian | ✓ [fully translated](../src/SMAPI/i18n/it.json) Japanese | ✓ [fully translated](../src/SMAPI/i18n/ja.json) -Korean | ❑ not translated +Korean | ✓ [fully translated](../src/SMAPI/i18n/ko.json) Portuguese | ✓ [fully translated](../src/SMAPI/i18n/pt.json) Russian | ✓ [fully translated](../src/SMAPI/i18n/ru.json) Spanish | ✓ [fully translated](../src/SMAPI/i18n/es.json) diff --git a/docs/release-notes.md b/docs/release-notes.md index 185ddc69..97aabd37 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,6 +1,33 @@ ← [README](README.md) # Release notes +## 3.5 +Released 27 April 2020 for Stardew Valley 1.4.1 or later. + +* For players: + * SMAPI now prevents more game errors due to broken items, so you no longer need save editing to remove them. + * Added option to disable console colors. + * Updated compatibility list. + * Improved translations.¹ + +* For the Console Commands mod: + * Commands like `world_setday` now also affect the 'days played' stat, so in-game events/randomization match what you'd get if you played to that date normally (thanks to kdau!). + +* For the web UI: + * Updated the JSON validator/schema for Content Patcher 1.13. + * Fixed rare intermittent "CGI application encountered an error" errors. + +* For modders: + * Added map patching to the content API (via `asset.AsMap()`). + * Added support for using patch helpers with arbitrary data (via `helper.Content.GetPatchHelper`). + * Added `SDate` fields/methods: `SeasonIndex`, `FromDaysSinceStart`, `FromWorldDate`, `ToWorldDate`, and `ToLocaleString` (thanks to kdau!). + * Added `SDate` translations taken from the Lookup Anything mod.¹ + * Fixed asset propagation for certain maps loaded through temporary content managers. This notably fixes unreliable patches to the farmhouse and town maps. + * Fixed asset propagation on Linux/Mac for monster sprites, NPC dialogue, and NPC schedules. + * Fixed asset propagation for NPC dialogue sometimes causing a spouse to skip marriage dialogue or not allow kisses. + +¹ Date format translations were taken from the Lookup Anything mod; thanks to translators FixThisPlz (improved Russian), LeecanIt (added Italian), pomepome (added Japanese), S2SKY (added Korean), Sasara (added German), SteaNN (added Russian), ThomasGabrielDelavault (added Spanish), VincentRoth (added French), Yllelder (improved Spanish), and yuwenlan (added Chinese). Some translations for Korean, Hungarian, and Turkish were derived from the game translations. + ## 3.4.1 Released 24 March 2020 for Stardew Valley 1.4.1 or later. diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index 2d58baf0..5b0c6e1f 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -88,8 +88,8 @@ namespace StardewModdingApi.Installer yield return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley", "ErrorLogs"); // remove old log files } - /// <summary>Handles writing color-coded text to the console.</summary> - private ColorfulConsoleWriter ConsoleWriter; + /// <summary>Handles writing text to the console.</summary> + private IConsoleWriter ConsoleWriter; /********* diff --git a/src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs b/src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs index aefda9b6..b5bd4600 100644 --- a/src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs +++ b/src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs @@ -4,8 +4,8 @@ using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingAPI.Internal.ConsoleWriting { - /// <summary>Provides a wrapper for writing color-coded text to the console.</summary> - internal class ColorfulConsoleWriter + /// <summary>Writes color-coded text to the console.</summary> + internal class ColorfulConsoleWriter : IConsoleWriter { /********* ** Fields @@ -30,8 +30,16 @@ namespace StardewModdingAPI.Internal.ConsoleWriting /// <param name="colorConfig">The colors to use for text written to the SMAPI console.</param> public ColorfulConsoleWriter(Platform platform, ColorSchemeConfig colorConfig) { - this.SupportsColor = this.TestColorSupport(); - this.Colors = this.GetConsoleColorScheme(platform, colorConfig); + if (colorConfig.UseScheme == MonitorColorScheme.None) + { + this.SupportsColor = false; + this.Colors = null; + } + else + { + this.SupportsColor = this.TestColorSupport(); + this.Colors = this.GetConsoleColorScheme(platform, colorConfig); + } } /// <summary>Write a message line to the log.</summary> diff --git a/src/SMAPI.Internal/ConsoleWriting/IConsoleWriter.cs b/src/SMAPI.Internal/ConsoleWriting/IConsoleWriter.cs new file mode 100644 index 00000000..fbcf161c --- /dev/null +++ b/src/SMAPI.Internal/ConsoleWriting/IConsoleWriter.cs @@ -0,0 +1,11 @@ +namespace StardewModdingAPI.Internal.ConsoleWriting +{ + /// <summary>Writes text to the console.</summary> + internal interface IConsoleWriter + { + /// <summary>Write a message line to the log.</summary> + /// <param name="message">The message to log.</param> + /// <param name="level">The log level.</param> + void WriteLine(string message, ConsoleLogLevel level); + } +} diff --git a/src/SMAPI.Internal/ConsoleWriting/MonitorColorScheme.cs b/src/SMAPI.Internal/ConsoleWriting/MonitorColorScheme.cs index bccb56d7..994ea6a5 100644 --- a/src/SMAPI.Internal/ConsoleWriting/MonitorColorScheme.cs +++ b/src/SMAPI.Internal/ConsoleWriting/MonitorColorScheme.cs @@ -10,6 +10,9 @@ namespace StardewModdingAPI.Internal.ConsoleWriting DarkBackground, /// <summary>Use darker text colors that look better on a white or light background.</summary> - LightBackground + LightBackground, + + /// <summary>Disable console color.</summary> + None } } diff --git a/src/SMAPI.Internal/SMAPI.Internal.projitems b/src/SMAPI.Internal/SMAPI.Internal.projitems index 7fcebc94..0d583a6d 100644 --- a/src/SMAPI.Internal/SMAPI.Internal.projitems +++ b/src/SMAPI.Internal/SMAPI.Internal.projitems @@ -12,6 +12,7 @@ <Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\ColorfulConsoleWriter.cs" /> <Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\ColorSchemeConfig.cs" /> <Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\ConsoleLogLevel.cs" /> + <Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\IConsoleWriter.cs" /> <Compile Include="$(MSBuildThisFileDirectory)ConsoleWriting\MonitorColorScheme.cs" /> </ItemGroup> </Project>
\ No newline at end of file diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetDayCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetDayCommand.cs index 8d6bd759..23c266ea 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetDayCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetDayCommand.cs @@ -1,4 +1,5 @@ using System.Linq; +using StardewModdingAPI.Utilities; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World @@ -32,6 +33,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World // handle Game1.dayOfMonth = day; + Game1.stats.DaysPlayed = (uint)SDate.Now().DaysSinceStart; monitor.Log($"OK, the date is now {Game1.currentSeason} {Game1.dayOfMonth}.", LogLevel.Info); } } diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetSeasonCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetSeasonCommand.cs index 0615afe7..676369fe 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetSeasonCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetSeasonCommand.cs @@ -1,4 +1,5 @@ using System.Linq; +using StardewModdingAPI.Utilities; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World @@ -40,6 +41,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World // handle Game1.currentSeason = season.ToLower(); Game1.setGraphicsForSeason(); + Game1.stats.DaysPlayed = (uint)SDate.Now().DaysSinceStart; monitor.Log($"OK, the date is now {Game1.currentSeason} {Game1.dayOfMonth}.", LogLevel.Info); } } diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetYearCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetYearCommand.cs index 66abd6dc..648830c1 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetYearCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetYearCommand.cs @@ -1,4 +1,5 @@ using System.Linq; +using StardewModdingAPI.Utilities; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World @@ -32,6 +33,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World // handle Game1.year = year; + Game1.stats.DaysPlayed = (uint)SDate.Now().DaysSinceStart; monitor.Log($"OK, the year is now {Game1.year}.", LogLevel.Info); } } diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index a55d168f..908d4f65 100644 --- a/src/SMAPI.Mods.ConsoleCommands/manifest.json +++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json @@ -1,9 +1,9 @@ { "Name": "Console Commands", "Author": "SMAPI", - "Version": "3.4.1", + "Version": "3.5.0", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", "EntryDll": "ConsoleCommands.dll", - "MinimumApiVersion": "3.4.1" + "MinimumApiVersion": "3.5.0" } diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index 5bf35b5c..cd42459e 100644 --- a/src/SMAPI.Mods.SaveBackup/manifest.json +++ b/src/SMAPI.Mods.SaveBackup/manifest.json @@ -1,9 +1,9 @@ { "Name": "Save Backup", "Author": "SMAPI", - "Version": "3.4.1", + "Version": "3.5.0", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", "EntryDll": "SaveBackup.dll", - "MinimumApiVersion": "3.4.1" + "MinimumApiVersion": "3.5.0" } diff --git a/src/SMAPI.Tests/Core/ModResolverTests.cs b/src/SMAPI.Tests/Core/ModResolverTests.cs index a9c88c60..45b3673b 100644 --- a/src/SMAPI.Tests/Core/ModResolverTests.cs +++ b/src/SMAPI.Tests/Core/ModResolverTests.cs @@ -73,7 +73,7 @@ namespace SMAPI.Tests.Core [nameof(IManifest.Description)] = Sample.String(), [nameof(IManifest.UniqueID)] = $"{Sample.String()}.{Sample.String()}", [nameof(IManifest.EntryDll)] = $"{Sample.String()}.dll", - [nameof(IManifest.MinimumApiVersion)] = $"{Sample.Int()}.{Sample.Int()}-{Sample.String()}", + [nameof(IManifest.MinimumApiVersion)] = $"{Sample.Int()}.{Sample.Int()}.{Sample.Int()}-{Sample.String()}", [nameof(IManifest.Dependencies)] = new[] { originalDependency }, ["ExtraString"] = Sample.String(), ["ExtraInt"] = Sample.Int() diff --git a/src/SMAPI.Tests/Utilities/SDateTests.cs b/src/SMAPI.Tests/Utilities/SDateTests.cs index d25a101a..0461952e 100644 --- a/src/SMAPI.Tests/Utilities/SDateTests.cs +++ b/src/SMAPI.Tests/Utilities/SDateTests.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text.RegularExpressions; using NUnit.Framework; using StardewModdingAPI.Utilities; +using StardewValley; namespace SMAPI.Tests.Utilities { @@ -82,6 +83,62 @@ namespace SMAPI.Tests.Utilities } /**** + ** FromDaysSinceStart + ****/ + [Test(Description = "Assert that FromDaysSinceStart returns the expected date.")] + [TestCase(1, ExpectedResult = "01 spring Y1")] + [TestCase(2, ExpectedResult = "02 spring Y1")] + [TestCase(28, ExpectedResult = "28 spring Y1")] + [TestCase(29, ExpectedResult = "01 summer Y1")] + [TestCase(141, ExpectedResult = "01 summer Y2")] + public string FromDaysSinceStart(int daysSinceStart) + { + // act + return SDate.FromDaysSinceStart(daysSinceStart).ToString(); + } + + [Test(Description = "Assert that FromDaysSinceStart throws an exception if the number of days is invalid.")] + [TestCase(-1)] // day < 0 + [TestCase(0)] // day == 0 + [SuppressMessage("ReSharper", "AssignmentIsFullyDiscarded", Justification = "Deliberate for unit test.")] + public void FromDaysSinceStart_RejectsInvalidValues(int daysSinceStart) + { + // act & assert + Assert.Throws<ArgumentException>(() => _ = SDate.FromDaysSinceStart(daysSinceStart), "Passing the invalid number of days didn't throw the expected exception."); + } + + /**** + ** From + ****/ + [Test(Description = "Assert that SDate.From constructs the correct instance for a given date.")] + [TestCase(0, ExpectedResult = "01 spring Y1")] + [TestCase(1, ExpectedResult = "02 spring Y1")] + [TestCase(27, ExpectedResult = "28 spring Y1")] + [TestCase(28, ExpectedResult = "01 summer Y1")] + [TestCase(140, ExpectedResult = "01 summer Y2")] + public string From_WorldDate(int totalDays) + { + return SDate.From(new WorldDate { TotalDays = totalDays }).ToString(); + } + + + /**** + ** SeasonIndex + ****/ + [Test(Description = "Assert the numeric index of the season.")] + [TestCase("01 spring Y1", ExpectedResult = 0)] + [TestCase("02 summer Y1", ExpectedResult = 1)] + [TestCase("28 fall Y1", ExpectedResult = 2)] + [TestCase("01 winter Y1", ExpectedResult = 3)] + [TestCase("01 winter Y2", ExpectedResult = 3)] + public int SeasonIndex(string dateStr) + { + // act + return this.GetDate(dateStr).SeasonIndex; + } + + + /**** ** DayOfWeek ****/ [Test(Description = "Assert the day of week.")] @@ -119,6 +176,7 @@ namespace SMAPI.Tests.Utilities return this.GetDate(dateStr).DayOfWeek; } + /**** ** DaysSinceStart ****/ @@ -134,6 +192,7 @@ namespace SMAPI.Tests.Utilities return this.GetDate(dateStr).DaysSinceStart; } + /**** ** ToString ****/ @@ -147,6 +206,7 @@ namespace SMAPI.Tests.Utilities return this.GetDate(dateStr).ToString(); } + /**** ** AddDays ****/ @@ -166,6 +226,18 @@ namespace SMAPI.Tests.Utilities return this.GetDate(dateStr).AddDays(addDays).ToString(); } + [Test(Description = "Assert that AddDays throws an exception if the number of days is invalid.")] + [TestCase("01 spring Y1", -1)] + [TestCase("01 summer Y1", -29)] + [TestCase("01 spring Y2", -113)] + [SuppressMessage("ReSharper", "AssignmentIsFullyDiscarded", Justification = "Deliberate for unit test.")] + public void AddDays_RejectsInvalidValues(string dateStr, int addDays) + { + // act & assert + Assert.Throws<ArithmeticException>(() => _ = this.GetDate(dateStr).AddDays(addDays), "Passing the invalid number of days didn't throw the expected exception."); + } + + /**** ** GetHashCode ****/ @@ -194,6 +266,25 @@ namespace SMAPI.Tests.Utilities } } + + /**** + ** ToWorldDate + ****/ + [Test(Description = "Assert that the WorldDate operator returns the corresponding WorldDate.")] + [TestCase("01 spring Y1", ExpectedResult = 0)] + [TestCase("02 spring Y1", ExpectedResult = 1)] + [TestCase("28 spring Y1", ExpectedResult = 27)] + [TestCase("01 summer Y1", ExpectedResult = 28)] + [TestCase("01 summer Y2", ExpectedResult = 140)] + public int ToWorldDate(string dateStr) + { + return this.GetDate(dateStr).ToWorldDate().TotalDays; + } + + + /**** + ** Operators + ****/ [Test(Description = "Assert that the == operator returns the expected values. We only need a few test cases, since it's based on GetHashCode which is tested more thoroughly.")] [TestCase(Dates.Now, null, ExpectedResult = false)] [TestCase(Dates.Now, Dates.PrevDay, ExpectedResult = false)] diff --git a/src/SMAPI.Web/Program.cs b/src/SMAPI.Web/Program.cs index 5d13cdf3..70082160 100644 --- a/src/SMAPI.Web/Program.cs +++ b/src/SMAPI.Web/Program.cs @@ -18,6 +18,7 @@ namespace StardewModdingAPI.Web .CreateDefaultBuilder(args) .CaptureStartupErrors(true) .UseSetting("detailedErrors", "true") + .UseKestrel().UseIISIntegration() // must be used together; fixes intermittent errors on Azure: https://stackoverflow.com/a/38312175/262123 .UseStartup<Startup>() .Build() .Run(); diff --git a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json index 3101fdf1..179ef42a 100644 --- a/src/SMAPI.Web/wwwroot/SMAPI.metadata.json +++ b/src/SMAPI.Web/wwwroot/SMAPI.metadata.json @@ -155,7 +155,7 @@ *********/ "Auto Quality Patch": { "ID": "SilentOak.AutoQualityPatch", - "~2.1.3-unofficial.7 | Status": "AssumeBroken" // runtime errors + "~2.1.3-unofficial.7-mizzion | Status": "AssumeBroken" // runtime errors }, "Fix Dice": { diff --git a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json index e6cd4e65..f627ab95 100644 --- a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json +++ b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json @@ -11,9 +11,9 @@ "title": "Format version", "description": "The format version. You should always use the latest version to enable the latest features and avoid obsolete behavior.", "type": "string", - "const": "1.11.0", + "const": "1.13.0", "@errorMessages": { - "const": "Incorrect value '@value'. This should be set to the latest format version, currently '1.11.0'." + "const": "Incorrect value '@value'. This should be set to the latest format version, currently '1.13.0'." } }, "ConfigSchema": { @@ -268,6 +268,48 @@ "type": "string" } }, + "MapTiles": { + "title": "Map tiles", + "description": "The individual map tiles to add, edit, or remove.", + "type": "array", + "items": { + "type": "object", + "properties": { + "Layer": { + "description": "The map layer name to change.", + "type": "string" + }, + "Position": { + "description": "The tile coordinates to change. You can use the Debug Mode mod to see tile coordinates in-game.", + "$ref": "#/definitions/Position" + }, + "SetTilesheet": { + "title": "Set tilesheet", + "description": "Sets the tilesheet ID for the tile index.", + "type": "string" + }, + "SetIndex": { + "title": "Set tile index", + "description": "Sets the tile index in the tilesheet.", + "type": [ "string", "number" ] + }, + "SetProperties": { + "title": "Set tile properties", + "description": "The properties to set or remove. This is merged into the existing tile properties, if any. To remove a property, set its value to `null` (not \"null\" in quotes).", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Remove": { + "description": "Whether to remove the current tile and all its properties on that layer. If combined with the other fields, a new tile is created from the other fields as if the tile didn't previously exist.", + "type": "boolean" + } + |
