From 24b824644d708a514614b4e30b41567ea87902cc Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 20 Jul 2017 00:01:40 -0400 Subject: make semantic versions equatable in 2.0 --- src/StardewModdingAPI/SemanticVersion.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'src/StardewModdingAPI/SemanticVersion.cs') diff --git a/src/StardewModdingAPI/SemanticVersion.cs b/src/StardewModdingAPI/SemanticVersion.cs index 4b27c819..f30c43cd 100644 --- a/src/StardewModdingAPI/SemanticVersion.cs +++ b/src/StardewModdingAPI/SemanticVersion.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text.RegularExpressions; namespace StardewModdingAPI @@ -178,6 +178,16 @@ namespace StardewModdingAPI return this.IsBetween(new SemanticVersion(min), new SemanticVersion(max)); } +#if !SMAPI_1_x + /// Indicates whether the current object is equal to another object of the same type. + /// true if the current object is equal to the parameter; otherwise, false. + /// An object to compare with this object. + public bool Equals(ISemanticVersion other) + { + return other != null && this.CompareTo(other) == 0; + } +#endif + /// Get a string representation of the version. public override string ToString() { -- cgit From 74be6f13114e8e4cb8421a684009d160c4e861f1 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 23 Jul 2017 13:15:28 -0400 Subject: improve handling of legacy non-semantic game versions (#333) --- .../Utilities/SemanticVersionTests.cs | 30 ++++++++++ src/StardewModdingAPI/Constants.cs | 28 ++------- src/StardewModdingAPI/Framework/GameVersion.cs | 68 ++++++++++++++++++++++ src/StardewModdingAPI/Program.cs | 16 ++--- src/StardewModdingAPI/SemanticVersion.cs | 3 +- src/StardewModdingAPI/StardewModdingAPI.csproj | 1 + 6 files changed, 113 insertions(+), 33 deletions(-) create mode 100644 src/StardewModdingAPI/Framework/GameVersion.cs (limited to 'src/StardewModdingAPI/SemanticVersion.cs') diff --git a/src/StardewModdingAPI.Tests/Utilities/SemanticVersionTests.cs b/src/StardewModdingAPI.Tests/Utilities/SemanticVersionTests.cs index 95d0d74f..db46aee4 100644 --- a/src/StardewModdingAPI.Tests/Utilities/SemanticVersionTests.cs +++ b/src/StardewModdingAPI.Tests/Utilities/SemanticVersionTests.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; using NUnit.Framework; +using StardewModdingAPI.Framework; namespace StardewModdingAPI.Tests.Utilities { @@ -206,6 +207,35 @@ namespace StardewModdingAPI.Tests.Utilities return version.IsBetween(lower, upper); } + /**** + ** GameVersion + ****/ + [Test(Description = "Assert that the GameVersion subclass correctly parses legacy game versions.")] + [TestCase("1.0")] + [TestCase("1.01")] + [TestCase("1.02")] + [TestCase("1.03")] + [TestCase("1.04")] + [TestCase("1.05")] + [TestCase("1.051")] + [TestCase("1.051b")] + [TestCase("1.06")] + [TestCase("1.07")] + [TestCase("1.07a")] + [TestCase("1.1")] + [TestCase("1.11")] + [TestCase("1.2")] + [TestCase("1.2.15")] + public void GameVersion(string versionStr) + { + // act + GameVersion version = new GameVersion(versionStr); + + // assert + Assert.AreEqual(versionStr, version.ToString(), "The game version did not round-trip to the same value."); + Assert.IsTrue(version.IsOlderThan(new SemanticVersion("1.2.30")), "The game version should be considered older than the later semantic versions."); + } + /********* ** Private methods diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index 8217a6a5..ee57be0f 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -9,6 +9,7 @@ using StardewModdingAPI.AssemblyRewriters.Finders; using StardewModdingAPI.AssemblyRewriters.Rewriters; using StardewModdingAPI.AssemblyRewriters.Rewriters.Wrappers; using StardewModdingAPI.Events; +using StardewModdingAPI.Framework; using StardewValley; namespace StardewModdingAPI @@ -86,7 +87,7 @@ namespace StardewModdingAPI internal static string ModPath { get; } = Path.Combine(Constants.ExecutionPath, "Mods"); /// The game's current semantic version. - internal static ISemanticVersion GameVersion { get; } = Constants.GetGameVersion(); + internal static ISemanticVersion GameVersion { get; } = new GameVersion(Constants.GetGameVersion()); /// The target game platform. internal static Platform TargetPlatform { get; } = @@ -219,19 +220,6 @@ namespace StardewModdingAPI }; } - /// Get game current version as it should be displayed to players. - /// The semantic game version. - internal static ISemanticVersion GetGameDisplayVersion(ISemanticVersion version) - { - switch (version.ToString()) - { - case "1.1.1": - return new SemanticVersion(1, 11, 0); // The 1.1 patch was released as 1.11 - default: - return version; - } - } - /// Get the name of a save directory for the current player. private static string GetSaveFolderName() { @@ -239,20 +227,14 @@ namespace StardewModdingAPI return $"{prefix}_{Game1.uniqueIDForThisGame}"; } - /// Get the game's current semantic version. - private static ISemanticVersion GetGameVersion() + /// Get the game's current version string. + private static string GetGameVersion() { - // get raw version // we need reflection because it's a constant, so SMAPI's references to it are inlined at compile-time FieldInfo field = typeof(Game1).GetField(nameof(Game1.version), BindingFlags.Public | BindingFlags.Static); if (field == null) throw new InvalidOperationException($"The {nameof(Game1)}.{nameof(Game1.version)} field could not be found."); - string version = (string)field.GetValue(null); - - // get semantic version - if (version == "1.11") - version = "1.1.1"; // The 1.1 patch was released as 1.11, which means it's out of order for semantic version checks - return new SemanticVersion(version); + return (string)field.GetValue(null); } } } diff --git a/src/StardewModdingAPI/Framework/GameVersion.cs b/src/StardewModdingAPI/Framework/GameVersion.cs new file mode 100644 index 00000000..48159f61 --- /dev/null +++ b/src/StardewModdingAPI/Framework/GameVersion.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; + +namespace StardewModdingAPI.Framework +{ + /// An implementation of that correctly handles the non-semantic versions used by older Stardew Valley releases. + internal class GameVersion : SemanticVersion + { + /********* + ** Private methods + *********/ + /// A mapping of game to semantic versions. + private static readonly IDictionary VersionMap = new Dictionary(StringComparer.InvariantCultureIgnoreCase) + { + ["1.01"] = "1.0.1", + ["1.02"] = "1.0.2", + ["1.03"] = "1.0.3", + ["1.04"] = "1.0.4", + ["1.05"] = "1.0.5", + ["1.051"] = "1.0.6-prerelease1", // not a very good mapping, but good enough for SMAPI's purposes. + ["1.051b"] = "1.0.6-prelease2", + ["1.06"] = "1.0.6", + ["1.07"] = "1.0.7", + ["1.07a"] = "1.0.8-prerelease1", + ["1.11"] = "1.1.1" + }; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The game version string. + public GameVersion(string version) + : base(GameVersion.GetSemanticVersionString(version)) { } + + /// Get a string representation of the version. + public override string ToString() + { + return GameVersion.GetGameVersionString(base.ToString()); + } + + + /********* + ** Private methods + *********/ + /// Convert a game version string to a semantic version string. + /// The game version string. + private static string GetSemanticVersionString(string gameVersion) + { + return GameVersion.VersionMap.TryGetValue(gameVersion, out string semanticVersion) + ? semanticVersion + : gameVersion; + } + + /// Convert a game version string to a semantic version string. + /// The game version string. + private static string GetGameVersionString(string gameVersion) + { + foreach (var mapping in GameVersion.VersionMap) + { + if (mapping.Value.Equals(gameVersion, StringComparison.InvariantCultureIgnoreCase)) + return mapping.Key; + } + return gameVersion; + } + } +} diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 75bb991d..ff73962e 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -125,7 +125,7 @@ namespace StardewModdingAPI try { // init logging - this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)} on {this.GetFriendlyPlatformName()}", LogLevel.Info); + this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GameVersion} on {this.GetFriendlyPlatformName()}", LogLevel.Info); this.Monitor.Log($"Mods go here: {Constants.ModPath}"); #if SMAPI_1_x this.Monitor.Log("Preparing SMAPI..."); @@ -138,13 +138,13 @@ namespace StardewModdingAPI // validate game version if (Constants.GameVersion.IsOlderThan(Constants.MinimumGameVersion)) { - this.Monitor.Log($"Oops! You're running Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)}, but the oldest supported version is {Constants.GetGameDisplayVersion(Constants.MinimumGameVersion)}. Please update your game before using SMAPI. If you have the beta version on Steam, you may need to opt out to get the latest non-beta updates.", LogLevel.Error); + this.Monitor.Log($"Oops! You're running Stardew Valley {Constants.GameVersion}, but the oldest supported version is {Constants.MinimumGameVersion}. Please update your game before using SMAPI. If you have the beta version on Steam, you may need to opt out to get the latest non-beta updates.", LogLevel.Error); this.PressAnyKeyToExit(); return; } if (Constants.MaximumGameVersion != null && Constants.GameVersion.IsNewerThan(Constants.MaximumGameVersion)) { - this.Monitor.Log($"Oops! You're running Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)}, but this version of SMAPI is only compatible up to Stardew Valley {Constants.GetGameDisplayVersion(Constants.MaximumGameVersion)}. Please check for a newer version of SMAPI.", LogLevel.Error); + this.Monitor.Log($"Oops! You're running Stardew Valley {Constants.GameVersion}, but this version of SMAPI is only compatible up to Stardew Valley {Constants.MaximumGameVersion}. Please check for a newer version of SMAPI.", LogLevel.Error); this.PressAnyKeyToExit(); return; } @@ -191,8 +191,8 @@ namespace StardewModdingAPI ContentEvents.AfterLocaleChanged += (sender, e) => this.OnLocaleChanged(); // set window titles - this.GameInstance.Window.Title = $"Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)} - running SMAPI {Constants.ApiVersion}"; - Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)}"; + this.GameInstance.Window.Title = $"Stardew Valley {Constants.GameVersion} - running SMAPI {Constants.ApiVersion}"; + Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GameVersion}"; } catch (Exception ex) { @@ -315,7 +315,7 @@ namespace StardewModdingAPI if (Type.GetType($"StardewValley.LocalizedContentManager+LanguageCode, {gameAssemblyName}", throwOnError: false) == null) { PrintErrorAndExit(Constants.GameVersion.IsOlderThan(Constants.MinimumGameVersion) - ? $"Oops! You're running Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)}, but the oldest supported version is {Constants.GetGameDisplayVersion(Constants.MinimumGameVersion)}. Please update your game before using SMAPI." + ? $"Oops! You're running Stardew Valley {Constants.GameVersion}, but the oldest supported version is {Constants.MinimumGameVersion}. Please update your game before using SMAPI." : "Oops! SMAPI doesn't seem to be compatible with your game. Make sure you're running the latest version of Stardew Valley and SMAPI." ); } @@ -445,8 +445,8 @@ namespace StardewModdingAPI // update window titles int modsLoaded = this.ModRegistry.GetMods().Count(); - this.GameInstance.Window.Title = $"Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)} - running SMAPI {Constants.ApiVersion} with {modsLoaded} mods"; - Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)} with {modsLoaded} mods"; + this.GameInstance.Window.Title = $"Stardew Valley {Constants.GameVersion} - running SMAPI {Constants.ApiVersion} with {modsLoaded} mods"; + Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GameVersion} with {modsLoaded} mods"; // start SMAPI console new Thread(this.RunConsoleLoop).Start(); diff --git a/src/StardewModdingAPI/SemanticVersion.cs b/src/StardewModdingAPI/SemanticVersion.cs index f30c43cd..782a962b 100644 --- a/src/StardewModdingAPI/SemanticVersion.cs +++ b/src/StardewModdingAPI/SemanticVersion.cs @@ -117,8 +117,7 @@ namespace StardewModdingAPI { // compare numerically if possible { - int curNum, otherNum; - if (int.TryParse(curParts[i], out curNum) && int.TryParse(otherParts[i], out otherNum)) + if (int.TryParse(curParts[i], out int curNum) && int.TryParse(otherParts[i], out int otherNum)) return curNum.CompareTo(otherNum); } diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index f7992122..4cef91d9 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -126,6 +126,7 @@ + -- cgit From a15a2c5d092ca4608e8b85520443aae39063a11f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 2 Aug 2017 02:24:02 -0400 Subject: tweak semantic version regex to fix unnecessary capturing groups --- src/StardewModdingAPI/SemanticVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/StardewModdingAPI/SemanticVersion.cs') diff --git a/src/StardewModdingAPI/SemanticVersion.cs b/src/StardewModdingAPI/SemanticVersion.cs index 782a962b..58f24503 100644 --- a/src/StardewModdingAPI/SemanticVersion.cs +++ b/src/StardewModdingAPI/SemanticVersion.cs @@ -17,7 +17,7 @@ namespace StardewModdingAPI /// - allows hyphens in prerelease tags as synonyms for dots (like "-unofficial-update.3"); /// - doesn't allow '+build' suffixes. /// - private static readonly Regex Regex = new Regex(@"^(?0|[1-9]\d*)\.(?0|[1-9]\d*)(\.(?0|[1-9]\d*))?(?:-(?([a-z0-9]+[\-\.]?)+))?$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture); + private static readonly Regex Regex = new Regex(@"^(?0|[1-9]\d*)\.(?0|[1-9]\d*)(?:\.(?0|[1-9]\d*))?(?:-(?(?:[a-z0-9]+[\-\.]?)+))?$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture); /********* -- cgit From 7976df856578045b87e17a3d0ea5074715445622 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 2 Aug 2017 02:36:55 -0400 Subject: optimise semver regex using atomic groups --- src/StardewModdingAPI/SemanticVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/StardewModdingAPI/SemanticVersion.cs') diff --git a/src/StardewModdingAPI/SemanticVersion.cs b/src/StardewModdingAPI/SemanticVersion.cs index 58f24503..26de3aa8 100644 --- a/src/StardewModdingAPI/SemanticVersion.cs +++ b/src/StardewModdingAPI/SemanticVersion.cs @@ -17,7 +17,7 @@ namespace StardewModdingAPI /// - allows hyphens in prerelease tags as synonyms for dots (like "-unofficial-update.3"); /// - doesn't allow '+build' suffixes. /// - private static readonly Regex Regex = new Regex(@"^(?0|[1-9]\d*)\.(?0|[1-9]\d*)(?:\.(?0|[1-9]\d*))?(?:-(?(?:[a-z0-9]+[\-\.]?)+))?$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture); + private static readonly Regex Regex = new Regex(@"^(?>(?0|[1-9]\d*))\.(?>(?0|[1-9]\d*))(?>(?:\.(?0|[1-9]\d*))?)(?:-(?(?>[a-z0-9]+[\-\.]?)+))?$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture); /********* -- cgit From 67b1a8398f665a745f57f6d53532e504979ea40f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 14 Aug 2017 07:55:21 -0400 Subject: fix SemanticVersion not being deserialisable through Json.NET --- release-notes.md | 7 ++++--- .../Utilities/SemanticVersionTests.cs | 17 +++++++++++++++++ src/StardewModdingAPI/SemanticVersion.cs | 16 +++++++++------- 3 files changed, 30 insertions(+), 10 deletions(-) (limited to 'src/StardewModdingAPI/SemanticVersion.cs') diff --git a/release-notes.md b/release-notes.md index 9bfd1981..f8980af4 100644 --- a/release-notes.md +++ b/release-notes.md @@ -32,10 +32,11 @@ For players: * Updated compatibility list. For mod developers: -* Added `Context.CanPlayerMove` value for mod convenience. -* Added `helper.Content` properties for the current language. +* Added `Context.CanPlayerMove` property for mod convenience. +* Added content helper properties for the game's current language. +* Fixed `Context.IsPlayerFree` being false if the player is performing an action. * Fixed `GraphicsEvents.Resize` being raised before the game updates its window data. -* Fixed `Context.IsPlayerFree` being incorrectly false in some cases (e.g. when using a tool). +* Fixed `SemanticVersion` not being deserialisable through Json.NET. For SMAPI developers: * Internal changes to support the upcoming SMAPI 2.0 release. diff --git a/src/StardewModdingAPI.Tests/Utilities/SemanticVersionTests.cs b/src/StardewModdingAPI.Tests/Utilities/SemanticVersionTests.cs index db46aee4..03cd26c9 100644 --- a/src/StardewModdingAPI.Tests/Utilities/SemanticVersionTests.cs +++ b/src/StardewModdingAPI.Tests/Utilities/SemanticVersionTests.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; +using Newtonsoft.Json; using NUnit.Framework; using StardewModdingAPI.Framework; @@ -207,6 +208,22 @@ namespace StardewModdingAPI.Tests.Utilities return version.IsBetween(lower, upper); } + /**** + ** Serialisable + ****/ + [Test(Description = "Assert that SemanticVersion can be round-tripped through JSON with no special configuration.")] + [TestCase("1.0")] + public void Serialisable(string versionStr) + { + // act + string json = JsonConvert.SerializeObject(new SemanticVersion(versionStr)); + SemanticVersion after = JsonConvert.DeserializeObject(json); + + // assert + Assert.IsNotNull(after, "The semantic version after deserialisation is unexpectedly null."); + Assert.AreEqual(versionStr, after.ToString(), "The semantic version after deserialisation doesn't match the input version."); + } + /**** ** GameVersion ****/ diff --git a/src/StardewModdingAPI/SemanticVersion.cs b/src/StardewModdingAPI/SemanticVersion.cs index 26de3aa8..e448eae1 100644 --- a/src/StardewModdingAPI/SemanticVersion.cs +++ b/src/StardewModdingAPI/SemanticVersion.cs @@ -1,5 +1,6 @@ using System; using System.Text.RegularExpressions; +using Newtonsoft.Json; namespace StardewModdingAPI { @@ -40,15 +41,16 @@ namespace StardewModdingAPI ** Public methods *********/ /// Construct an instance. - /// The major version incremented for major API changes. - /// The minor version incremented for backwards-compatible changes. - /// The patch version for backwards-compatible bug fixes. + /// The major version incremented for major API changes. + /// The minor version incremented for backwards-compatible changes. + /// The patch version for backwards-compatible bug fixes. /// An optional build tag. - public SemanticVersion(int major, int minor, int patch, string build = null) + [JsonConstructor] + public SemanticVersion(int majorVersion, int minorVersion, int patchVersion, string build = null) { - this.MajorVersion = major; - this.MinorVersion = minor; - this.PatchVersion = patch; + this.MajorVersion = majorVersion; + this.MinorVersion = minorVersion; + this.PatchVersion = patchVersion; this.Build = this.GetNormalisedTag(build); } -- cgit