summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build/common.targets6
-rw-r--r--build/prepare-install-package.targets2
-rw-r--r--docs/README.md5
-rw-r--r--docs/release-notes.md17
-rw-r--r--docs/technical/smapi.md50
-rw-r--r--src/SMAPI.Installer/SMAPI.Installer.csproj2
-rw-r--r--src/SMAPI.Internal/ExceptionHelper.cs2
-rw-r--r--src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj2
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj2
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/manifest.json4
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs33
-rw-r--r--src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj2
-rw-r--r--src/SMAPI.Mods.ErrorHandler/i18n/pl.json4
-rw-r--r--src/SMAPI.Mods.ErrorHandler/i18n/th.json4
-rw-r--r--src/SMAPI.Mods.ErrorHandler/manifest.json4
-rw-r--r--src/SMAPI.Mods.SaveBackup/SMAPI.Mods.SaveBackup.csproj2
-rw-r--r--src/SMAPI.Mods.SaveBackup/manifest.json4
-rw-r--r--src/SMAPI.Tests/SMAPI.Tests.csproj2
-rw-r--r--src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs139
-rw-r--r--src/SMAPI.Toolkit.CoreInterfaces/SMAPI.Toolkit.CoreInterfaces.csproj2
-rw-r--r--src/SMAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs16
-rw-r--r--src/SMAPI.Toolkit/Framework/Clients/Wiki/ChangeDescriptor.cs193
-rw-r--r--src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs105
-rw-r--r--src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiDataOverrideEntry.cs29
-rw-r--r--src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs13
-rw-r--r--src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs10
-rw-r--r--src/SMAPI.Toolkit/SMAPI.Toolkit.csproj2
-rw-r--r--src/SMAPI.Web/Controllers/ModsApiController.cs30
-rw-r--r--src/SMAPI.Web/Framework/ModSiteManager.cs39
-rw-r--r--src/SMAPI.Web/Startup.cs12
-rw-r--r--src/SMAPI.Web/Views/Mods/Index.cshtml12
-rw-r--r--src/SMAPI.Web/wwwroot/Content/js/mods.js30
-rw-r--r--src/SMAPI/Constants.cs2
-rw-r--r--src/SMAPI/Framework/SCore.cs6
-rw-r--r--src/SMAPI/Framework/SGame.cs14
-rw-r--r--src/SMAPI/Framework/SGameRunner.cs10
-rw-r--r--src/SMAPI/Metadata/CoreAssetPropagator.cs60
-rw-r--r--src/SMAPI/SMAPI.csproj2
-rw-r--r--src/SMAPI/i18n/pl.json6
-rw-r--r--src/SMAPI/i18n/th.json6
40 files changed, 721 insertions, 164 deletions
diff --git a/build/common.targets b/build/common.targets
index 1f6cf0b6..02142351 100644
--- a/build/common.targets
+++ b/build/common.targets
@@ -1,7 +1,7 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!--set general build properties -->
- <Version>3.12.7</Version>
+ <Version>3.12.8</Version>
<Product>SMAPI</Product>
<LangVersion>latest</LangVersion>
<AssemblySearchPaths>$(AssemblySearchPaths);{GAC}</AssemblySearchPaths>
@@ -54,13 +54,13 @@
<Copy SourceFiles="@(TranslationFiles)" DestinationFolder="$(GamePath)\Mods\$(AssemblyName)\i18n" />
</Target>
- <Target Name="CopyToolkit" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit' AND $(TargetFramework) == 'net4.5'" AfterTargets="PostBuildEvent">
+ <Target Name="CopyToolkit" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit' AND $(TargetFramework) == 'net452'" AfterTargets="PostBuildEvent">
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)\smapi-internal" />
</Target>
- <Target Name="CopyToolkitCoreInterfaces" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit.CoreInterfaces' AND $(TargetFramework) == 'net4.5'" AfterTargets="PostBuildEvent">
+ <Target Name="CopyToolkitCoreInterfaces" Condition="'$(MSBuildProjectName)' == 'SMAPI.Toolkit.CoreInterfaces' AND $(TargetFramework) == 'net452'" AfterTargets="PostBuildEvent">
<Copy SourceFiles="$(TargetDir)\$(TargetName).dll" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).pdb" DestinationFolder="$(GamePath)\smapi-internal" />
<Copy SourceFiles="$(TargetDir)\$(TargetName).xml" DestinationFolder="$(GamePath)\smapi-internal" />
diff --git a/build/prepare-install-package.targets b/build/prepare-install-package.targets
index 88e565f9..601f6496 100644
--- a/build/prepare-install-package.targets
+++ b/build/prepare-install-package.targets
@@ -14,7 +14,7 @@
<OutRootPath>$(SolutionDir)\..\bin</OutRootPath>
<SmapiBin>$(BuildRootPath)\SMAPI\bin\$(Configuration)</SmapiBin>
- <ToolkitBin>$(BuildRootPath)\SMAPI.Toolkit\bin\$(Configuration)\net4.5</ToolkitBin>
+ <ToolkitBin>$(BuildRootPath)\SMAPI.Toolkit\bin\$(Configuration)\net452</ToolkitBin>
<ConsoleCommandsBin>$(BuildRootPath)\SMAPI.Mods.ConsoleCommands\bin\$(Configuration)</ConsoleCommandsBin>
<ErrorHandlerBin>$(BuildRootPath)\SMAPI.Mods.ErrorHandler\bin\$(Configuration)</ErrorHandlerBin>
<SaveBackupBin>$(BuildRootPath)\SMAPI.Mods.SaveBackup\bin\$(Configuration)</SaveBackupBin>
diff --git a/docs/README.md b/docs/README.md
index e7b8a9b3..ecfa6f2b 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -66,7 +66,12 @@ 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 | ✓ [fully translated](../src/SMAPI/i18n/ko.json)
+Polish¹ | ✓ [fully translated](../src/SMAPI/i18n/pl.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)
+Thai¹ | ✓ [fully translated](../src/SMAPI/i18n/th.json)
Turkish | ✓ [fully translated](../src/SMAPI/i18n/tr.json)
+
+¹ This is a custom language provided by a mod (see [Polish](https://www.nexusmods.com/stardewvalley/mods/3616)
+and [Thai](https://www.nexusmods.com/stardewvalley/mods/7052)).
diff --git a/docs/release-notes.md b/docs/release-notes.md
index 8ed155bd..25b0d740 100644
--- a/docs/release-notes.md
+++ b/docs/release-notes.md
@@ -1,6 +1,23 @@
← [README](README.md)
# Release notes
+## 3.12.8
+Released 18 October 2021 for Stardew Valley 1.5.4.
+
+* For players:
+ * Fixed mod edits to the farmhouse shifting the player down one tile in some cases.
+ * Improved translations. Thanks to ellipszist (added Thai) and Zangorr (added Polish)!
+ _These are custom languages which require Stardew Valley 1.5.5 and the [Polish](https://www.nexusmods.com/stardewvalley/mods/3616) or [Thai](https://www.nexusmods.com/stardewvalley/mods/7052) mod._
+
+* For mod authors:
+ * SMAPI now intercepts dictionary duplicate-key errors and adds the key to the error message to simplify troubleshooting. (Due to Harmony limitations, this only works for the dictionary types used by the game.)
+ * Fixed map tile rotations/flips not working for farmhands in split-screen mode.
+ * Fixed barn/coop exit warps being reset when you edit their interior map.
+
+* For the web UI:
+ * Added support for unified [mod data overrides](https://stardewvalleywiki.com/Modding:Mod_compatibility#Mod_data_overrides) defined on the wiki.
+ * The mod compatibility list now shows separate beta stats when 'show advanced info' is enabled.
+
## 3.12.7
Released 18 September 2021 for Stardew Valley 1.5.4.
diff --git a/docs/technical/smapi.md b/docs/technical/smapi.md
index 4be062e2..f454e99c 100644
--- a/docs/technical/smapi.md
+++ b/docs/technical/smapi.md
@@ -76,10 +76,48 @@ the debugger attached, so you can intercept errors and step through the code bei
doesn't work in MonoDevelop on Linux, unfortunately.
### Preparing a release
-To prepare a crossplatform SMAPI release, you'll need to compile it on two platforms. See
-[crossplatforming info](https://stardewvalleywiki.com/Modding:Modder_Guide/Test_and_Troubleshoot#Testing_on_all_platforms)
-on the wiki for the first-time setup.
-
+To prepare a crossplatform SMAPI release, you'll need to compile it on two platforms: Windows and
+Linux. The instructions below assume you have Windows 11, but you can adapt them for
+a different setup if needed.
+
+#### Initial setup
+First-time setup on Windows:
+1. [Install Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/install).
+2. Install the needed software in WSL:
+ 1. Run `sudo apt update` to update the package list.
+ 2. Install [the .NET 5 SDK](https://docs.microsoft.com/en-us/dotnet/core/install/linux-ubuntu)
+ (for Stardew Valley 1.5.5+) or [`mono-complete`](https://www.mono-project.com/download/stable/)
+ (for earlier versions).
+ _You can run `lsb_release -a` to get the Ubuntu version number._
+ 3. [Install Steam](https://linuxconfig.org/how-to-install-steam-on-ubuntu-20-04-focal-fossa-linux).
+ 4. Launch `steam` and install the game like usual.
+ 5. Download and install your preferred IDE. For the [latest standalone Rider
+ version](https://www.jetbrains.com/help/rider/Installation_guide.html#prerequisites):
+ ```sh
+ wget "<download url here>" -O rider-install.tar.gz
+ sudo tar -xzvf rider-install.tar.gz -C /opt
+ ln -s "/opt/JetBrains Rider-<version>/bin/rider.sh"
+ ./rider.sh
+ ```
+ 3. Clone the SMAPI repo in WSL:
+ ```sh
+ git clone https://github.com/Pathoschild/SMAPI.git
+ ```
+
+To compile SMAPI in WSL:
+1. Run `./rider.sh` to open the Rider GUI.
+2. Use the GUI to compile the solution.
+
+To launch the game:
+1. Open a WSL terminal.
+2. Run these commands to start Steam:
+ ```sh
+ export TERM=xterm
+ steam
+ ```
+3. Launch the game through the Steam UI.
+
+#### Prepare the release
1. Update the version numbers in `build/common.targets`, `Constants`, and the `manifest.json` for
bundled mods. Make sure you use a [semantic version](https://semver.org). Recommended format:
@@ -90,8 +128,8 @@ on the wiki for the first-time setup.
release | `<version>` | `3.0.0`
2. In Windows:
1. Rebuild the solution with the _release_ solution configuration.
- 2. Copy the `bin/SMAPI installer` and `bin/SMAPI installer for developers` folders to Linux/macOS.
-4. In Linux/macOS:
+ 2. Copy the `bin/SMAPI installer` and `bin/SMAPI installer for developers` folders to Linux.
+4. In Linux:
1. Rebuild the solution with the _release_ solution configuration.
2. Add the `windows-install.*` files from Windows to the `bin/SMAPI installer` and
`bin/SMAPI installer for developers` folders compiled on Linux.
diff --git a/src/SMAPI.Installer/SMAPI.Installer.csproj b/src/SMAPI.Installer/SMAPI.Installer.csproj
index 1777be5f..c47f3e6e 100644
--- a/src/SMAPI.Installer/SMAPI.Installer.csproj
+++ b/src/SMAPI.Installer/SMAPI.Installer.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<RootNamespace>StardewModdingAPI.Installer</RootNamespace>
<Description>The SMAPI installer for players.</Description>
- <TargetFramework>net45</TargetFramework>
+ <TargetFramework>net452</TargetFramework>
<OutputType>Exe</OutputType>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
diff --git a/src/SMAPI.Internal/ExceptionHelper.cs b/src/SMAPI.Internal/ExceptionHelper.cs
index 4bc55f95..05b96c2e 100644
--- a/src/SMAPI.Internal/ExceptionHelper.cs
+++ b/src/SMAPI.Internal/ExceptionHelper.cs
@@ -65,7 +65,7 @@ namespace StardewModdingAPI.Internal
// remove unneeded root build paths for SMAPI and Stardew Valley
message = message
- .Replace(@"C:\source\_Stardew\SMAPI\src\", "")
+ .Replace(@"E:\source\_Stardew\SMAPI\src\", "")
.Replace(@"C:\GitlabRunner\builds\Gq5qA5P4\0\ConcernedApe\", "");
// remove placeholder info in Linux/macOS stack traces
diff --git a/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj b/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj
index b18e79d5..93769650 100644
--- a/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj
+++ b/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<!--build-->
<RootNamespace>StardewModdingAPI.ModBuildConfig</RootNamespace>
- <TargetFramework>net45</TargetFramework>
+ <TargetFramework>net452</TargetFramework>
<LangVersion>latest</LangVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
diff --git a/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj b/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj
index a187c1ff..528348a0 100644
--- a/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj
+++ b/src/SMAPI.Mods.ConsoleCommands/SMAPI.Mods.ConsoleCommands.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<AssemblyName>ConsoleCommands</AssemblyName>
<RootNamespace>StardewModdingAPI.Mods.ConsoleCommands</RootNamespace>
- <TargetFramework>net45</TargetFramework>
+ <TargetFramework>net452</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json
index e53bf991..9af3ce5d 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.12.7",
+ "Version": "3.12.8",
"Description": "Adds SMAPI console commands that let you manipulate the game.",
"UniqueID": "SMAPI.ConsoleCommands",
"EntryDll": "ConsoleCommands.dll",
- "MinimumApiVersion": "3.12.7"
+ "MinimumApiVersion": "3.12.8"
}
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs
index 6ad64e16..8ceafcc5 100644
--- a/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/DictionaryPatcher.cs
@@ -48,6 +48,11 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
original: AccessTools.Method(dictionaryType, "get_Item") ?? throw new InvalidOperationException($"Can't find method {PatchHelper.GetMethodString(dictionaryType, "get_Item")} to patch."),
finalizer: this.GetHarmonyMethod(nameof(DictionaryPatcher.Finalize_GetItem))
);
+
+ harmony.Patch(
+ original: AccessTools.Method(dictionaryType, "Add") ?? throw new InvalidOperationException($"Can't find method {PatchHelper.GetMethodString(dictionaryType, "Add")} to patch."),
+ finalizer: this.GetHarmonyMethod(nameof(DictionaryPatcher.Finalize_Add))
+ );
}
}
}
@@ -63,13 +68,31 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
private static Exception Finalize_GetItem(object key, Exception __exception)
{
if (__exception is KeyNotFoundException)
- {
- DictionaryPatcher.Reflection
- .GetField<string>(__exception, "_message")
- .SetValue($"{__exception.Message}\nkey: '{key}'");
- }
+ DictionaryPatcher.AddKey(__exception, key);
return __exception;
}
+
+ /// <summary>The method to call after a dictionary insert throws an exception.</summary>
+ /// <param name="key">The dictionary key being inserted.</param>
+ /// <param name="__exception">The exception thrown by the wrapped method, if any.</param>
+ /// <returns>Returns the exception to throw, if any.</returns>
+ private static Exception Finalize_Add(object key, Exception __exception)
+ {
+ if (__exception is ArgumentException)
+ DictionaryPatcher.AddKey(__exception, key);
+
+ return __exception;
+ }
+
+ /// <summary>Add the dictionary key to an exception message.</summary>
+ /// <param name="exception">The exception whose message to edit.</param>
+ /// <param name="key">The dictionary key.</param>
+ private static void AddKey(Exception exception, object key)
+ {
+ DictionaryPatcher.Reflection
+ .GetField<string>(exception, "_message")
+ .SetValue($"{exception.Message}\nkey: '{key}'");
+ }
}
}
diff --git a/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj b/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj
index eb878bc5..182a978e 100644
--- a/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj
+++ b/src/SMAPI.Mods.ErrorHandler/SMAPI.Mods.ErrorHandler.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<AssemblyName>ErrorHandler</AssemblyName>
<RootNamespace>StardewModdingAPI.Mods.ErrorHandler</RootNamespace>
- <TargetFramework>net45</TargetFramework>
+ <TargetFramework>net452</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
diff --git a/src/SMAPI.Mods.ErrorHandler/i18n/pl.json b/src/SMAPI.Mods.ErrorHandler/i18n/pl.json
new file mode 100644
index 00000000..05a9d411
--- /dev/null
+++ b/src/SMAPI.Mods.ErrorHandler/i18n/pl.json
@@ -0,0 +1,4 @@
+{
+ // warning messages
+ "warn.invalid-content-removed": "Nieprawidłowa zawartość została usunięta, aby zapobiec awarii (zobacz konsolę SMAPI po więcej informacji)."
+}
diff --git a/src/SMAPI.Mods.ErrorHandler/i18n/th.json b/src/SMAPI.Mods.ErrorHandler/i18n/th.json
new file mode 100644
index 00000000..e2a67dda
--- /dev/null
+++ b/src/SMAPI.Mods.ErrorHandler/i18n/th.json
@@ -0,0 +1,4 @@
+{
+ // warning messages
+ "warn.invalid-content-removed": "ทำการลบเนื้อหาที่ไม่ถูกต้องออก เพื่อป้องกันไฟล์เกมเสียหาย (ดูรายละเอียดที่หน้าคอลโซลของ SMAPI)"
+}
diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json
index 37a7e9bf..cfbaa661 100644
--- a/src/SMAPI.Mods.ErrorHandler/manifest.json
+++ b/src/SMAPI.Mods.ErrorHandler/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Error Handler",
"Author": "SMAPI",
- "Version": "3.12.7",
+ "Version": "3.12.8",
"Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.",
"UniqueID": "SMAPI.ErrorHandler",
"EntryDll": "ErrorHandler.dll",
- "MinimumApiVersion": "3.12.7"
+ "MinimumApiVersion": "3.12.8"
}
diff --git a/src/SMAPI.Mods.SaveBackup/SMAPI.Mods.SaveBackup.csproj b/src/SMAPI.Mods.SaveBackup/SMAPI.Mods.SaveBackup.csproj
index a6f76781..079beb08 100644
--- a/src/SMAPI.Mods.SaveBackup/SMAPI.Mods.SaveBackup.csproj
+++ b/src/SMAPI.Mods.SaveBackup/SMAPI.Mods.SaveBackup.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<AssemblyName>SaveBackup</AssemblyName>
<RootNamespace>StardewModdingAPI.Mods.SaveBackup</RootNamespace>
- <TargetFramework>net45</TargetFramework>
+ <TargetFramework>net452</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json
index 28c7ec14..1efd1d89 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.12.7",
+ "Version": "3.12.8",
"Description": "Automatically backs up all your saves once per day into its folder.",
"UniqueID": "SMAPI.SaveBackup",
"EntryDll": "SaveBackup.dll",
- "MinimumApiVersion": "3.12.7"
+ "MinimumApiVersion": "3.12.8"
}
diff --git a/src/SMAPI.Tests/SMAPI.Tests.csproj b/src/SMAPI.Tests/SMAPI.Tests.csproj
index 27520baf..8f7bfab4 100644
--- a/src/SMAPI.Tests/SMAPI.Tests.csproj
+++ b/src/SMAPI.Tests/SMAPI.Tests.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<AssemblyName>SMAPI.Tests</AssemblyName>
<RootNamespace>SMAPI.Tests</RootNamespace>
- <TargetFramework>net45</TargetFramework>
+ <TargetFramework>net452</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<LangVersion>latest</LangVersion>
</PropertyGroup>
diff --git a/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs b/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs
new file mode 100644
index 00000000..b896b09c
--- /dev/null
+++ b/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs
@@ -0,0 +1,139 @@
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using StardewModdingAPI;
+using StardewModdingAPI.Toolkit.Framework.Clients.Wiki;
+
+namespace SMAPI.Tests.WikiClient
+{
+ /// <summary>Unit tests for <see cref="ChangeDescriptor"/>.</summary>
+ [TestFixture]
+ internal class ChangeDescriptorTests
+ {
+ /*********
+ ** Unit tests
+ *********/
+ /****
+ ** Constructor
+ ****/
+ [Test(Description = "Assert that Parse sets the expected values for valid and invalid descriptors.")]
+ public void Parse_SetsExpectedValues_Raw()
+ {
+ // arrange
+ string rawDescriptor = "-Nexus:2400, -B, XX → YY, Nexus:451,+A, XXX → YYY, invalidA →, → invalidB";
+ string[] expectedAdd = new[] { "Nexus:451", "A" };
+ string[] expectedRemove = new[] { "Nexus:2400", "B" };
+ IDictionary<string, string> expectedReplace = new Dictionary<string, string>
+ {
+ ["XX"] = "YY",
+ ["XXX"] = "YYY"
+ };
+ string[] expectedErrors = new[]
+ {
+ "Failed parsing ' invalidA →': can't map to a blank value. Use the '-value' format to remove a value.",
+ "Failed parsing ' → invalidB': can't map from a blank old value. Use the '+value' format to add a value."
+ };
+
+ // act
+ ChangeDescriptor parsed = ChangeDescriptor.Parse(rawDescriptor, out string[] errors);
+
+ // assert
+ Assert.That(parsed.Add, Is.EquivalentTo(expectedAdd), $"{nameof(parsed.Add)} doesn't match the expected value.");
+ Assert.That(parsed.Remove, Is.EquivalentTo(expectedRemove), $"{nameof(parsed.Replace)} doesn't match the expected value.");
+ Assert.That(parsed.Replace, Is.EquivalentTo(expectedReplace), $"{nameof(parsed.Replace)} doesn't match the expected value.");
+ Assert.That(errors, Is.EquivalentTo(expectedErrors), $"{nameof(errors)} doesn't match the expected value.");
+ }
+
+ [Test(Description = "Assert that Parse sets the expected values for descriptors when a format callback is specified.")]
+ public void Parse_SetsExpectedValues_Formatted()
+ {
+ // arrange
+ string rawDescriptor = "-1.0.1, -2.0-beta, 1.00 → 1.0, 1.0.0,+2.0-beta.15, 2.0 → 2.0-beta, invalidA →, → invalidB";
+ string[] expectedAdd = new[] { "1.0.0", "2.0.0-beta.15" };
+ string[] expectedRemove = new[] { "1.0.1", "2.0.0-beta" };
+ IDictionary<string, string> expectedReplace = new Dictionary<string, string>
+ {
+ ["1.00"] = "1.0.0",
+ ["2.0.0"] = "2.0.0-beta"
+ };
+ string[] expectedErrors = new[]
+ {
+ "Failed parsing ' invalidA →': can't map to a blank value. Use the '-value' format to remove a value.",
+ "Failed parsing ' → invalidB': can't map from a blank old value. Use the '+value' format to add a value."
+ };
+
+ // act
+ ChangeDescriptor parsed = ChangeDescriptor.Parse(
+ rawDescriptor,
+ out string[] errors,
+ formatValue: raw => SemanticVersion.TryParse(raw, out ISemanticVersion version)
+ ? version.ToString()
+ : raw
+ );
+
+ // assert
+ Assert.That(parsed.Add, Is.EquivalentTo(expectedAdd), $"{nameof(parsed.Add)} doesn't match the expected value.");
+ Assert.That(parsed.Remove, Is.EquivalentTo(expectedRemove), $"{nameof(parsed.Replace)} doesn't match the expected value.");
+ Assert.That(parsed.Replace, Is.EquivalentTo(expectedReplace), $"{nameof(parsed.Replace)} doesn't match the expected value.");
+ Assert.That(errors, Is.EquivalentTo(expectedErrors), $"{nameof(errors)} doesn't match the expected value.");
+ }
+
+ [Test(Description = "Assert that Apply returns the expected value for the given descriptor.")]
+
+ // null input
+ [TestCase(null, "", ExpectedResult = null)]
+ [TestCase(null, "+Nexus:2400", ExpectedResult = "Nexus:2400")]
+ [TestCase(null, "-Nexus:2400", ExpectedResult = null)]
+
+ // blank input
+ [TestCase("", null, ExpectedResult = "")]
+ [TestCase("", "", ExpectedResult = "")]
+
+ // add value
+ [TestCase("", "+Nexus:2400", ExpectedResult = "Nexus:2400")]
+ [TestCase("Nexus:2400", "+Nexus:2400", ExpectedResult = "Nexus:2400")]
+ [TestCase("Nexus:2400", "Nexus:2400", ExpectedResult = "Nexus:2400")]
+ [TestCase("Nexus:2400", "+Nexus:2401", ExpectedResult = "Nexus:2400, Nexus:2401")]
+ [TestCase("Nexus:2400", "Nexus:2401", ExpectedResult = "Nexus:2400, Nexus:2401")]
+
+ // remove value
+ [TestCase("", "-Nexus:2400", ExpectedResult = "")]
+ [TestCase("Nexus:2400", "-Nexus:2400", ExpectedResult = "")]
+ [TestCase("Nexus:2400", "-Nexus:2401", ExpectedResult = "Nexus:2400")]
+
+ // replace value
+ [TestCase("", "Nexus:2400 → Nexus:2401", ExpectedResult = "")]
+ [TestCase("Nexus:2400", "Nexus:2400 → Nexus:2401", ExpectedResult = "Nexus:2401")]
+ [TestCase("Nexus:1", "Nexus: 2400 → Nexus: 2401", ExpectedResult = "Nexus:1")]
+
+ // complex strings
+ [TestCase("", "+Nexus:A, Nexus:B, -Chucklefish:14, Nexus:2400 → Nexus:2401, Nexus:A→Nexus:B", ExpectedResult = "Nexus:A, Nexus:B")]
+ [TestCase("Nexus:2400", "+Nexus:A, Nexus:B, -Chucklefish:14, Nexus:2400 → Nexus:2401, Nexus:A→Nexus:B", ExpectedResult = "Nexus:2401, Nexus:A, Nexus:B")]
+ [TestCase("Nexus:2400, Nexus:2401, Nexus:B,Chucklefish:14", "+Nexus:A, Nexus:B, -Chucklefish:14, Nexus:2400 → Nexus:2401, Nexus:A→Nexus:B", ExpectedResult = "Nexus:2401, Nexus:2401, Nexus:B, Nexus:A")]
+ public string Apply_Raw(string input, string descriptor)
+ {
+ var parsed = ChangeDescriptor.Parse(descriptor, out string[] errors);
+
+ Assert.IsEmpty(errors, "Parsing the descriptor failed.");
+
+ return parsed.ApplyToCopy(input);
+ }
+
+ [Test(Description = "Assert that ToString returns the expected normalized descriptors.")]
+ [TestCase(null, ExpectedResult = "")]
+ [TestCase("", ExpectedResult = "")]
+ [TestCase("+ Nexus:2400", ExpectedResult = "+Nexus:2400")]
+ [TestCase(" Nexus:2400 ", ExpectedResult = "+Nexus:2400")]
+ [TestCase("-Nexus:2400", ExpectedResult = "-Nexus:2400")]
+ [TestCase(" Nexus:2400 →Nexus:2401 ", ExpectedResult = "Nexus:2400 → Nexus:2401")]
+ [TestCase("+Nexus:A, Nexus:B, -Chucklefish:14, Nexus:2400 → Nexus:2401, Nexus:A→Nexus:B", ExpectedResult = "+Nexus:A, +Nexus:B, -Chucklefish:14, Nexus:2400 → Nexus:2401, Nexus:A → Nexus:B")]
+ public string ToString(string descriptor)
+ {
+ var parsed = ChangeDescriptor.Parse(descriptor, out string[] errors);
+
+ Assert.IsEmpty(errors, "Parsing the descriptor failed.");
+
+ return parsed.ToString();
+ }
+ }
+}
diff --git a/src/SMAPI.Toolkit.CoreInterfaces/SMAPI.Toolkit.CoreInterfaces.csproj b/src/SMAPI.Toolkit.CoreInterfaces/SMAPI.Toolkit.CoreInterfaces.csproj
index d36a1882..0e1e40b2 100644
--- a/src/SMAPI.Toolkit.CoreInterfaces/SMAPI.Toolkit.CoreInterfaces.csproj
+++ b/src/SMAPI.Toolkit.CoreInterfaces/SMAPI.Toolkit.CoreInterfaces.csproj
@@ -2,7 +2,7 @@
<PropertyGroup>
<RootNamespace>StardewModdingAPI</RootNamespace>
<Description>Provides toolkit interfaces which are available to SMAPI mods.</Description>
- <TargetFrameworks>net4.5;netstandard2.0</TargetFrameworks>
+ <TargetFrameworks>net452;netstandard2.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs
index 8c21e4e0..5c2ce366 100644
--- a/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs
+++ b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs
@@ -87,11 +87,14 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi
/****
** Version mappings
****/
- /// <summary>Maps local versions to a semantic version for update che