From 2a9c8d43df156ba2b6eb32c690eba4a80167a549 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 7 Jun 2017 02:08:20 -0400 Subject: add date utility --- src/StardewModdingAPI/StardewModdingAPI.csproj | 3 +- src/StardewModdingAPI/Utilities/SDate.cs | 131 +++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 src/StardewModdingAPI/Utilities/SDate.cs (limited to 'src/StardewModdingAPI') diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index ae454a35..7cc537ac 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -202,6 +202,7 @@ + @@ -273,4 +274,4 @@ - + \ No newline at end of file diff --git a/src/StardewModdingAPI/Utilities/SDate.cs b/src/StardewModdingAPI/Utilities/SDate.cs new file mode 100644 index 00000000..4729bfb9 --- /dev/null +++ b/src/StardewModdingAPI/Utilities/SDate.cs @@ -0,0 +1,131 @@ +using System; +using System.Linq; +using StardewValley; + +namespace StardewModdingAPI.Utilities +{ + /// Represents a Stardew Valley date. + public class SDate + { + /********* + ** Properties + *********/ + /// The internal season names in order. + private readonly string[] Seasons = { "spring", "summer", "fall", "winter" }; + + /// The number of days in a season. + private readonly int DaysInSeason = 28; + + + /********* + ** Accessors + *********/ + /// The day of month. + public int Day { get; } + + /// The season name. + public string Season { get; } + + /// The year. + public int Year { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The day of month. + /// The season name. + /// One of the arguments has an invalid value (like day 35). + public SDate(int day, string season) + : this(day, season, Game1.year) { } + + /// Construct an instance. + /// The day of month. + /// The season name. + /// The year. + /// One of the arguments has an invalid value (like day 35). + public SDate(int day, string season, int year) + { + // validate + if (season == null) + throw new ArgumentNullException(nameof(season)); + if (!this.Seasons.Contains(season)) + throw new ArgumentException($"Unknown season '{season}', must be one of [{string.Join(", ", this.Seasons)}]."); + if (day < 1 || day > this.DaysInSeason) + throw new ArgumentException($"Invalid day '{day}', must be a value from 1 to {this.DaysInSeason}."); + if (year < 1) + throw new ArgumentException($"Invalid year '{year}', must be at least 1."); + + // initialise + this.Day = day; + this.Season = season; + this.Year = year; + } + + /// Get a new date with the given number of days added. + /// The number of days to add. + /// Returns the resulting date. + /// The offset would result in an invalid date (like year 0). + public SDate AddDays(int offset) + { + // simple case + int day = this.Day + offset; + string season = this.Season; + int year = this.Year; + + // handle season transition + if (day > this.DaysInSeason || day < 1) + { + // get current season index + int curSeasonIndex = Array.IndexOf(this.Seasons, this.Season); + if (curSeasonIndex == -1) + throw new InvalidOperationException($"The current season '{this.Season}' wasn't recognised."); + + // get season offset + int seasonOffset = day / this.DaysInSeason; + if (day < 1) + seasonOffset -= 1; + + // get new date + day = this.GetWrappedIndex(day, this.DaysInSeason); + season = this.Seasons[this.GetWrappedIndex(curSeasonIndex + seasonOffset, this.Seasons.Length)]; + year += seasonOffset / this.Seasons.Length; + } + + // validate + if(year < 1) + throw new ArithmeticException($"Adding {offset} days to {this} would result in invalid date {day:00} {season} {year}."); + + // return new date + return new SDate(day, season, year); + } + + /// Get a string representation of the date. This is mainly intended for debugging or console messages. + public override string ToString() + { + return $"{this.Day:00} {this.Season} Y{this.Year}"; + } + + /// Get the current in-game date. + public static SDate Now() + { + return new SDate(Game1.dayOfMonth, Game1.currentSeason, Game1.year); + } + + + /********* + ** Private methods + *********/ + /// Get the real index in an array which should be treated as a two-way loop. + /// The index in the looped array. + /// The number of elements in the array. + private int GetWrappedIndex(int index, int length) + { + int wrapped = index % length; + if (wrapped < 0) + wrapped += length; + return wrapped; + } + } +} -- cgit From a4713ea88238e6a6d62447aef97b35321e63c010 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 12 Jun 2017 18:44:36 -0400 Subject: add separate list of obsolete mods --- .../Framework/ModLoading/ModResolver.cs | 22 +++++++++++++++++----- .../Framework/Models/DisabledMod.cs | 22 ++++++++++++++++++++++ src/StardewModdingAPI/Framework/Models/SConfig.cs | 3 +++ src/StardewModdingAPI/Program.cs | 2 +- .../StardewModdingAPI.config.json | 20 ++++++++++++-------- src/StardewModdingAPI/StardewModdingAPI.csproj | 1 + 6 files changed, 56 insertions(+), 14 deletions(-) create mode 100644 src/StardewModdingAPI/Framework/Models/DisabledMod.cs (limited to 'src/StardewModdingAPI') diff --git a/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs b/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs index f5139ce5..e8308f3e 100644 --- a/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs +++ b/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs @@ -17,10 +17,13 @@ namespace StardewModdingAPI.Framework.ModLoading /// The root path to search for mods. /// The JSON helper with which to read manifests. /// Metadata about mods that SMAPI should assume is compatible or broken, regardless of whether it detects incompatible code. + /// Metadata about mods that SMAPI should consider obsolete and not load. /// Returns the manifests by relative folder. - public IEnumerable ReadManifests(string rootPath, JsonHelper jsonHelper, IEnumerable compatibilityRecords) + public IEnumerable ReadManifests(string rootPath, JsonHelper jsonHelper, IEnumerable compatibilityRecords, IEnumerable disabledMods) { compatibilityRecords = compatibilityRecords.ToArray(); + disabledMods = disabledMods.ToArray(); + foreach (DirectoryInfo modDir in this.GetModFolders(rootPath)) { // read file @@ -47,20 +50,29 @@ namespace StardewModdingAPI.Framework.ModLoading error = $"parsing its manifest failed:\n{ex.GetLogSummary()}"; } - // get compatibility record + // validate metadata ModCompatibility compatibility = null; if (manifest != null) { + // get unique key for lookups string key = !string.IsNullOrWhiteSpace(manifest.UniqueID) ? manifest.UniqueID : manifest.EntryDll; + + // check if mod should be disabled + DisabledMod disabledMod = disabledMods.FirstOrDefault(mod => mod.ID.Contains(key, StringComparer.InvariantCultureIgnoreCase)); + if (disabledMod != null) + error = $"it's obsolete: {disabledMod.ReasonPhrase}"; + + // get compatibility record compatibility = ( from mod in compatibilityRecords where - mod.ID.Contains(key, StringComparer.InvariantCultureIgnoreCase) - && (mod.LowerSemanticVersion == null || !manifest.Version.IsOlderThan(mod.LowerSemanticVersion)) - && !manifest.Version.IsNewerThan(mod.UpperSemanticVersion) + mod.ID.Contains(key, StringComparer.InvariantCultureIgnoreCase) + && (mod.LowerSemanticVersion == null || !manifest.Version.IsOlderThan(mod.LowerSemanticVersion)) + && !manifest.Version.IsNewerThan(mod.UpperSemanticVersion) select mod ).FirstOrDefault(); } + // build metadata string displayName = !string.IsNullOrWhiteSpace(manifest?.Name) ? manifest.Name diff --git a/src/StardewModdingAPI/Framework/Models/DisabledMod.cs b/src/StardewModdingAPI/Framework/Models/DisabledMod.cs new file mode 100644 index 00000000..170fa760 --- /dev/null +++ b/src/StardewModdingAPI/Framework/Models/DisabledMod.cs @@ -0,0 +1,22 @@ +namespace StardewModdingAPI.Framework.Models +{ + /// Metadata about for a mod that should never be loaded. + internal class DisabledMod + { + /********* + ** Accessors + *********/ + /**** + ** From config + ****/ + /// The unique mod IDs. + public string[] ID { get; set; } + + /// The mod name. + public string Name { get; set; } + + /// The reason phrase to show in the warning, or null to use the default value. + /// "this mod is no longer supported or used" + public string ReasonPhrase { get; set; } + } +} diff --git a/src/StardewModdingAPI/Framework/Models/SConfig.cs b/src/StardewModdingAPI/Framework/Models/SConfig.cs index c3f0816e..b2ca4113 100644 --- a/src/StardewModdingAPI/Framework/Models/SConfig.cs +++ b/src/StardewModdingAPI/Framework/Models/SConfig.cs @@ -17,5 +17,8 @@ /// A list of mod versions which should be considered compatible or incompatible regardless of whether SMAPI detects incompatible code. public ModCompatibility[] ModCompatibility { get; set; } + + /// A list of mods which should be considered obsolete and not loaded. + public DisabledMod[] DisabledMods { get; set; } } } diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index d75d5193..71f09f5c 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -364,7 +364,7 @@ namespace StardewModdingAPI ModResolver resolver = new ModResolver(); // load manifests - IModMetadata[] mods = resolver.ReadManifests(Constants.ModPath, new JsonHelper(), this.Settings.ModCompatibility).ToArray(); + IModMetadata[] mods = resolver.ReadManifests(Constants.ModPath, new JsonHelper(), this.Settings.ModCompatibility, this.Settings.DisabledMods).ToArray(); resolver.ValidateManifests(mods, Constants.ApiVersion); // check for deprecated metadata diff --git a/src/StardewModdingAPI/StardewModdingAPI.config.json b/src/StardewModdingAPI/StardewModdingAPI.config.json index f62db90c..432a40e5 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.config.json +++ b/src/StardewModdingAPI/StardewModdingAPI.config.json @@ -26,6 +26,18 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha */ "VerboseLogging": false, + /** + * A list of mods SMAPI should consider obsolete and not load. Changing this field is not + * recommended and may destabilise your game. + */ + "DisabledMods": [ + { + "Name": "StarDustCore", + "ID": [ "StarDustCore" ], + "ReasonPhrase": "it was only used by earlier versions of Save Anywhere (which no longer uses it), and is no longer maintained." + } + ], + /** * A list of mod versions SMAPI should consider compatible or broken regardless of whether it * detects incompatible code. Each record can be set to `AssumeCompatible` or `AssumeBroken`. @@ -315,14 +327,6 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/798", "Notes": "Needs update for SDV 1.2." }, - { - "Name": "StarDustCore", - "ID": [ "StarDustCore" ], - "UpperVersion": "1.0", - "Compatibility": "AssumeBroken", - "UpdateUrl": "http://www.nexusmods.com/stardewvalley/mods/683", - "Notes": "Obsolete (originally needed by Save Anywhere); broken in SDV 1.2." - }, { "Name": "Teleporter", "ID": [ "Teleporter" ], diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 7cc537ac..0e832848 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -124,6 +124,7 @@ + -- cgit From b0967e6309cd10df4aa08ae0d3719ba92155f604 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 12 Jun 2017 18:51:17 -0400 Subject: add SerializerUtils to obsolete-mods list --- src/StardewModdingAPI/StardewModdingAPI.config.json | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/StardewModdingAPI') diff --git a/src/StardewModdingAPI/StardewModdingAPI.config.json b/src/StardewModdingAPI/StardewModdingAPI.config.json index 432a40e5..4e871636 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.config.json +++ b/src/StardewModdingAPI/StardewModdingAPI.config.json @@ -31,6 +31,11 @@ This file contains advanced configuration for SMAPI. You generally shouldn't cha * recommended and may destabilise your game. */ "DisabledMods": [ + { + "Name": "Modder Serialization Utility", + "ID": [ "SerializerUtils-0-1" ], + "ReasonPhrase": "it's no longer used by any mods, and is no longer maintained." + }, { "Name": "StarDustCore", "ID": [ "StarDustCore" ], -- cgit From cdac6dad7d163736ead307041e15857123e07951 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 18 Jun 2017 18:01:15 -0400 Subject: enable C# 7 tuples --- src/StardewModdingAPI/StardewModdingAPI.csproj | 4 ++++ src/StardewModdingAPI/packages.config | 1 + 2 files changed, 5 insertions(+) (limited to 'src/StardewModdingAPI') diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 0e832848..465a5ea7 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -79,6 +79,9 @@ True + + ..\packages\System.ValueTuple.4.3.1\lib\netstandard1.0\System.ValueTuple.dll + @@ -263,6 +266,7 @@ + diff --git a/src/StardewModdingAPI/packages.config b/src/StardewModdingAPI/packages.config index e5fa3c3a..6a2a8d1b 100644 --- a/src/StardewModdingAPI/packages.config +++ b/src/StardewModdingAPI/packages.config @@ -2,4 +2,5 @@ + \ No newline at end of file -- cgit From 3c3953a7fdca6e79f50a4a5474be69ca6aab6446 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 18 Jun 2017 18:18:04 -0400 Subject: add support for minimum dependency versions (#286) --- .../Framework/ModLoading/ModResolver.cs | 44 ++++++++++++++-------- .../Framework/Models/ManifestDependency.cs | 9 ++++- .../Serialisation/ManifestFieldConverter.cs | 3 +- src/StardewModdingAPI/IManifestDependency.cs | 3 ++ 4 files changed, 41 insertions(+), 18 deletions(-) (limited to 'src/StardewModdingAPI') diff --git a/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs b/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs index e8308f3e..dc140483 100644 --- a/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs +++ b/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs @@ -205,20 +205,40 @@ namespace StardewModdingAPI.Framework.ModLoading return states[mod] = ModDependencyStatus.Sorted; } + // get dependencies + var dependencies = + ( + from entry in mod.Manifest.Dependencies + let dependencyMod = mods.FirstOrDefault(m => string.Equals(m.Manifest?.UniqueID, entry.UniqueID, StringComparison.InvariantCultureIgnoreCase)) + orderby entry.UniqueID + select (ID: entry.UniqueID, MinVersion: entry.MinimumVersion, Mod: dependencyMod) + ) + .ToArray(); + // missing required dependencies, mark failed { - string[] missingModIDs = + string[] failedIDs = (from entry in dependencies where entry.Mod == null select entry.ID).ToArray(); + if (failedIDs.Any()) + { + sortedMods.Push(mod); + mod.SetStatus(ModMetadataStatus.Failed, $"it requires mods which aren't installed ({string.Join(", ", failedIDs)})."); + return states[mod] = ModDependencyStatus.Failed; + } + } + + // dependency min version not met, mark failed + { + string[] failedLabels = ( - from dependency in mod.Manifest.Dependencies - where mods.All(m => m.Manifest?.UniqueID != dependency.UniqueID) - orderby dependency.UniqueID - select dependency.UniqueID + from entry in dependencies + where entry.MinVersion != null && entry.MinVersion.IsNewerThan(entry.Mod.Manifest.Version) + select $"{entry.Mod.DisplayName} (needs {entry.MinVersion} or later)" ) .ToArray(); - if (missingModIDs.Any()) + if (failedLabels.Any()) { sortedMods.Push(mod); - mod.SetStatus(ModMetadataStatus.Failed, $"it requires mods which aren't installed ({string.Join(", ", missingModIDs)})."); + mod.SetStatus(ModMetadataStatus.Failed, $"it needs newer versions of some mods: {string.Join(", ", failedLabels)}."); return states[mod] = ModDependencyStatus.Failed; } } @@ -227,16 +247,8 @@ namespace StardewModdingAPI.Framework.ModLoading { states[mod] = ModDependencyStatus.Checking; - // get mods to load first - IModMetadata[] modsToLoadFirst = - ( - from other in mods - where mod.Manifest.Dependencies.Any(required => required.UniqueID == other.Manifest?.UniqueID) - select other - ) - .ToArray(); - // recursively sort dependencies + IModMetadata[] modsToLoadFirst = dependencies.Select(p => p.Mod).ToArray(); foreach (IModMetadata requiredMod in modsToLoadFirst) { var subchain = new List(currentChain) { mod }; diff --git a/src/StardewModdingAPI/Framework/Models/ManifestDependency.cs b/src/StardewModdingAPI/Framework/Models/ManifestDependency.cs index 2f580c1d..a0ff0c90 100644 --- a/src/StardewModdingAPI/Framework/Models/ManifestDependency.cs +++ b/src/StardewModdingAPI/Framework/Models/ManifestDependency.cs @@ -9,15 +9,22 @@ /// The unique mod ID to require. public string UniqueID { get; set; } + /// The minimum required version (if any). + public ISemanticVersion MinimumVersion { get; set; } + /********* ** Public methods *********/ /// Construct an instance. /// The unique mod ID to require. - public ManifestDependency(string uniqueID) + /// The minimum required version (if any). + public ManifestDependency(string uniqueID, string minimumVersion) { this.UniqueID = uniqueID; + this.MinimumVersion = !string.IsNullOrWhiteSpace(minimumVersion) + ? new SemanticVersion(minimumVersion) + : null; } } } diff --git a/src/StardewModdingAPI/Framework/Serialisation/ManifestFieldConverter.cs b/src/StardewModdingAPI/Framework/Serialisation/ManifestFieldConverter.cs index 6b5a6aaa..7acb5fd0 100644 --- a/src/StardewModdingAPI/Framework/Serialisation/ManifestFieldConverter.cs +++ b/src/StardewModdingAPI/Framework/Serialisation/ManifestFieldConverter.cs @@ -51,7 +51,8 @@ namespace StardewModdingAPI.Framework.Serialisation foreach (JObject obj in JArray.Load(reader).Children()) { string uniqueID = obj.Value(nameof(IManifestDependency.UniqueID)); - result.Add(new ManifestDependency(uniqueID)); + string minVersion = obj.Value(nameof(IManifestDependency.MinimumVersion)); + result.Add(new ManifestDependency(uniqueID, minVersion)); } return result.ToArray(); } diff --git a/src/StardewModdingAPI/IManifestDependency.cs b/src/StardewModdingAPI/IManifestDependency.cs index 7bd2e8b6..ebb1140e 100644 --- a/src/StardewModdingAPI/IManifestDependency.cs +++ b/src/StardewModdingAPI/IManifestDependency.cs @@ -8,5 +8,8 @@ *********/ /// The unique mod ID to require. string UniqueID { get; } + + /// The minimum required version (if any). + ISemanticVersion MinimumVersion { get; } } } -- cgit From 230ab1738af34c89e9c308d2ce4976d4ae8dbe70 Mon Sep 17 00:00:00 2001 From: Nicholas Johnson Date: Fri, 16 Jun 2017 03:47:36 -0700 Subject: - This adds in operators to SDate. And Tests. And a NUnit Adapter - sorry about the latter.. --- src/StardewModdingAPI/Utilities/SDate.cs | 150 +++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) (limited to 'src/StardewModdingAPI') diff --git a/src/StardewModdingAPI/Utilities/SDate.cs b/src/StardewModdingAPI/Utilities/SDate.cs index 4729bfb9..fdeffe80 100644 --- a/src/StardewModdingAPI/Utilities/SDate.cs +++ b/src/StardewModdingAPI/Utilities/SDate.cs @@ -113,6 +113,156 @@ namespace StardewModdingAPI.Utilities return new SDate(Game1.dayOfMonth, Game1.currentSeason, Game1.year); } + /********* + ** Operator methods + *********/ + + /// + /// Equality operator. Tests the date being equal to each other + /// + /// The first date being compared + /// The second date being compared + /// The equality of the dates + public static bool operator ==(SDate s1, SDate s2) + { + if (s1.Day == s2.Day && s1.Year == s2.Year && s1.Season == s2.Season) + return true; + else + return false; + } + + /// + /// Inequality operator. Tests the date being not equal to each other + /// + /// The first date being compared + /// The second date being compared + /// The inequality of the dates + public static bool operator !=(SDate s1, SDate s2) + { + if (s1.Day == s2.Day && s1.Year == s2.Year && s1.Season == s2.Season) + return false; + else + return true; + } + + /// + /// Less than operator. Tests the date being less than to each other + /// + /// The first date being compared + /// The second date being compared + /// If the dates are less than + public static bool operator >(SDate s1, SDate s2) + { + if (s1.Year > s2.Year) + return true; + else if (s1.Year == s2.Year) + { + if (s1.Season == "winter" && s2.Season != "winter") + return true; + else if (s1.Season == s2.Season && s1.Day > s2.Day) + return true; + if (s1.Season == "fall" && (s2.Season == "summer" || s2.Season == "spring")) + return true; + if (s1.Season == "summer" && s2.Season == "spring") + return true; + } + + return false; + } + + /// + /// Less or equal than operator. Tests the date being less than or equal to each other + /// + /// The first date being compared + /// The second date being compared + /// If the dates are less than or equal than + public static bool operator >=(SDate s1, SDate s2) + { + if (s1.Year > s2.Year) + return true; + else if (s1.Year == s2.Year) + { + if (s1.Season == "winter" && s2.Season != "winter") + return true; + else if (s1.Season == s2.Season && s1.Day >= s2.Day) + return true; + if (s1.Season == "fall" && (s2.Season == "summer" || s2.Season == "spring")) + return true; + if (s1.Season == "summer" && s2.Season == "spring") + return true; + } + + return false; + } + + /// + /// Greater or equal than operator. Tests the date being greater than or equal to each other + /// + /// The first date being compared + /// The second date being compared + /// If the dates are greater than or equal than + public static bool operator <=(SDate s1, SDate s2) + { + if (s1.Year < s2.Year) + return true; + else if (s1.Year == s2.Year) + { + if (s1.Season == s2.Season && s1.Day <= s2.Day) + return true; + else if (s1.Season == "spring" && s2.Season != "spring") + return true; + if (s1.Season == "summer" && (s2.Season == "fall" || s2.Season == "winter")) + return true; + if (s1.Season == "fall" && s2.Season == "winter") + return true; + } + + return false; + } + + /// + /// Greater than operator. Tests the date being greater than to each other + /// + /// The first date being compared + /// The second date being compared + /// If the dates are greater than + public static bool operator <(SDate s1, SDate s2) + { + if (s1.Year < s2.Year) + return true; + else if (s1.Year == s2.Year) + { + if (s1.Season == s2.Season && s1.Day < s2.Day) + return true; + else if (s1.Season == "spring" && s2.Season != "spring") + return true; + if (s1.Season == "summer" && (s2.Season == "fall" || s2.Season == "winter")) + return true; + if (s1.Season == "fall" && s2.Season == "winter") + return true; + } + + return false; + } + + /// + /// Overrides the equals function. + /// + /// Object being compared. + /// The equalaity of the object. + public override bool Equals(object obj) + { + return base.Equals(obj); + } + + /// + /// This returns the hashcode of the object + /// + /// The hashcode of the object. + public override int GetHashCode() + { + return base.GetHashCode(); + } /********* ** Private methods -- cgit From 0a8c07cc0773a1e3c109a3ccfa8b95896b7d75a8 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 18 Jun 2017 20:24:32 -0400 Subject: simplify date operators by making SDate.GetHashCode() return unique ordered values, expand unit tests (#307) --- src/StardewModdingAPI/Utilities/SDate.cs | 180 ++++++++++--------------------- 1 file changed, 56 insertions(+), 124 deletions(-) (limited to 'src/StardewModdingAPI') diff --git a/src/StardewModdingAPI/Utilities/SDate.cs b/src/StardewModdingAPI/Utilities/SDate.cs index fdeffe80..5f7ff030 100644 --- a/src/StardewModdingAPI/Utilities/SDate.cs +++ b/src/StardewModdingAPI/Utilities/SDate.cs @@ -13,6 +13,9 @@ namespace StardewModdingAPI.Utilities /// The internal season names in order. private readonly string[] Seasons = { "spring", "summer", "fall", "winter" }; + /// The number of seasons in a year. + private int SeasonsInYear => this.Seasons.Length; + /// The number of days in a season. private readonly int DaysInSeason = 28; @@ -77,10 +80,8 @@ namespace StardewModdingAPI.Utilities // handle season transition if (day > this.DaysInSeason || day < 1) { - // get current season index - int curSeasonIndex = Array.IndexOf(this.Seasons, this.Season); - if (curSeasonIndex == -1) - throw new InvalidOperationException($"The current season '{this.Season}' wasn't recognised."); + // get season index + int curSeasonIndex = this.GetSeasonIndex(); // get season offset int seasonOffset = day / this.DaysInSeason; @@ -94,7 +95,7 @@ namespace StardewModdingAPI.Utilities } // validate - if(year < 1) + if (year < 1) throw new ArithmeticException($"Adding {offset} days to {this} would result in invalid date {day:00} {season} {year}."); // return new date @@ -116,157 +117,88 @@ namespace StardewModdingAPI.Utilities /********* ** Operator methods *********/ - - /// - /// Equality operator. Tests the date being equal to each other - /// - /// The first date being compared - /// The second date being compared + /// Get whether one date is equal to another. + /// The base date to compare. + /// The other date to compare. /// The equality of the dates - public static bool operator ==(SDate s1, SDate s2) + public static bool operator ==(SDate date, SDate other) { - if (s1.Day == s2.Day && s1.Year == s2.Year && s1.Season == s2.Season) - return true; - else - return false; + return date?.GetHashCode() == other?.GetHashCode(); } - /// - /// Inequality operator. Tests the date being not equal to each other - /// - /// The first date being compared - /// The second date being compared - /// The inequality of the dates - public static bool operator !=(SDate s1, SDate s2) + /// Get whether one date is not equal to another. + /// The base date to compare. + /// The other date to compare. + public static bool operator !=(SDate date, SDate other) { - if (s1.Day == s2.Day && s1.Year == s2.Year && s1.Season == s2.Season) - return false; - else - return true; + return date?.GetHashCode() != other?.GetHashCode(); } - /// - /// Less than operator. Tests the date being less than to each other - /// - /// The first date being compared - /// The second date being compared - /// If the dates are less than - public static bool operator >(SDate s1, SDate s2) + /// Get whether one date is more than another. + /// The base date to compare. + /// The other date to compare. + public static bool operator >(SDate date, SDate other) { - if (s1.Year > s2.Year) - return true; - else if (s1.Year == s2.Year) - { - if (s1.Season == "winter" && s2.Season != "winter") - return true; - else if (s1.Season == s2.Season && s1.Day > s2.Day) - return true; - if (s1.Season == "fall" && (s2.Season == "summer" || s2.Season == "spring")) - return true; - if (s1.Season == "summer" && s2.Season == "spring") - return true; - } - - return false; + return date?.GetHashCode() > other?.GetHashCode(); } - /// - /// Less or equal than operator. Tests the date being less than or equal to each other - /// - /// The first date being compared - /// The second date being compared - /// If the dates are less than or equal than - public static bool operator >=(SDate s1, SDate s2) + /// Get whether one date is more than or equal to another. + /// The base date to compare. + /// The other date to compare. + public static bool operator >=(SDate date, SDate other) { - if (s1.Year > s2.Year) - return true; - else if (s1.Year == s2.Year) - { - if (s1.Season == "winter" && s2.Season != "winter") - return true; - else if (s1.Season == s2.Season && s1.Day >= s2.Day) - return true; - if (s1.Season == "fall" && (s2.Season == "summer" || s2.Season == "spring")) - return true; - if (s1.Season == "summer" && s2.Season == "spring") - return true; - } - - return false; + return date?.GetHashCode() >= other?.GetHashCode(); } - /// - /// Greater or equal than operator. Tests the date being greater than or equal to each other - /// - /// The first date being compared - /// The second date being compared - /// If the dates are greater than or equal than - public static bool operator <=(SDate s1, SDate s2) + /// Get whether one date is less than or equal to another. + /// The base date to compare. + /// The other date to compare. + public static bool operator <=(SDate date, SDate other) { - if (s1.Year < s2.Year) - return true; - else if (s1.Year == s2.Year) - { - if (s1.Season == s2.Season && s1.Day <= s2.Day) - return true; - else if (s1.Season == "spring" && s2.Season != "spring") - return true; - if (s1.Season == "summer" && (s2.Season == "fall" || s2.Season == "winter")) - return true; - if (s1.Season == "fall" && s2.Season == "winter") - return true; - } - - return false; + return date?.GetHashCode() <= other?.GetHashCode(); } - /// - /// Greater than operator. Tests the date being greater than to each other - /// - /// The first date being compared - /// The second date being compared - /// If the dates are greater than - public static bool operator <(SDate s1, SDate s2) + /// Get whether one date is less than another. + /// The base date to compare. + /// The other date to compare. + public static bool operator <(SDate date, SDate other) { - if (s1.Year < s2.Year) - return true; - else if (s1.Year == s2.Year) - { - if (s1.Season == s2.Season && s1.Day < s2.Day) - return true; - else if (s1.Season == "spring" && s2.Season != "spring") - return true; - if (s1.Season == "summer" && (s2.Season == "fall" || s2.Season == "winter")) - return true; - if (s1.Season == "fall" && s2.Season == "winter") - return true; - } - - return false; + return date?.GetHashCode() < other?.GetHashCode(); } - /// - /// Overrides the equals function. - /// + /// Overrides the equals function. /// Object being compared. /// The equalaity of the object. public override bool Equals(object obj) { - return base.Equals(obj); + return obj is SDate other && this == other; } - /// - /// This returns the hashcode of the object - /// - /// The hashcode of the object. + /// Get a hash code which uniquely identifies a date. public override int GetHashCode() { - return base.GetHashCode(); + // return the number of days since 01 spring Y1 + int yearIndex = this.Year - 1; + return + yearIndex * this.DaysInSeason * this.SeasonsInYear + + this.GetSeasonIndex() * this.DaysInSeason + + this.Day; } + /********* ** Private methods *********/ + /// Get the current season index. + /// The current season wasn't recognised. + private int GetSeasonIndex() + { + int index = Array.IndexOf(this.Seasons, this.Season); + if (index == -1) + throw new InvalidOperationException($"The current season '{this.Season}' wasn't recognised."); + return index; + } + /// Get the real index in an array which should be treated as a two-way loop. /// The index in the looped array. /// The number of elements in the array. -- cgit From 3e50c90230bf4f7aa4efb69b3db47dddd1e43750 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 18 Jun 2017 20:55:12 -0400 Subject: add IEquatable interface to SDate (#307) --- src/StardewModdingAPI/Utilities/SDate.cs | 61 ++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 26 deletions(-) (limited to 'src/StardewModdingAPI') diff --git a/src/StardewModdingAPI/Utilities/SDate.cs b/src/StardewModdingAPI/Utilities/SDate.cs index 5f7ff030..e0613491 100644 --- a/src/StardewModdingAPI/Utilities/SDate.cs +++ b/src/StardewModdingAPI/Utilities/SDate.cs @@ -5,7 +5,7 @@ using StardewValley; namespace StardewModdingAPI.Utilities { /// Represents a Stardew Valley date. - public class SDate + public class SDate : IEquatable { /********* ** Properties @@ -66,6 +66,12 @@ namespace StardewModdingAPI.Utilities this.Year = year; } + /// Get the current in-game date. + public static SDate Now() + { + return new SDate(Game1.dayOfMonth, Game1.currentSeason, Game1.year); + } + /// Get a new date with the given number of days added. /// The number of days to add. /// Returns the resulting date. @@ -108,15 +114,37 @@ namespace StardewModdingAPI.Utilities return $"{this.Day:00} {this.Season} Y{this.Year}"; } - /// Get the current in-game date. - public static SDate Now() + /**** + ** IEquatable + ****/ + /// Get whether this instance is equal to another. + /// The other value to compare. + public bool Equals(SDate other) { - return new SDate(Game1.dayOfMonth, Game1.currentSeason, Game1.year); + return this == other; } - /********* - ** Operator methods - *********/ + /// Get whether this instance is equal to another. + /// The other value to compare. + public override bool Equals(object obj) + { + return obj is SDate other && this == other; + } + + /// Get a hash code which uniquely identifies a date. + public override int GetHashCode() + { + // return the number of days since 01 spring Y1 + int yearIndex = this.Year - 1; + return + yearIndex * this.DaysInSeason * this.SeasonsInYear + + this.GetSeasonIndex() * this.DaysInSeason + + this.Day; + } + + /**** + ** Operators + ****/ /// Get whether one date is equal to another. /// The base date to compare. /// The other date to compare. @@ -166,25 +194,6 @@ namespace StardewModdingAPI.Utilities return date?.GetHashCode() < other?.GetHashCode(); } - /// Overrides the equals function. - /// Object being compared. - /// The equalaity of the object. - public override bool Equals(object obj) - { - return obj is SDate other && this == other; - } - - /// Get a hash code which uniquely identifies a date. - public override int GetHashCode() - { - // return the number of days since 01 spring Y1 - int yearIndex = this.Year - 1; - return - yearIndex * this.DaysInSeason * this.SeasonsInYear - + this.GetSeasonIndex() * this.DaysInSeason - + this.Day; - } - /********* ** Private methods -- cgit From b46776a4fbabe765b81751f8c4984cdd8a207419 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 18 Jun 2017 22:08:56 -0400 Subject: enable string versions in manifest.json (#308) --- .../Serialisation/ManifestFieldConverter.cs | 25 ++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) (limited to 'src/StardewModdingAPI') diff --git a/src/StardewModdingAPI/Framework/Serialisation/ManifestFieldConverter.cs b/src/StardewModdingAPI/Framework/Serialisation/ManifestFieldConverter.cs index 7acb5fd0..7a59f134 100644 --- a/src/StardewModdingAPI/Framework/Serialisation/ManifestFieldConverter.cs +++ b/src/StardewModdingAPI/Framework/Serialisation/ManifestFieldConverter.cs @@ -36,12 +36,25 @@ namespace StardewModdingAPI.Framework.Serialisation // semantic version if (objectType == typeof(ISemanticVersion)) { - JObject obj = JObject.Load(reader); - int major = obj.Value(nameof(ISemanticVersion.MajorVersion)); - int minor = obj.Value(nameof(ISemanticVersion.MinorVersion)); - int patch = obj.Value(nameof(ISemanticVersion.PatchVersion)); - string build = obj.Value(nameof(ISemanticVersion.Build)); - return new SemanticVersion(major, minor, patch, build); + JToken token = JToken.Load(reader); + switch (token.Type) + { + case JTokenType.Object: + { + JObject obj = (JObject)token; + int major = obj.Value(nameof(ISemanticVersion.MajorVersion)); + int minor = obj.Value(nameof(ISemanticVersion.MinorVersion)); + int patch = obj.Value(nameof(ISemanticVersion.PatchVersion)); + string build = obj.Value(nameof(ISemanticVersion.Build)); + return new SemanticVersion(major, minor, patch, build); + } + + case JTokenType.String: + return new SemanticVersion(token.Value()); + + default: + throw new FormatException($"Can't parse {token.Type} token as a semantic version, must be an object or string."); + } } // manifest dependency -- cgit From fb8fefea00aacd603e68fbdbaecd27e4c451cc82 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 18 Jun 2017 22:11:48 -0400 Subject: show friendly error when parsing a manifest version fails (#308) --- .../Framework/Exceptions/SParseException.cs | 17 +++++++++++++++++ .../Framework/ModLoading/ModResolver.cs | 5 +++++ .../Framework/Serialisation/ManifestFieldConverter.cs | 12 ++++++++++-- src/StardewModdingAPI/StardewModdingAPI.csproj | 1 + 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 src/StardewModdingAPI/Framework/Exceptions/SParseException.cs (limited to 'src/StardewModdingAPI') diff --git a/src/StardewModdingAPI/Framework/Exceptions/SParseException.cs b/src/StardewModdingAPI/Framework/Exceptions/SParseException.cs new file mode 100644 index 00000000..f7133ee7 --- /dev/null +++ b/src/StardewModdingAPI/Framework/Exceptions/SParseException.cs @@ -0,0 +1,17 @@ +using System; + +namespace StardewModdingAPI.Framework.Exceptions +{ + /// A format exception which provides a user-facing error message. + internal class SParseException : FormatException + { + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The error message. + /// The underlying exception, if any. + public SParseException(string message, Exception ex = null) + : base(message, ex) { } + } +} diff --git a/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs b/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs index dc140483..045b175c 100644 --- a/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs +++ b/src/StardewModdingAPI/Framework/ModLoading/ModResolver.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Models; using StardewModdingAPI.Framework.Serialisation; @@ -45,6 +46,10 @@ namespace StardewModdingAPI.Framework.ModLoading else if (string.IsNullOrWhiteSpace(manifest.EntryDll)) error = "its manifest doesn't set an entry DLL."; } + catch (SParseException ex) + { + error = $"parsing its manifest failed: {ex.Message}"; + } catch (Exception ex) { error = $"parsing its manifest failed:\n{ex.GetLogSummary()}"; diff --git a/src/StardewModdingAPI/Framework/Serialisation/ManifestFieldConverter.cs b/src/StardewModdingAPI/Framework/Serialisation/ManifestFieldConverter.cs index 7a59f134..e6d62d50 100644 --- a/src/StardewModdingAPI/Framework/Serialisation/ManifestFieldConverter.cs +++ b/src/StardewModdingAPI/Framework/Serialisation/ManifestFieldConverter.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Models; namespace StardewModdingAPI.Framework.Serialisation @@ -50,10 +51,17 @@ namespace StardewModdingAPI.Framework.Serialisation } case JTokenType.String: - return new SemanticVersion(token.Value()); + { + string str = token.Value(); + if (string.IsNullOrWhiteSpace(str)) + return null; + if (!SemanticVersion.TryParse(str, out ISemanticVersion version)) + throw new SParseException($"Can't parse semantic version from invalid value '{str}', should be formatted like 1.2, 1.2.30, or 1.2.30-beta."); + return version; + } default: - throw new FormatException($"Can't parse {token.Type} token as a semantic version, must be an object or string."); + throw new SParseException($"Can't parse semantic version from {token.Type}, must be an object or string."); } } diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 465a5ea7..77d3b12b 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -151,6 +151,7 @@ + -- cgit From a011c28d4000f469327ac85f79b4801a432a498b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 19 Jun 2017 01:05:43 -0400 Subject: make version parsing stricter, add unit tests for parsing (#309) --- src/StardewModdingAPI/SemanticVersion.cs | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) (limited to 'src/StardewModdingAPI') diff --git a/src/StardewModdingAPI/SemanticVersion.cs b/src/StardewModdingAPI/SemanticVersion.cs index a2adb657..4b27c819 100644 --- a/src/StardewModdingAPI/SemanticVersion.cs +++ b/src/StardewModdingAPI/SemanticVersion.cs @@ -10,8 +10,14 @@ namespace StardewModdingAPI ** 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); + /// + /// This pattern is derived from the BNF documentation in the semver repo, + /// with three important deviations intended to support Stardew Valley mod conventions: + /// - allows short-form "x.y" versions; + /// - 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); /********* @@ -48,17 +54,22 @@ namespace StardewModdingAPI /// Construct an instance. /// The semantic version string. + /// The is null. /// The is not a valid semantic version. public SemanticVersion(string version) { - var match = SemanticVersion.Regex.Match(version); + // parse + if (version == null) + throw new ArgumentNullException(nameof(version), "The input version string can't be null."); + var match = SemanticVersion.Regex.Match(version.Trim()); if (!match.Success) - throw new FormatException($"The input '{version}' is not a valid semantic version."); + throw new FormatException($"The input '{version}' isn't a valid semantic version."); + // initialise 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 ? this.GetNormalisedTag(match.Groups["build"].Value) : null; + this.Build = match.Groups["prerelease"].Success ? this.GetNormalisedTag(match.Groups["prerelease"].Value) : null; } /// Get an integer indicating whether this version precedes (less than 0), supercedes (more than 0), or is equivalent to (0) the specified version. @@ -93,8 +104,8 @@ namespace StardewModdingAPI return curOlder; // compare two pre-release tag values - string[] curParts = this.Build.Split('.'); - string[] otherParts = other.Build.Split('.'); + 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 @@ -200,6 +211,7 @@ namespace StardewModdingAPI } } + /********* ** Private methods *********/ @@ -207,11 +219,9 @@ namespace StardewModdingAPI /// The tag to normalise. private string GetNormalisedTag(string tag) { - tag = tag?.Trim().Trim('-', '.'); - if (string.IsNullOrWhiteSpace(tag)) + tag = tag?.Trim(); + if (string.IsNullOrWhiteSpace(tag) || tag == "0") // '0' from incorrect examples in old SMAPI documentation return null; - if (tag == "0") - return null; // from incorrect examples in old SMAPI documentation return tag; } } -- cgit From 640a523eb4a63aa078db15b63ac6e01c6c15d53c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 19 Jun 2017 02:12:18 -0400 Subject: when the ObjectInformation.xnb file is broken, print one error instead of a warning flood --- src/StardewModdingAPI/Program.cs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) (limited to 'src/StardewModdingAPI') diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index 71f09f5c..da0c5bca 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -355,7 +355,7 @@ namespace StardewModdingAPI // validate XNB integrity if (!this.ValidateContentIntegrity()) - this.Monitor.Log("SMAPI found problems in the game's XNB files which may cause errors or crashes while you're playing. Consider uninstalling XNB mods or reinstalling the game.", LogLevel.Warn); + this.Monitor.Log("SMAPI found problems in your game's content files which are likely to cause errors or crashes. Consider uninstalling XNB mods or reinstalling the game.", LogLevel.Error); // load mods int modsLoaded; @@ -486,17 +486,18 @@ namespace StardewModdingAPI this.Monitor.Log("Detecting common issues..."); bool issuesFound = false; - - // object format (commonly broken by outdated files) + // object format (commonly broken by outdated mods) { - void LogIssue(int id, string issue) => this.Monitor.Log($"Detected issue: item #{id} in Content\\Data\\ObjectInformation is invalid ({issue}).", LogLevel.Warn); + // detect issues + bool hasObjectIssues = false; + void LogIssue(int id, string issue) => this.Monitor.Log($@"Detected issue: item #{id} in Content\Data\ObjectInformation.xnb is invalid ({issue}).", LogLevel.Trace); foreach (KeyValuePair entry in Game1.objectInformation) { // must not be empty if (string.IsNullOrWhiteSpace(entry.Value)) { LogIssue(entry.Key, "entry is empty"); - issuesFound = true; + hasObjectIssues = true; continue; } @@ -505,7 +506,7 @@ namespace StardewModdingAPI if (fields.Length < SObject.objectInfoDescriptionIndex + 1) { LogIssue(entry.Key, "too few fields for an object"); - issuesFound = true; + hasObjectIssues = true; continue; } @@ -516,11 +517,18 @@ namespace StardewModdingAPI if (fields.Length < SObject.objectInfoBuffDurationIndex + 1) { LogIssue(entry.Key, "too few fields for a cooking item"); - issuesFound = true; + hasObjectIssues = true; } break; } } + + // log error + if (hasObjectIssues) + { + issuesFound = true; + this.Monitor.Log(@"Your Content\Data\ObjectInformation.xnb file seems to be broken or outdated.", LogLevel.Warn); + } } return !issuesFound; -- cgit From 8d7b5b372657c0f96196cb2a902b2bdcce184fe4 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 26 Jun 2017 11:01:47 -0400 Subject: improve logging when SMAPI loads mods --- .../Framework/ModLoading/AssemblyLoader.cs | 27 +++++++++++++--------- src/StardewModdingAPI/Program.cs | 11 +++++++-- 2 files changed, 25 insertions(+), 13 deletions(-) (limited to 'src/StardewModdingAPI') diff --git a/src/StardewModdingAPI/Framework/ModLoading/AssemblyLoader.cs b/src/StardewModdingAPI/Framework/ModLoading/AssemblyLoader.cs index 42bd7bfb..406d49e1 100644 --- a/src/StardewModdingAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/StardewModdingAPI/Framework/ModLoading/AssemblyLoader.cs @@ -71,13 +71,15 @@ namespace StardewModdingAPI.Framework.ModLoading } // rewrite & load assemblies in leaf-to-root order + bool oneAssembly = assemblies.Length == 1; Assembly lastAssembly = null; foreach (AssemblyParseResult assembly in assemblies) { - bool changed = this.RewriteAssembly(assembly.Definition, assumeCompatible); + bool changed = this.RewriteAss