From cd93d59eaf5aa05e0fa55eadd958339b9042155d Mon Sep 17 00:00:00 2001 From: Tyler Staples Date: Mon, 12 Dec 2016 15:58:44 -0800 Subject: Added a struct to wrap cache entries for the sake of tracking invalid lookups. This fixes the issue where a null reference exception would be thrown when trying to look up non-existant or non-private members. Added a null check to GetPrivateValue and it's overloads to fix the issue where it would throw a null reference exception when required was false and the field was null. --- .../Framework/Reflection/ReflectionHelper.cs | 49 ++++++++++++++++++---- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs b/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs index 38b4e357..1d5cf157 100644 --- a/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs +++ b/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs @@ -12,6 +12,16 @@ namespace StardewModdingAPI.Framework.Reflection /********* ** Properties *********/ + /// MemberInfo wrapper for tracking validity. + internal struct CacheEntry + { + /// Is this member valid. Used to avoid unecessary lookups. + public bool IsValid; + + /// The reflection data for this member. This will be null if IsValid is false. + public MemberInfo MemberInfo; + } + /// The cached fields and methods found via reflection. private readonly MemoryCache Cache = new MemoryCache(typeof(ReflectionHelper).FullName); @@ -67,10 +77,17 @@ namespace StardewModdingAPI.Framework.Reflection /// The object which has the field. /// The field name. /// Whether to throw an exception if the private field is not found. - /// This is a shortcut for followed by . + /// The value of the field or the default value of the type if the field is not found. + /// + /// This is a shortcut for followed by . + /// When is false, this will return the default value if reflection fails. If you need to check whether the field exists, use instead. + /// public TValue GetPrivateValue(object obj, string name, bool required = true) { - return this.GetPrivateField(obj, name, required).GetValue(); + IPrivateField field = this.GetPrivateField(obj, name, required); + return (field != null) + ? field.GetValue() + : default(TValue); } /// Get the value of a private static field. @@ -78,10 +95,17 @@ namespace StardewModdingAPI.Framework.Reflection /// The type which has the field. /// The field name. /// Whether to throw an exception if the private field is not found. - /// This is a shortcut for followed by . + /// The value of the field or the default value of the type if the field is not found. + /// + /// This is a shortcut for followed by . + /// When is false, this will return the default value if reflection fails. If you need to check whether the field exists, use instead. + /// public TValue GetPrivateValue(Type type, string name, bool required = true) { - return this.GetPrivateField(type, name, required).GetValue(); + IPrivateField field = this.GetPrivateField(type, name, required); + return (field != null) + ? field.GetValue() + : default(TValue); } /**** @@ -228,11 +252,22 @@ namespace StardewModdingAPI.Framework.Reflection { // get from cache if (this.Cache.Contains(key)) - return (TMemberInfo)this.Cache[key]; + { + CacheEntry entry = (CacheEntry)this.Cache[key]; + return entry.IsValid + ? (TMemberInfo)entry.MemberInfo + : default(TMemberInfo); + } - // fetch & cache new value + // fetch & cache new value, marking if it's valid for future lookups. TMemberInfo result = fetch(); - this.Cache.Add(key, result, new CacheItemPolicy { SlidingExpiration = this.SlidingCacheExpiry }); + CacheEntry cacheEntry = new CacheEntry() + { + IsValid = (result != null), + MemberInfo = result + }; + + this.Cache.Add(key, cacheEntry, new CacheItemPolicy { SlidingExpiration = this.SlidingCacheExpiry }); return result; } } -- cgit From d9e87399bf65d0053ad57d316d0df9e1a631a42b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 18 Dec 2016 12:27:44 -0500 Subject: format code (#193) --- .../Framework/Reflection/CacheEntry.cs | 30 ++++++++++++++++++++ .../Framework/Reflection/ReflectionHelper.cs | 33 ++++++---------------- src/StardewModdingAPI/StardewModdingAPI.csproj | 1 + 3 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 src/StardewModdingAPI/Framework/Reflection/CacheEntry.cs diff --git a/src/StardewModdingAPI/Framework/Reflection/CacheEntry.cs b/src/StardewModdingAPI/Framework/Reflection/CacheEntry.cs new file mode 100644 index 00000000..30faca37 --- /dev/null +++ b/src/StardewModdingAPI/Framework/Reflection/CacheEntry.cs @@ -0,0 +1,30 @@ +using System.Reflection; + +namespace StardewModdingAPI.Framework.Reflection +{ + /// A cached member reflection result. + internal struct CacheEntry + { + /********* + ** Accessors + *********/ + /// Whether the lookup found a valid match. + public bool IsValid; + + /// The reflection data for this member (or null if invalid). + public MemberInfo MemberInfo; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// Whether the lookup found a valid match. + /// The reflection data for this member (or null if invalid). + public CacheEntry(bool isValid, MemberInfo memberInfo) + { + this.IsValid = isValid; + this.MemberInfo = memberInfo; + } + } +} diff --git a/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs b/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs index 1d5cf157..edf59b81 100644 --- a/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs +++ b/src/StardewModdingAPI/Framework/Reflection/ReflectionHelper.cs @@ -12,16 +12,6 @@ namespace StardewModdingAPI.Framework.Reflection /********* ** Properties *********/ - /// MemberInfo wrapper for tracking validity. - internal struct CacheEntry - { - /// Is this member valid. Used to avoid unecessary lookups. - public bool IsValid; - - /// The reflection data for this member. This will be null if IsValid is false. - public MemberInfo MemberInfo; - } - /// The cached fields and methods found via reflection. private readonly MemoryCache Cache = new MemoryCache(typeof(ReflectionHelper).FullName); @@ -77,7 +67,7 @@ namespace StardewModdingAPI.Framework.Reflection /// The object which has the field. /// The field name. /// Whether to throw an exception if the private field is not found. - /// The value of the field or the default value of the type if the field is not found. + /// Returns the field value, or the default value for if the field wasn't found and is false. /// /// This is a shortcut for followed by . /// When is false, this will return the default value if reflection fails. If you need to check whether the field exists, use instead. @@ -85,7 +75,7 @@ namespace StardewModdingAPI.Framework.Reflection public TValue GetPrivateValue(object obj, string name, bool required = true) { IPrivateField field = this.GetPrivateField(obj, name, required); - return (field != null) + return field != null ? field.GetValue() : default(TValue); } @@ -95,7 +85,7 @@ namespace StardewModdingAPI.Framework.Reflection /// The type which has the field. /// The field name. /// Whether to throw an exception if the private field is not found. - /// The value of the field or the default value of the type if the field is not found. + /// Returns the field value, or the default value for if the field wasn't found and is false. /// /// This is a shortcut for followed by . /// When is false, this will return the default value if reflection fails. If you need to check whether the field exists, use instead. @@ -103,7 +93,7 @@ namespace StardewModdingAPI.Framework.Reflection public TValue GetPrivateValue(Type type, string name, bool required = true) { IPrivateField field = this.GetPrivateField(type, name, required); - return (field != null) + return field != null ? field.GetValue() : default(TValue); } @@ -254,21 +244,16 @@ namespace StardewModdingAPI.Framework.Reflection if (this.Cache.Contains(key)) { CacheEntry entry = (CacheEntry)this.Cache[key]; - return entry.IsValid - ? (TMemberInfo)entry.MemberInfo + return entry.IsValid + ? (TMemberInfo)entry.MemberInfo : default(TMemberInfo); } - // fetch & cache new value, marking if it's valid for future lookups. + // fetch & cache new value TMemberInfo result = fetch(); - CacheEntry cacheEntry = new CacheEntry() - { - IsValid = (result != null), - MemberInfo = result - }; - + CacheEntry cacheEntry = new CacheEntry(result != null, result); this.Cache.Add(key, cacheEntry, new CacheItemPolicy { SlidingExpiration = this.SlidingCacheExpiry }); return result; } } -} \ No newline at end of file +} diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 1a31b751..65083e67 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -162,6 +162,7 @@ + -- cgit From db6b204a1c1c9a057fb0679bca37c0b2b074f1d4 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 18 Dec 2016 13:28:23 -0500 Subject: update release notes --- release-notes.md | 76 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/release-notes.md b/release-notes.md index eb2d619e..15c14ad9 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,7 +1,13 @@ # Release notes +## 1.5 +See [log](https://github.com/Pathoschild/SMAPI/compare/stable...develop). + +For players: + * Fixed error when a mod uses the new reflection API on a missing field or method. + ## 1.4 -See [log](https://github.com/CLxS/SMAPI/compare/1.3...1.4). +See [log](https://github.com/Pathoschild/SMAPI/compare/1.3...1.4). For players: * SMAPI will now prevent mods from crashing your game with menu errors. @@ -20,14 +26,14 @@ For developers: * Fixed an issue where you couldn't debug into an assembly because it was copied into the `.cache` directory. That will now only happen if necessary. ## 1.3 -See [log](https://github.com/CLxS/SMAPI/compare/1.2...1.3). +See [log](https://github.com/Pathoschild/SMAPI/compare/1.2...1.3). For players: * You can now run most mods on any platform (e.g. run Windows mods on Linux/Mac). * Fixed the normal uninstaller not removing files added by the 'SMAPI for developers' installer. ## 1.2 -See [log](https://github.com/CLxS/SMAPI/compare/1.1.1...1.2). +See [log](https://github.com/Pathoschild/SMAPI/compare/1.1.1...1.2). For players: * Fixed compatibility with some older mods. @@ -39,7 +45,7 @@ For developers: * Improved logging to show `ReflectionTypeLoadException` details when it's caught by SMAPI. ## 1.1 -See [log](https://github.com/CLxS/SMAPI/compare/1.0...1.1.1). +See [log](https://github.com/Pathoschild/SMAPI/compare/1.0...1.1.1). For players: * Fixed console exiting immediately when some exceptions occur. @@ -60,7 +66,7 @@ For developers: * Fixed deprecation warnings being repeated if the mod can't be identified.1.1.1 ## 1.0 -See [log](https://github.com/CLxS/SMAPI/compare/0.40.1.1-3...1.0). +See [log](https://github.com/Pathoschild/SMAPI/compare/0.40.1.1-3...1.0). For players: * Added support for Linux and Mac. @@ -89,44 +95,44 @@ For SMAPI developers: * Internal cleanup & refactoring. ## 0.x -* 0.40.1.1 (2016-09-30, [log](https://github.com/CLxS/SMAPI/compare/0.40.0...0.40.1.1-3)) +* 0.40.1.1 (2016-09-30, [log](https://github.com/Pathoschild/SMAPI/compare/0.40.0...0.40.1.1-3)) * Added support for Stardew Valley 1.1. -* 0.40.0 (2016-04-05, [log](https://github.com/CLxS/SMAPI/compare/0.39.7...0.40.0)) +* 0.40.0 (2016-04-05, [log](https://github.com/Pathoschild/SMAPI/compare/0.39.7...0.40.0)) * Fixed an error that ocurred during minigames. -* 0.39.7 (2016-04-04, [log](https://github.com/CLxS/SMAPI/compare/0.39.6...0.39.7)) +* 0.39.7 (2016-04-04, [log](https://github.com/Pathoschild/SMAPI/compare/0.39.6...0.39.7)) * Added 'no check' graphics events that are triggered regardless of game's if checks. -* 0.39.6 (2016-04-01, [log](https://github.com/CLxS/SMAPI/compare/0.39.5...0.39.6)) +* 0.39.6 (2016-04-01, [log](https://github.com/Pathoschild/SMAPI/compare/0.39.5...0.39.6)) * Added game & SMAPI versions to log. * Fixed conflict in graphics tick events. * Bug fixes. -* 0.39.5 (2016-03-30, [log](https://github.com/CLxS/SMAPI/compare/0.39.4...0.39.5)) -* 0.39.4 (2016-03-29, [log](https://github.com/CLxS/SMAPI/compare/0.39.3...0.39.4)) -* 0.39.3 (2016-03-28, [log](https://github.com/CLxS/SMAPI/compare/0.39.2...0.39.3)) -* 0.39.2 (2016-03-23, [log](https://github.com/CLxS/SMAPI/compare/0.39.1...0.39.2)) -* 0.39.1 (2016-03-23, [log](https://github.com/CLxS/SMAPI/compare/0.38.8...0.39.1)) -* 0.38.8 (2016-03-23, [log](https://github.com/CLxS/SMAPI/compare/0.38.7...0.38.8)) -* 0.38.7 (2016-03-23, [log](https://github.com/CLxS/SMAPI/compare/0.38.6...0.38.7)) -* 0.38.6 (2016-03-22, [log](https://github.com/CLxS/SMAPI/compare/0.38.5...0.38.6)) -* 0.38.5 (2016-03-22, [log](https://github.com/CLxS/SMAPI/compare/0.38.4...0.38.5)) -* 0.38.4 (2016-03-21, [log](https://github.com/CLxS/SMAPI/compare/0.38.3...0.38.4)) -* 0.38.3 (2016-03-21, [log](https://github.com/CLxS/SMAPI/compare/0.38.2...0.38.3)) -* 0.38.2 (2016-03-21, [log](https://github.com/CLxS/SMAPI/compare/0.38.0...0.38.2)) -* 0.38.0 (2016-03-20, [log](https://github.com/CLxS/SMAPI/compare/0.38.1...0.38.0)) -* 0.38.1 (2016-03-20, [log](https://github.com/CLxS/SMAPI/compare/0.37.3...0.38.1)) -* 0.37.3 (2016-03-08, [log](https://github.com/CLxS/SMAPI/compare/0.37.2...0.37.3)) -* 0.37.2 (2016-03-07, [log](https://github.com/CLxS/SMAPI/compare/0.37.1...0.37.2)) -* 0.37.1 (2016-03-06, [log](https://github.com/CLxS/SMAPI/compare/0.36...0.37.1)) -* 0.36 (2016-03-04, [log](https://github.com/CLxS/SMAPI/compare/0.37...0.36)) -* 0.37 (2016-03-04, [log](https://github.com/CLxS/SMAPI/compare/0.35...0.37)) -* 0.35 (2016-03-02, [log](https://github.com/CLxS/SMAPI/compare/0.34...0.35)) -* 0.34 (2016-03-02, [log](https://github.com/CLxS/SMAPI/compare/0.33...0.34)) -* 0.33 (2016-03-02, [log](https://github.com/CLxS/SMAPI/compare/0.32...0.33)) -* 0.32 (2016-03-02, [log](https://github.com/CLxS/SMAPI/compare/0.31...0.32)) -* 0.31 (2016-03-02, [log](https://github.com/CLxS/SMAPI/compare/0.3...0.31)) -* 0.3 (2016-03-01, [log](https://github.com/CLxS/SMAPI/compare/Alpha0.2...0.3)) -* 0.2 (2016-02-29, [log](https://github.com/CLxS/SMAPI/compare/Alpha0.1...Alpha0.2) +* 0.39.5 (2016-03-30, [log](https://github.com/Pathoschild/SMAPI/compare/0.39.4...0.39.5)) +* 0.39.4 (2016-03-29, [log](https://github.com/Pathoschild/SMAPI/compare/0.39.3...0.39.4)) +* 0.39.3 (2016-03-28, [log](https://github.com/Pathoschild/SMAPI/compare/0.39.2...0.39.3)) +* 0.39.2 (2016-03-23, [log](https://github.com/Pathoschild/SMAPI/compare/0.39.1...0.39.2)) +* 0.39.1 (2016-03-23, [log](https://github.com/Pathoschild/SMAPI/compare/0.38.8...0.39.1)) +* 0.38.8 (2016-03-23, [log](https://github.com/Pathoschild/SMAPI/compare/0.38.7...0.38.8)) +* 0.38.7 (2016-03-23, [log](https://github.com/Pathoschild/SMAPI/compare/0.38.6...0.38.7)) +* 0.38.6 (2016-03-22, [log](https://github.com/Pathoschild/SMAPI/compare/0.38.5...0.38.6)) +* 0.38.5 (2016-03-22, [log](https://github.com/Pathoschild/SMAPI/compare/0.38.4...0.38.5)) +* 0.38.4 (2016-03-21, [log](https://github.com/Pathoschild/SMAPI/compare/0.38.3...0.38.4)) +* 0.38.3 (2016-03-21, [log](https://github.com/Pathoschild/SMAPI/compare/0.38.2...0.38.3)) +* 0.38.2 (2016-03-21, [log](https://github.com/Pathoschild/SMAPI/compare/0.38.0...0.38.2)) +* 0.38.0 (2016-03-20, [log](https://github.com/Pathoschild/SMAPI/compare/0.38.1...0.38.0)) +* 0.38.1 (2016-03-20, [log](https://github.com/Pathoschild/SMAPI/compare/0.37.3...0.38.1)) +* 0.37.3 (2016-03-08, [log](https://github.com/Pathoschild/SMAPI/compare/0.37.2...0.37.3)) +* 0.37.2 (2016-03-07, [log](https://github.com/Pathoschild/SMAPI/compare/0.37.1...0.37.2)) +* 0.37.1 (2016-03-06, [log](https://github.com/Pathoschild/SMAPI/compare/0.36...0.37.1)) +* 0.36 (2016-03-04, [log](https://github.com/Pathoschild/SMAPI/compare/0.37...0.36)) +* 0.37 (2016-03-04, [log](https://github.com/Pathoschild/SMAPI/compare/0.35...0.37)) +* 0.35 (2016-03-02, [log](https://github.com/Pathoschild/SMAPI/compare/0.34...0.35)) +* 0.34 (2016-03-02, [log](https://github.com/Pathoschild/SMAPI/compare/0.33...0.34)) +* 0.33 (2016-03-02, [log](https://github.com/Pathoschild/SMAPI/compare/0.32...0.33)) +* 0.32 (2016-03-02, [log](https://github.com/Pathoschild/SMAPI/compare/0.31...0.32)) +* 0.31 (2016-03-02, [log](https://github.com/Pathoschild/SMAPI/compare/0.3...0.31)) +* 0.3 (2016-03-01, [log](https://github.com/Pathoschild/SMAPI/compare/Alpha0.2...0.3)) +* 0.2 (2016-02-29, [log](https://github.com/Pathoschild/SMAPI/compare/Alpha0.1...Alpha0.2) * 0.1 (2016-02-28) -- cgit From 487ae1dce92a410984a7c13bf0f30bdd0d878aea Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 18 Dec 2016 13:52:09 -0500 Subject: add version.IsOlderThan(other) method for convenience --- src/StardewModdingAPI/Version.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/StardewModdingAPI/Version.cs b/src/StardewModdingAPI/Version.cs index bdecc8e0..7c6d319c 100644 --- a/src/StardewModdingAPI/Version.cs +++ b/src/StardewModdingAPI/Version.cs @@ -98,6 +98,13 @@ namespace StardewModdingAPI return string.Compare(this.ToString(), other.ToString(), StringComparison.InvariantCultureIgnoreCase); } + /// Get whether this version is older than the specified version. + /// The version to compare with this instance. + public bool IsOlderThan(Version other) + { + return this.CompareTo(other) < 0; + } + /// Get whether this version is newer than the specified version. /// The version to compare with this instance. public bool IsNewerThan(Version other) -- cgit From 23988a3c33a7a1616c2d36a2c4b7e3a2d06f4216 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 18 Dec 2016 15:37:23 -0500 Subject: migrate manifest & version to interfaces with backwards compatibility (#197) --- src/StardewModdingAPI/Constants.cs | 8 +- .../Framework/AssemblyRewriting/CacheEntry.cs | 2 +- .../Framework/ModAssemblyLoader.cs | 4 +- src/StardewModdingAPI/IManifest.cs | 27 +++++ src/StardewModdingAPI/ISemanticVersion.cs | 35 ++++++ src/StardewModdingAPI/Manifest.cs | 60 ++++++---- src/StardewModdingAPI/Mod.cs | 15 ++- src/StardewModdingAPI/Program.cs | 22 ++-- src/StardewModdingAPI/SemanticVersion.cs | 130 +++++++++++++++++++++ src/StardewModdingAPI/StardewModdingAPI.csproj | 3 + src/StardewModdingAPI/Version.cs | 118 +++++++------------ 11 files changed, 311 insertions(+), 113 deletions(-) create mode 100644 src/StardewModdingAPI/IManifest.cs create mode 100644 src/StardewModdingAPI/ISemanticVersion.cs create mode 100644 src/StardewModdingAPI/SemanticVersion.cs diff --git a/src/StardewModdingAPI/Constants.cs b/src/StardewModdingAPI/Constants.cs index f5b9e70a..57a89e76 100644 --- a/src/StardewModdingAPI/Constants.cs +++ b/src/StardewModdingAPI/Constants.cs @@ -26,7 +26,11 @@ namespace StardewModdingAPI ** Accessors *********/ /// SMAPI's current semantic version. - public static readonly Version Version = new Version(1, 4, 0, null); + [Obsolete("Use " + nameof(Constants) + "." + nameof(ApiVersion))] + public static readonly Version Version = (Version)Constants.ApiVersion; + + /// SMAPI's current semantic version. + public static ISemanticVersion ApiVersion => new Version(1, 4, 0, null, suppressDeprecationWarning: true); /// The minimum supported version of Stardew Valley. public const string MinimumGameVersion = "1.1"; @@ -56,7 +60,7 @@ namespace StardewModdingAPI public static string ExecutionPath => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); /// The title of the SMAPI console window. - public static string ConsoleTitle => $"Stardew Modding API Console - Version {Constants.Version} - Mods Loaded: {Program.ModsLoaded}"; + public static string ConsoleTitle => $"Stardew Modding API Console - Version {Constants.ApiVersion} - Mods Loaded: {Program.ModsLoaded}"; /// The directory path in which error logs should be stored. public static string LogDir => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley", "ErrorLogs"); diff --git a/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs b/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs index 3dfbc78c..a747eaa8 100644 --- a/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs +++ b/src/StardewModdingAPI/Framework/AssemblyRewriting/CacheEntry.cs @@ -36,7 +36,7 @@ namespace StardewModdingAPI.Framework.AssemblyRewriting /// The paths for the cached assembly. /// The MD5 hash of the original assembly. /// The current SMAPI version. - public bool IsUpToDate(CachePaths paths, string hash, Version currentVersion) + public bool IsUpToDate(CachePaths paths, string hash, ISemanticVersion currentVersion) { return hash == this.Hash && this.ApiVersion == currentVersion.ToString() diff --git a/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs b/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs index 1ceb8ad2..a2c4ac23 100644 --- a/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/ModAssemblyLoader.cs @@ -58,7 +58,7 @@ namespace StardewModdingAPI.Framework CachePaths cachePaths = this.GetCachePaths(assemblyPath); { CacheEntry cacheEntry = File.Exists(cachePaths.Metadata) ? JsonConvert.DeserializeObject(File.ReadAllText(cachePaths.Metadata)) : null; - if (cacheEntry != null && cacheEntry.IsUpToDate(cachePaths, hash, Constants.Version)) + if (cacheEntry != null && cacheEntry.IsUpToDate(cachePaths, hash, Constants.ApiVersion)) return new RewriteResult(assemblyPath, cachePaths, assemblyBytes, cacheEntry.Hash, cacheEntry.UseCachedAssembly, isNewerThanCache: false); // no rewrite needed } this.Monitor.Log($"Preprocessing {Path.GetFileName(assemblyPath)} for compatibility...", LogLevel.Trace); @@ -99,7 +99,7 @@ namespace StardewModdingAPI.Framework // cache all results foreach (RewriteResult result in results) { - CacheEntry cacheEntry = new CacheEntry(result.Hash, Constants.Version.ToString(), forceCacheAssemblies || result.UseCachedAssembly); + CacheEntry cacheEntry = new CacheEntry(result.Hash, Constants.ApiVersion.ToString(), forceCacheAssemblies || result.UseCachedAssembly); File.WriteAllText(result.CachePaths.Metadata, JsonConvert.SerializeObject(cacheEntry)); if (forceCacheAssemblies || result.UseCachedAssembly) File.WriteAllBytes(result.CachePaths.Assembly, result.AssemblyBytes); diff --git a/src/StardewModdingAPI/IManifest.cs b/src/StardewModdingAPI/IManifest.cs new file mode 100644 index 00000000..3e4b7513 --- /dev/null +++ b/src/StardewModdingAPI/IManifest.cs @@ -0,0 +1,27 @@ +namespace StardewModdingAPI +{ + /// A manifest which describes a mod for SMAPI. + public interface IManifest + { + /// The mod name. + string Name { get; set; } + + /// A brief description of the mod. + string Description { get; set; } + + /// The mod author's name. + string Author { get; } + + /// The mod version. + ISemanticVersion Version { get; set; } + + /// The minimum SMAPI version required by this mod, if any. + string MinimumApiVersion { get; set; } + + /// The unique mod ID. + string UniqueID { get; set; } + + /// The name of the DLL in the directory that has the method. + string EntryDll { get; set; } + } +} \ No newline at end of file diff --git a/src/StardewModdingAPI/ISemanticVersion.cs b/src/StardewModdingAPI/ISemanticVersion.cs new file mode 100644 index 00000000..f50752fe --- /dev/null +++ b/src/StardewModdingAPI/ISemanticVersion.cs @@ -0,0 +1,35 @@ +using System; + +namespace StardewModdingAPI +{ + /// A semantic version with an optional release tag. + public interface ISemanticVersion : IComparable + { + /********* + ** Accessors + *********/ + /// The major version incremented for major API changes. + int MajorVersion { get; } + + /// The minor version incremented for backwards-compatible changes. + int MinorVersion { get; } + + /// The patch version for backwards-compatible bug fixes. + int PatchVersion { get; } + + /// An optional build tag. + string Build { get; } + + + /********* + ** Accessors + *********/ + /// Get whether this version is older than the specified version. + /// The version to compare with this instance. + bool IsOlderThan(ISemanticVersion other); + + /// Get whether this version is newer than the specified version. + /// The version to compare with this instance. + bool IsNewerThan(ISemanticVersion other); + } +} \ No newline at end of file diff --git a/src/StardewModdingAPI/Manifest.cs b/src/StardewModdingAPI/Manifest.cs index bfc66c43..981ff023 100644 --- a/src/StardewModdingAPI/Manifest.cs +++ b/src/StardewModdingAPI/Manifest.cs @@ -3,21 +3,52 @@ using Newtonsoft.Json; namespace StardewModdingAPI { + /// Wraps so it can implement without breaking backwards compatibility. + [Obsolete("Use " + nameof(IManifest) + " or " + nameof(Mod) + "." + nameof(Mod.ModManifest) + " instead")] + internal class ManifestImpl : Manifest, IManifest + { + /// The mod version. + public new ISemanticVersion Version + { + get { return base.Version; } + set { base.Version = (Version)value; } + } + } + /// A manifest which describes a mod for SMAPI. public class Manifest { /********* ** Accessors *********/ - /// Whether the manifest defined the deprecated field. - [JsonIgnore] - internal bool UsedAuthourField { get; private set; } - /// The mod name. - public virtual string Name { get; set; } = ""; + public string Name { get; set; } + + /// A brief description of the mod. + public string Description { get; set; } /// The mod author's name. - public virtual string Author { get; set; } = ""; + public string Author { get; set; } + + /// The mod version. + public Version Version { get; set; } = new Version(0, 0, 0, "", suppressDeprecationWarning: true); + + /// The minimum SMAPI version required by this mod, if any. + public string MinimumApiVersion { get; set; } + + /// The name of the DLL in the directory that has the method. + public string EntryDll { get; set; } + + /// The unique mod ID. + public string UniqueID { get; set; } = Guid.NewGuid().ToString(); + + + /**** + ** Obsolete + ****/ + /// Whether the manifest defined the deprecated field. + [JsonIgnore] + internal bool UsedAuthourField { get; private set; } /// Obsolete. [Obsolete("Use " + nameof(Manifest) + "." + nameof(Manifest.Author) + ".")] @@ -31,23 +62,8 @@ namespace StardewModdingAPI } } - /// The mod version. - public virtual Version Version { get; set; } = new Version(0, 0, 0, ""); - - /// A brief description of the mod. - public virtual string Description { get; set; } = ""; - - /// The unique mod ID. - public virtual string UniqueID { get; set; } = Guid.NewGuid().ToString(); - /// Whether the mod uses per-save config files. [Obsolete("Use " + nameof(Mod) + "." + nameof(Mod.Helper) + "." + nameof(IModHelper.ReadConfig) + " instead")] - public virtual bool PerSaveConfigs { get; set; } - - /// The minimum SMAPI version required by this mod, if any. - public string MinimumApiVersion { get; set; } - - /// The name of the DLL in the directory that has the method. - public virtual string EntryDll { get; set; } = ""; + public bool PerSaveConfigs { get; set; } } } diff --git a/src/StardewModdingAPI/Mod.cs b/src/StardewModdingAPI/Mod.cs index 21551771..f0f876fa 100644 --- a/src/StardewModdingAPI/Mod.cs +++ b/src/StardewModdingAPI/Mod.cs @@ -24,7 +24,18 @@ namespace StardewModdingAPI public IMonitor Monitor { get; internal set; } /// The mod's manifest. - public Manifest Manifest { get; internal set; } + [Obsolete("Use " + nameof(Mod) + "." + nameof(ModManifest))] + public Manifest Manifest + { + get + { + Program.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Manifest)}", "1.5", DeprecationLevel.Notice); + return (Manifest)this.ModManifest; + } + } + + /// The mod's manifest. + public IManifest ModManifest { get; internal set; } /// The full path to the mod's directory on the disk. [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(IModHelper.DirectoryPath) + " instead")] @@ -94,7 +105,7 @@ namespace StardewModdingAPI Program.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(Mod.PerSaveConfigFolder)}", "1.0", DeprecationLevel.Notice); Program.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(Mod.PathOnDisk)}", "1.0"); // avoid redundant warnings - if (!this.Manifest.PerSaveConfigs) + if (!((Manifest)this.Manifest).PerSaveConfigs) { this.Monitor.Log("Tried to fetch the per-save config folder, but this mod isn't configured to use per-save config files.", LogLevel.Error); return ""; diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 62b9dabd..7c9cdcc3 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -98,7 +98,7 @@ namespace StardewModdingAPI Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); // add info header - Program.Monitor.Log($"SMAPI {Constants.Version} with Stardew Valley {Game1.version} on {Environment.OSVersion}", LogLevel.Info); + Program.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Game1.version} on {Environment.OSVersion}", LogLevel.Info); // load user settings { @@ -191,9 +191,9 @@ namespace StardewModdingAPI try { GitRelease release = UpdateHelper.GetLatestVersionAsync(Constants.GitHubRepository).Result; - Version latestVersion = new Version(release.Tag); - if (latestVersion.IsNewerThan(Constants.Version)) - Program.Monitor.Log($"You can update SMAPI from version {Constants.Version} to {latestVersion}", LogLevel.Alert); + ISemanticVersion latestVersion = new SemanticVersion(release.Tag); + if (latestVersion.IsNewerThan(Constants.ApiVersion)) + Program.Monitor.Log($"You can update SMAPI from version {Constants.ApiVersion} to {latestVersion}", LogLevel.Alert); } catch (Exception ex) { @@ -212,7 +212,7 @@ namespace StardewModdingAPI Program.StardewAssembly = Assembly.UnsafeLoadFrom(Program.GameExecutablePath); Program.StardewProgramType = Program.StardewAssembly.GetType("StardewValley.Program", true); Program.StardewGameInfo = Program.StardewProgramType.GetField("gamePtr"); - Game1.version += $"-Z_MODDED | SMAPI {Constants.Version}"; + Game1.version += $"-Z_MODDED | SMAPI {Constants.ApiVersion}"; // add error interceptors #if SMAPI_FOR_WINDOWS @@ -335,7 +335,7 @@ namespace StardewModdingAPI string errorPrefix = $"Couldn't load mod for manifest '{manifestPath}'"; // read manifest - Manifest manifest; + ManifestImpl manifest; try { // read manifest text @@ -347,7 +347,7 @@ namespace StardewModdingAPI } // deserialise manifest - manifest = helper.ReadJsonFile("manifest.json"); + manifest = helper.ReadJsonFile("manifest.json"); if (manifest == null) { Program.Monitor.Log($"{errorPrefix}: the manifest file does not exist.", LogLevel.Error); @@ -374,8 +374,8 @@ namespace StardewModdingAPI { try { - Version minVersion = new Version(manifest.MinimumApiVersion); - if (minVersion.IsNewerThan(Constants.Version)) + ISemanticVersion minVersion = new SemanticVersion(manifest.MinimumApiVersion); + if (minVersion.IsNewerThan(Constants.ApiVersion)) { Program.Monitor.Log($"{errorPrefix}: this mod requires SMAPI {minVersion} or later. Please update SMAPI to the latest version to use this mod.", LogLevel.Error); continue; @@ -473,11 +473,11 @@ namespace StardewModdingAPI Program.ModRegistry.Add(manifest, modAssembly); // hook up mod - modEntry.Manifest = manifest; + modEntry.ModManifest = manifest; modEntry.Helper = helper; modEntry.Monitor = new Monitor(manifest.Name, Program.LogFile) { ShowTraceInConsole = Program.DeveloperMode }; modEntry.PathOnDisk = directory; - Program.Monitor.Log($"Loaded mod: {modEntry.Manifest.Name} by {modEntry.Manifest.Author}, v{modEntry.Manifest.Version} | {modEntry.Manifest.Description}", LogLevel.Info); + Program.Monitor.Log($"Loaded mod: {modEntry.ModManifest.Name} by {modEntry.ModManifest.Author}, v{modEntry.ModManifest.Version} | {modEntry.ModManifest.Description}", LogLevel.Info); Program.ModsLoaded += 1; modEntry.Entry(); // deprecated since 1.0 modEntry.Entry((ModHelper)modEntry.Helper); // deprecated since 1.1 diff --git a/src/StardewModdingAPI/SemanticVersion.cs b/src/StardewModdingAPI/SemanticVersion.cs new file mode 100644 index 00000000..b3d9ee4a --- /dev/null +++ b/src/StardewModdingAPI/SemanticVersion.cs @@ -0,0 +1,130 @@ +using System; +using System.Text.RegularExpressions; + +namespace StardewModdingAPI +{ + /// A semantic version with an optional release tag. + public class SemanticVersion : ISemanticVersion + { + /********* + ** Properties + *********/ + /// A regular expression matching a semantic version string. + /// Derived from https://github.com/maxhauser/semver. + private static readonly Regex Regex = new Regex(@"^(?\d+)(\.(?\d+))?(\.(?\d+))?(?.*)$", RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture); + + + /********* + ** Accessors + *********/ + /// The major version incremented for major API changes. + public int MajorVersion { get; set; } + + /// The minor version incremented for backwards-compatible changes. + public int MinorVersion { get; set; } + + /// The patch version for backwards-compatible bug fixes. + public int PatchVersion { get; set; } + + /// An optional build tag. + public string Build { get; set; } + + + /********* + ** 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. + /// An optional build tag. + public SemanticVersion(int major, int minor, int patch, string build = null) + { + this.MajorVersion = major; + this.MinorVersion = minor; + this.PatchVersion = patch; + this.Build = build; + } + + /// Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version. + /// The version to compare with this instance. + public int CompareTo(ISemanticVersion other) + { + // compare version numbers + if (this.MajorVersion != other.MajorVersion) + return this.MajorVersion - other.MajorVersion; + if (this.MinorVersion != other.MinorVersion) + return this.MinorVersion - other.MinorVersion; + if (this.PatchVersion != other.PatchVersion) + return this.PatchVersion - other.PatchVersion; + + // stable version (without tag) supercedes prerelease (with tag) + bool curHasTag = !string.IsNullOrWhiteSpace(this.Build); + bool otherHasTag = !string.IsNullOrWhiteSpace(other.Build); + if (!curHasTag && otherHasTag) + return 1; + if (curHasTag && !otherHasTag) + return -1; + + // else compare by string + return string.Compare(this.ToString(), other.ToString(), StringComparison.InvariantCultureIgnoreCase); + } + + /// Get whether this version is older than the specified version. + /// The version to compare with this instance. + public bool IsOlderThan(ISemanticVersion other) + { + return this.CompareTo(other) < 0; + } + + /// Get whether this version is newer than the specified version. + /// The version to compare with this instance. + public bool IsNewerThan(ISemanticVersion other) + { + return this.CompareTo(other) > 0; + } + + /// Get a string representation of the version. + public override string ToString() + { + // version + string result = this.PatchVersion != 0 + ? $"{this.MajorVersion}.{this.MinorVersion}.{this.PatchVersion}" + : $"{this.MajorVersion}.{this.MinorVersion}"; + + // tag + string tag = this.GetNormalisedTag(this.Build); + if (tag != null) + result += $"-{tag}"; + return result; + } + + + /********* + ** Private methods + *********/ + /// Construct an instance. + /// The semantic version string. + internal SemanticVersion(string version) + { + var match = SemanticVersion.Regex.Match(version); + if (!match.Success) + throw new FormatException($"The input '{version}' is not a semantic version."); + + this.MajorVersion = int.Parse(match.Groups["major"].Value); + this.MinorVersion = match.Groups["minor"].Success ? int.Parse(match.Groups["minor"].Value) : 0; + this.PatchVersion = match.Groups["patch"].Success ? int.Parse(match.Groups["patch"].Value) : 0; + this.Build = (match.Groups["build"].Success ? match.Groups["build"].Value : "").Trim(' ', '-', '.'); + } + + /// Get a normalised build tag. + /// The tag to normalise. + private string GetNormalisedTag(string tag) + { + tag = tag?.Trim().Trim('-', '.'); + if (string.IsNullOrWhiteSpace(tag) || tag == "0") + return null; + return tag; + } + } +} \ No newline at end of file diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 65083e67..875bc1f3 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -166,8 +166,10 @@ + + @@ -190,6 +192,7 @@ + diff --git a/src/StardewModdingAPI/Version.cs b/src/StardewModdingAPI/Version.cs index 7c6d319c..9c13381a 100644 --- a/src/StardewModdingAPI/Version.cs +++ b/src/StardewModdingAPI/Version.cs @@ -1,21 +1,13 @@ using System; -using System.Text.RegularExpressions; using Newtonsoft.Json; using StardewModdingAPI.Framework; namespace StardewModdingAPI { /// A semantic version with an optional release tag. - public struct Version : IComparable + [Obsolete("Use " + nameof(SemanticVersion) + " or " + nameof(Manifest) + "." + nameof(Manifest.Version) + " instead")] + public struct Version : ISemanticVersion { - /********* - ** Properties - *********/ - /// A regular expression matching a semantic version string. - /// Derived from https://github.com/maxhauser/semver. - private static readonly Regex Regex = new Regex(@"^(?\d+)(\.(?\d+))?(\.(?\d+))?(?.*)$", RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture); - - /********* ** Accessors *********/ @@ -39,7 +31,7 @@ namespace StardewModdingAPI get { Program.DeprecationManager.Warn($"{nameof(Version)}.{nameof(Version.VersionString)}", "1.0", DeprecationLevel.Notice); - return this.ToString(); + return this.GetSemanticVersion().ToString(); } } @@ -53,92 +45,72 @@ namespace StardewModdingAPI /// The patch version for backwards-compatible bug fixes. /// An optional build tag. public Version(int major, int minor, int patch, string build) + : this(major, minor, patch, build, suppressDeprecationWarning: false) + { } + + /// Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version. + /// The version to compare with this instance. + public int CompareTo(Version other) { - this.MajorVersion = major; - this.MinorVersion = minor; - this.PatchVersion = patch; - this.Build = build; + return this.GetSemanticVersion().CompareTo(other); } - /// Construct an instance. - /// The semantic version string. - internal Version(string version) + /// Get whether this version is newer than the specified version. + /// The version to compare with this instance. + [Obsolete("Use " + nameof(ISemanticVersion) + "." + nameof(ISemanticVersion.IsNewerThan) + " instead")] + public bool IsNewerThan(Version other) { - var match = Version.Regex.Match(version); - if (!match.Success) - throw new FormatException($"The input '{version}' is not a semantic version."); - - this.MajorVersion = int.Parse(match.Groups["major"].Value); - this.MinorVersion = match.Groups["minor"].Success ? int.Parse(match.Groups["minor"].Value) : 0; - this.PatchVersion = match.Groups["patch"].Success ? int.Parse(match.Groups["patch"].Value) : 0; - this.Build = (match.Groups["build"].Success ? match.Groups["build"].Value : "").Trim(' ', '-', '.'); + return this.GetSemanticVersion().IsNewerThan(other); } - /// Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version. - /// The version to compare with this instance. - public int CompareTo(Version other) + /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. + /// A value that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance precedes in the sort order. Zero This instance occurs in the same position in the sort order as . Greater than zero This instance follows in the sort order. + /// An object to compare with this instance. + int IComparable.CompareTo(ISemanticVersion other) { - // compare version numbers - if (this.MajorVersion != other.MajorVersion) - return this.MajorVersion - other.MajorVersion; - if (this.MinorVersion != other.MinorVersion) - return this.MinorVersion - other.MinorVersion; - if (this.PatchVersion != other.PatchVersion) - return this.PatchVersion - other.PatchVersion; - - // stable version (without tag) supercedes prerelease (with tag) - bool curHasTag = !string.IsNullOrWhiteSpace(this.Build); - bool otherHasTag = !string.IsNullOrWhiteSpace(other.Build); - if (!curHasTag && otherHasTag) - return 1; - if (curHasTag && !otherHasTag) - return -1; - - // else compare by string - return string.Compare(this.ToString(), other.ToString(), StringComparison.InvariantCultureIgnoreCase); + return this.GetSemanticVersion().CompareTo(other); } /// Get whether this version is older than the specified version. /// The version to compare with this instance. - public bool IsOlderThan(Version other) + bool ISemanticVersion.IsOlderThan(ISemanticVersion other) { - return this.CompareTo(other) < 0; + return this.GetSemanticVersion().IsOlderThan(other); } /// Get whether this version is newer than the specified version. /// The version to compare with this instance. - public bool IsNewerThan(Version other) - { - return this.CompareTo(other) > 0; - } - - /// Get a string representation of the version. - public override string ToString() + bool ISemanticVersion.IsNewerThan(ISemanticVersion other) { - // version - string result = this.PatchVersion != 0 - ? $"{this.MajorVersion}.{this.MinorVersion}.{this.PatchVersion}" - : $"{this.MajorVersion}.{this.MinorVersion}"; - - // tag - string tag = this.GetNormalisedTag(this.Build); - if (tag != null) - result += $"-{tag}"; - return result; + return this.GetSemanticVersion().IsNewerThan(other); } /********* ** Private methods *********/ - /// Get a normalised build tag. - /// The tag to normalise. - private string GetNormalisedTag(string tag) + /// 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. + /// An optional build tag. + /// Whether to suppress the deprecation warning. + internal Version(int major, int minor, int patch, string build, bool suppressDeprecationWarning) + { + if (!suppressDeprecationWarning) + Program.DeprecationManager.Warn($"{nameof(Version)}", "1.5", DeprecationLevel.Notice); + + this.MajorVersion = major; + this.MinorVersion = minor; + this.PatchVersion = patch; + this.Build = build; + } + + /// Get the equivalent semantic version. + /// This is a hack so the struct can wrap without a mutable backing field, which would cause a due to recreating the struct value on each change. + private SemanticVersion GetSemanticVersion() { - tag = tag?.Trim().Trim('-', '.'); - if (string.IsNullOrWhiteSpace(tag) || tag == "0") - return null; - return tag; + return new SemanticVersion(this.MajorVersion, this.MinorVersion, this.PatchVersion, this.Build); } } } -- cgit From a8cc3636995c9fc5eb08fa9d42129a05d79bb666 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 18 Dec 2016 16:58:47 -0500 Subject: fix legacy version wrapper not implementing ToString method (#197) --- src/StardewModdingAPI/ISemanticVersion.cs | 3 +++ src/StardewModdingAPI/Version.cs | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/StardewModdingAPI/ISemanticVersion.cs b/src/StardewModdingAPI/ISemanticVersion.cs index f50752fe..3b9bdb44 100644 --- a/src/StardewModdingAPI/ISemanticVersion.cs +++ b/src/StardewModdingAPI/ISemanticVersion.cs @@ -31,5 +31,8 @@ namespace StardewModdingAPI /// Get whether this version is newer than the specified version. /// The version to compare with this instance. bool IsNewerThan(ISemanticVersion other); + + /// Get a string representation of the version. + string ToString(); } } \ No newline at end of file diff --git a/src/StardewModdingAPI/Version.cs b/src/StardewModdingAPI/Version.cs index 9c13381a..87cd4f3d 100644 --- a/src/StardewModdingAPI/Version.cs +++ b/src/StardewModdingAPI/Version.cs @@ -85,6 +85,11 @@ namespace StardewModdingAPI return this.GetSemanticVersion().IsNewerThan(other); } + /// Get a string representation of the version. + public override string ToString() + { + return this.GetSemanticVersion().ToString(); + } /********* ** Private methods -- cgit From 45ee74219ee931b5fef3b67c08adf79d55aa091d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 18 Dec 2016 17:42:51 -0500 Subject: fix issue where changing the active menu inside a menu change handler didn't trigger a new event (#194) --- src/StardewModdingAPI/Inheritance/SGame.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/StardewModdingAPI/Inheritance/SGame.cs b/src/StardewModdingAPI/Inheritance/SGame.cs index f70d0696..16803a73 100644 --- a/src/StardewModdingAPI/Inheritance/SGame.cs +++ b/src/StardewModdingAPI/Inheritance/SGame.cs @@ -913,11 +913,17 @@ namespace StardewModdingAPI.Inheritance // raise menu changed if (Game1.activeClickableMenu != this.PreviousActiveMenu) { + // raise events + IClickableMenu previousMenu = this.PreviousActiveMenu; + IClickableMenu newMenu = Game1.activeClickableMenu; if (Game1.activeClickableMenu != null) - MenuEvents.InvokeMenuChanged(this.Monitor, this.PreviousActiveMenu, Game1.activeClickableMenu); + MenuEvents.InvokeMenuChanged(this.Monitor, previousMenu, newMenu); else - MenuEvents.InvokeMenuClosed(this.Monitor, this.PreviousActiveMenu); - this.PreviousActiveMenu = Game1.activeClickableMenu; + MenuEvents.InvokeMenuClosed(this.Monitor, previousMenu); + + // update previous menu + // (if the menu was changed in one of the handlers, deliberately defer detection until the next update so mods can be notified of the new menu change) + this.PreviousActiveMenu = newMenu; } // raise location list changed -- cgit From ea65b2b7df78fd075caecac96937e9870e06e646 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 21 Dec 2016 11:45:52 -0500 Subject: correct semantic version pre-release label precedence (#195) --- src/StardewModdingAPI/SemanticVersion.cs | 59 ++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/src/StardewModdingAPI/SemanticVersion.cs b/src/StardewModdingAPI/SemanticVersion.cs index b3d9ee4a..cf435aaa 100644 --- a/src/StardewModdingAPI/SemanticVersion.cs +++ b/src/StardewModdingAPI/SemanticVersion.cs @@ -48,25 +48,56 @@ namespace StardewModdingAPI /// Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version. /// The version to compare with this instance. + /// The implementation is defined by Semantic Version 2.0 (http://semver.org/). public int CompareTo(ISemanticVersion other) { - // compare version numbers + const int same = 0; + const int curNewer = 1; + const int curOlder = -1; + + // compare stable versions if (this.MajorVersion != other.MajorVersion) - return this.MajorVersion - other.MajorVersion; + return this.MajorVersion.CompareTo(other.MajorVersion); if (this.MinorVersion != other.MinorVersion) - return this.MinorVersion - other.MinorVersion; + return this.MinorVersion.CompareTo(other.MinorVersion); if (this.PatchVersion != other.PatchVersion) - return this.PatchVersion - other.PatchVersion; - - // stable version (without tag) supercedes prerelease (with tag) - bool curHasTag = !string.IsNullOrWhiteSpace(this.Build); - bool otherHasTag = !string.IsNullOrWhiteSpace(other.Build); - if (!curHasTag && otherHasTag) - return 1; - if (curHasTag && !otherHasTag) - return -1; - - // else compare by string + return this.PatchVersion.CompareTo(other.PatchVersion); + if (this.Build == other.Build) + return same; + + // stable supercedes pre-release + bool curIsStable = string.IsNullOrWhiteSpace(this.Build); + bool otherIsStable = string.IsNullOrWhiteSpace(other.Build); + if (curIsStable) + return curNewer; + if (otherIsStable) + return curOlder; + + // compare two pre-release tag values + string[] curParts = this.Build.Split('.'); + string[] otherParts = other.Build.Split('.'); + for (int i = 0; i < curParts.Length; i++) + { + // longer prerelease tag supercedes if otherwise equal + if (otherParts.Length <= i) + return curNewer; + + // compare if different + if (curParts[i] != otherParts[i]) + { + // compare numerically if possible + { + int curNum, otherNum; + if (int.TryParse(curParts[i], out curNum) && int.TryParse(otherParts[i], out otherNum)) + return curNum.CompareTo(otherNum); + } + + // else compare lexically + return string.Compare(curParts[i], otherParts[i], StringComparison.OrdinalIgnoreCase); + } + } + + // fallback (this should never happen) return string.Compare(this.ToString(), other.ToString(), StringComparison.InvariantCultureIgnoreCase); } -- cgit From c257d705758a23cf28ce2c1f00b6d3d42cd30a26 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 21 Dec 2016 13:27:56 -0500 Subject: rename SMAPI config file for consistency (#192, #202) --- README.md | 8 ++++---- src/StardewModdingAPI.Installer/InteractiveInstaller.cs | 5 +++-- .../StardewModdingAPI.Installer.csproj | 4 ++-- src/StardewModdingAPI/Program.cs | 2 +- src/StardewModdingAPI/StardewModdingAPI-settings.json | 4 ---- src/StardewModdingAPI/StardewModdingAPI.config.json | 4 ++++ src/StardewModdingAPI/StardewModdingAPI.csproj | 3 ++- 7 files changed, 16 insertions(+), 14 deletions(-) delete mode 100644 src/StardewModdingAPI/StardewModdingAPI-settings.json create mode 100644 src/StardewModdingAPI/StardewModdingAPI.config.json diff --git a/README.md b/README.md index 39cfb25c..c4bcd8d4 100644 --- a/README.md +++ b/README.md @@ -78,9 +78,9 @@ directory containing `src`). Mono.Ceci