From 57d20614b8dd3a6e468e81dafb611064119087ad Mon Sep 17 00:00:00 2001 From: Chase W Date: Sat, 3 Jun 2017 10:25:49 -0400 Subject: Add player_addwallpaper and player_addflooring --- src/TrainerMod/TrainerMod.cs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'src') diff --git a/src/TrainerMod/TrainerMod.cs b/src/TrainerMod/TrainerMod.cs index 9a3a8d0b..0cafd51f 100644 --- a/src/TrainerMod/TrainerMod.cs +++ b/src/TrainerMod/TrainerMod.cs @@ -99,6 +99,8 @@ namespace TrainerMod .Add("player_additem", $"Gives the player an item.\n\nUsage: player_additem [count] [quality]\n- item: the item ID (use the 'list_items' command to see a list).\n- count (optional): how many of the item to give.\n- quality (optional): one of {Object.lowQuality} (normal), {Object.medQuality} (silver), {Object.highQuality} (gold), or {Object.bestQuality} (iridium).", this.HandleCommand) .Add("player_addweapon", "Gives the player a weapon.\n\nUsage: player_addweapon \n- item: the weapon ID (use the 'list_items' command to see a list).", this.HandleCommand) .Add("player_addring", "Gives the player a ring.\n\nUsage: player_addring \n- item: the ring ID (use the 'list_items' command to see a list).", this.HandleCommand) + .Add("player_addwallpaper", "Gives the player a wallpaper.\n\nUsage: player_addwallpaper \n- wallpaper: the wallpaper ID (ranges from 0 to 111).", this.HandleCommand) + .Add("player_addflooring", "Gives the player a flooring.\n\nUsage: player_addflooring \n- flooring: the flooring ID (ranges from 0 to 39).", this.HandleCommand) .Add("list_items", "Lists and searches items in the game data.\n\nUsage: list_items [search]\n- search (optional): an arbitrary search string to filter by.", this.HandleCommand) @@ -709,6 +711,31 @@ namespace TrainerMod this.LogArgumentsInvalid(command); break; + case "player_addwallpaper": + case "player_addflooring": + if (args.Any()) + { + string type = command.Substring(10); + int wallpaperID; + if (int.TryParse(args[0], out wallpaperID)) + { + int upperID = type == "wallpaper" ? 111 : 39; + if (wallpaperID < 0 || wallpaperID > upperID) + this.Monitor.Log($"There is no such {type} ID (must be between 0 and {upperID}).", LogLevel.Error); + else + { + Wallpaper wallpaper = new Wallpaper(wallpaperID, type == "flooring" ); + Game1.player.addItemByMenuIfNecessary(wallpaper); + this.Monitor.Log($"OK, added {type} {wallpaperID} to your inventory.", LogLevel.Info); + } + } + else + this.Monitor.Log($"<{type}> is invalid", LogLevel.Error); + } + else + this.LogArgumentsInvalid(command); + break; + case "list_items": { var matches = this.GetItems(args).ToArray(); -- cgit 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.Tests/SDateTests.cs | 112 ++++++++++++++++++ .../StardewModdingAPI.Tests.csproj | 1 + src/StardewModdingAPI/StardewModdingAPI.csproj | 3 +- src/StardewModdingAPI/Utilities/SDate.cs | 131 +++++++++++++++++++++ 4 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 src/StardewModdingAPI.Tests/SDateTests.cs create mode 100644 src/StardewModdingAPI/Utilities/SDate.cs (limited to 'src') diff --git a/src/StardewModdingAPI.Tests/SDateTests.cs b/src/StardewModdingAPI.Tests/SDateTests.cs new file mode 100644 index 00000000..a4c65a98 --- /dev/null +++ b/src/StardewModdingAPI.Tests/SDateTests.cs @@ -0,0 +1,112 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text.RegularExpressions; +using NUnit.Framework; +using StardewModdingAPI.Utilities; + +namespace StardewModdingAPI.Tests +{ + /// Unit tests for . + [TestFixture] + internal class SDateTests + { + /********* + ** Properties + *********/ + /// All valid seasons. + private static string[] ValidSeasons = { "spring", "summer", "fall", "winter" }; + + /// All valid days of a month. + private static int[] ValidDays = Enumerable.Range(1, 28).ToArray(); + + + /********* + ** Unit tests + *********/ + /**** + ** Constructor + ****/ + [Test(Description = "Assert that the constructor sets the expected values for all valid dates.")] + public void Constructor_SetsExpectedValues([ValueSource(nameof(SDateTests.ValidSeasons))] string season, [ValueSource(nameof(SDateTests.ValidDays))] int day, [Values(1, 2, 100)] int year) + { + // act + SDate date = new SDate(day, season, year); + + // assert + Assert.AreEqual(day, date.Day); + Assert.AreEqual(season, date.Season); + Assert.AreEqual(year, date.Year); + } + + [Test(Description = "Assert that the constructor throws an exception if the values are invalid.")] + [TestCase(01, "Spring", 1)] // seasons are case-sensitive + [TestCase(01, "springs", 1)] // invalid season name + [TestCase(-1, "spring", 1)] // day < 0 + [TestCase(29, "spring", 1)] // day > 28 + [TestCase(01, "spring", -1)] // year < 1 + [TestCase(01, "spring", 0)] // year < 1 + [SuppressMessage("ReSharper", "AssignmentIsFullyDiscarded", Justification = "Deliberate for unit test.")] + public void Constructor_RejectsInvalidValues(int day, string season, int year) + { + // act & assert + Assert.Throws(() => _ = new SDate(day, season, year), "Constructing the invalid date didn't throw the expected exception."); + } + + /**** + ** ToString + ****/ + [Test(Description = "Assert that ToString returns the expected string.")] + [TestCase("14 spring Y1", ExpectedResult = "14 spring Y1")] + [TestCase("01 summer Y16", ExpectedResult = "01 summer Y16")] + [TestCase("28 fall Y10", ExpectedResult = "28 fall Y10")] + [TestCase("01 winter Y1", ExpectedResult = "01 winter Y1")] + public string ToString(string dateStr) + { + return this.ParseDate(dateStr).ToString(); + } + + /**** + ** AddDays + ****/ + [Test(Description = "Assert that AddDays returns the expected date.")] + [TestCase("01 spring Y1", 15, ExpectedResult = "16 spring Y1")] // day transition + [TestCase("01 spring Y1", 28, ExpectedResult = "01 summer Y1")] // season transition + [TestCase("01 spring Y1", 28 * 4, ExpectedResult = "01 spring Y2")] // year transition + [TestCase("01 spring Y1", 28 * 7 + 17, ExpectedResult = "18 winter Y2")] // year transition + [TestCase("15 spring Y1", -14, ExpectedResult = "01 spring Y1")] // negative day transition + [TestCase("15 summer Y1", -28, ExpectedResult = "15 spring Y1")] // negative season transition + [TestCase("15 summer Y2", -28 * 4, ExpectedResult = "15 summer Y1")] // negative year transition + [TestCase("01 spring Y3", -(28 * 7 + 17), ExpectedResult = "12 spring Y1")] // negative year transition + public string AddDays(string dateStr, int addDays) + { + return this.ParseDate(dateStr).AddDays(addDays).ToString(); + } + + + /********* + ** Private methods + *********/ + /// Convert a string date into a game date, to make unit tests easier to read. + /// The date string like "dd MMMM yy". + private SDate ParseDate(string dateStr) + { + void Fail(string reason) => throw new AssertionException($"Couldn't parse date '{dateStr}' because {reason}."); + + // parse + Match match = Regex.Match(dateStr, @"^(?\d+) (?\w+) Y(?\d+)$"); + if (!match.Success) + Fail("it doesn't match expected pattern (should be like 28 spring Y1)"); + + // extract parts + string season = match.Groups["season"].Value; + if (!int.TryParse(match.Groups["day"].Value, out int day)) + Fail($"'{match.Groups["day"].Value}' couldn't be parsed as a day."); + if (!int.TryParse(match.Groups["year"].Value, out int year)) + Fail($"'{match.Groups["year"].Value}' couldn't be parsed as a year."); + + // build date + return new SDate(day, season, year); + } + } +} diff --git a/src/StardewModdingAPI.Tests/StardewModdingAPI.Tests.csproj b/src/StardewModdingAPI.Tests/StardewModdingAPI.Tests.csproj index 3818ec9c..3ddb1326 100644 --- a/src/StardewModdingAPI.Tests/StardewModdingAPI.Tests.csproj +++ b/src/StardewModdingAPI.Tests/StardewModdingAPI.Tests.csproj @@ -48,6 +48,7 @@ Properties\GlobalAssemblyInfo.cs + 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 --- src/StardewModdingAPI.Tests/ModResolverTests.cs | 6 +++--- .../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 + 7 files changed, 59 insertions(+), 17 deletions(-) create mode 100644 src/StardewModdingAPI/Framework/Models/DisabledMod.cs (limited to 'src') diff --git a/src/StardewModdingAPI.Tests/ModResolverTests.cs b/src/StardewModdingAPI.Tests/ModResolverTests.cs index 23aeba64..a9df2056 100644 --- a/src/StardewModdingAPI.Tests/ModResolverTests.cs +++ b/src/StardewModdingAPI.Tests/ModResolverTests.cs @@ -31,7 +31,7 @@ namespace StardewModdingAPI.Tests Directory.CreateDirectory(rootFolder); // act - IModMetadata[] mods = new ModResolver().ReadManifests(rootFolder, new JsonHelper(), new ModCompatibility[0]).ToArray(); + IModMetadata[] mods = new ModResolver().ReadManifests(rootFolder, new JsonHelper(), new ModCompatibility[0], new DisabledMod[0]).ToArray(); // assert Assert.AreEqual(0, mods.Length, 0, $"Expected to find zero manifests, found {mods.Length} instead."); @@ -46,7 +46,7 @@ namespace StardewModdingAPI.Tests Directory.CreateDirectory(modFolder); // act - IModMetadata[] mods = new ModResolver().ReadManifests(rootFolder, new JsonHelper(), new ModCompatibility[0]).ToArray(); + IModMetadata[] mods = new ModResolver().ReadManifests(rootFolder, new JsonHelper(), new ModCompatibility[0], new DisabledMod[0]).ToArray(); IModMetadata mod = mods.FirstOrDefault(); // assert @@ -85,7 +85,7 @@ namespace StardewModdingAPI.Tests File.WriteAllText(filename, JsonConvert.SerializeObject(original)); // act - IModMetadata[] mods = new ModResolver().ReadManifests(rootFolder, new JsonHelper(), new ModCompatibility[0]).ToArray(); + IModMetadata[] mods = new ModResolver().ReadManifests(rootFolder, new JsonHelper(), new ModCompatibility[0], new DisabledMod[0]).ToArray(); IModMetadata mod = mods.FirstOrDefault(); // assert 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') 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.Installer/InteractiveInstaller.cs | 1 + src/StardewModdingAPI/StardewModdingAPI.csproj | 4 ++++ src/StardewModdingAPI/packages.config | 1 + src/prepare-install-package.targets | 2 ++ 4 files changed, 8 insertions(+) (limited to 'src') diff --git a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs index efad0a3e..78d3d10e 100644 --- a/src/StardewModdingAPI.Installer/InteractiveInstaller.cs +++ b/src/StardewModdingAPI.Installer/InteractiveInstaller.cs @@ -82,6 +82,7 @@ namespace StardewModdingApi.Installer yield return GetInstallPath("StardewModdingAPI.config.json"); yield return GetInstallPath("StardewModdingAPI.data.json"); yield return GetInstallPath("StardewModdingAPI.AssemblyRewriters.dll"); + yield return GetInstallPath("System.ValueTuple.dll"); yield return GetInstallPath("steam_appid.txt"); // Linux/Mac only 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 diff --git a/src/prepare-install-package.targets b/src/prepare-install-package.targets index f2a2b23c..df8bb100 100644 --- a/src/prepare-install-package.targets +++ b/src/prepare-install-package.targets @@ -31,6 +31,7 @@ + @@ -43,6 +44,7 @@ + -- 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) --- src/StardewModdingAPI.Tests/ModResolverTests.cs | 136 +++++++++++++++------ .../Framework/ModLoading/ModResolver.cs | 44 ++++--- .../Framework/Models/ManifestDependency.cs | 9 +- .../Serialisation/ManifestFieldConverter.cs | 3 +- src/StardewModdingAPI/IManifestDependency.cs | 3 + 5 files changed, 138 insertions(+), 57 deletions(-) (limited to 'src') diff --git a/src/StardewModdingAPI.Tests/ModResolverTests.cs b/src/StardewModdingAPI.Tests/ModResolverTests.cs index a9df2056..4afba162 100644 --- a/src/StardewModdingAPI.Tests/ModResolverTests.cs +++ b/src/StardewModdingAPI.Tests/ModResolverTests.cs @@ -160,7 +160,7 @@ namespace StardewModdingAPI.Tests Mock mock = new Mock(MockBehavior.Strict); mock.Setup(p => p.Status).Returns(ModMetadataStatus.Found); mock.Setup(p => p.Compatibility).Returns(() => null); - mock.Setup(p => p.Manifest).Returns(this.GetRandomManifest(m => m.MinimumApiVersion = "1.1")); + mock.Setup(p => p.Manifest).Returns(this.GetManifest(m => m.MinimumApiVersion = "1.1")); mock.Setup(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny())).Returns(() => mock.Object); // act @@ -177,7 +177,7 @@ namespace StardewModdingAPI.Tests Mock mock = new Mock(MockBehavior.Strict); mock.Setup(p => p.Status).Returns(ModMetadataStatus.Found); mock.Setup(p => p.Compatibility).Returns(() => null); - mock.Setup(p => p.Manifest).Returns(this.GetRandomManifest()); + mock.Setup(p => p.Manifest).Returns(this.GetManifest()); mock.Setup(p => p.DirectoryPath).Returns(Path.GetTempPath()); mock.Setup(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny())).Returns(() => mock.Object); @@ -192,7 +192,7 @@ namespace StardewModdingAPI.Tests public void ValidateManifests_Valid_Passes() { // set up manifest - IManifest manifest = this.GetRandomManifest(); + IManifest manifest = this.GetManifest(); // create DLL string modFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")); @@ -231,9 +231,9 @@ namespace StardewModdingAPI.Tests { // arrange // A B C - Mock modA = this.GetMetadataForDependencyTest("Mod A"); - Mock modB = this.GetMetadataForDependencyTest("Mod B"); - Mock modC = this.GetMetadataForDependencyTest("Mod C"); + Mock modA = this.GetMetadata("Mod A"); + Mock modB = this.GetMetadata("Mod B"); + Mock modC = this.GetMetadata("Mod C"); // act IModMetadata[] mods = new ModResolver().ProcessDependencies(new[] { modA.Object, modB.Object, modC.Object }).ToArray(); @@ -267,9 +267,9 @@ namespace StardewModdingAPI.Tests // ▲ ▲ // │ │ // └─ C ─┘ - Mock modA = this.GetMetadataForDependencyTest("Mod A"); - Mock modB = this.GetMetadataForDependencyTest("Mod B", dependencies: new[] { "Mod A" }); - Mock modC = this.GetMetadataForDependencyTest("Mod C", dependencies: new[] { "Mod A", "Mod B" }); + Mock modA = this.GetMetadata("Mod A"); + Mock modB = this.GetMetadata("Mod B", dependencies: new[] { "Mod A" }); + Mock modC = this.GetMetadata("Mod C", dependencies: new[] { "Mod A", "Mod B" }); // act IModMetadata[] mods = new ModResolver().ProcessDependencies(new[] { modC.Object, modA.Object, modB.Object }).ToArray(); @@ -286,10 +286,10 @@ namespace StardewModdingAPI.Tests { // arrange // A ◀── B ◀── C ◀── D - Mock modA = this.GetMetadataForDependencyTest("Mod A"); - Mock modB = this.GetMetadataForDependencyTest("Mod B", dependencies: new[] { "Mod A" }); - Mock modC = this.GetMetadataForDependencyTest("Mod C", dependencies: new[] { "Mod B" }); - Mock modD = this.GetMetadataForDependencyTest("Mod D", dependencies: new[] { "Mod C" }); + Mock modA = this.GetMetadata("Mod A"); + Mock modB = this.GetMetadata("Mod B", dependencies: new[] { "Mod A" }); + Mock modC = this.GetMetadata("Mod C", dependencies: new[] { "Mod B" }); + Mock modD = this.GetMetadata("Mod D", dependencies: new[] { "Mod C" }); // act IModMetadata[] mods = new ModResolver().ProcessDependencies(new[] { modC.Object, modA.Object, modB.Object, modD.Object }).ToArray(); @@ -310,12 +310,12 @@ namespace StardewModdingAPI.Tests // ▲ ▲ // │ │ // E ◀── F - Mock modA = this.GetMetadataForDependencyTest("Mod A"); - Mock modB = this.GetMetadataForDependencyTest("Mod B", dependencies: new[] { "Mod A" }); - Mock modC = this.GetMetadataForDependencyTest("Mod C", dependencies: new[] { "Mod B" }); - Mock modD = this.GetMetadataForDependencyTest("Mod D", dependencies: new[] { "Mod C" }); - Mock modE = this.GetMetadataForDependencyTest("Mod E", dependencies: new[] { "Mod B" }); - Mock modF = this.GetMetadataForDependencyTest("Mod F", dependencies: new[] { "Mod C", "Mod E" }); + Mock modA = this.GetMetadata("Mod A"); + Mock modB = this.GetMetadata("Mod B", dependencies: new[] { "Mod A" }); + Mock modC = this.GetMetadata("Mod C", dependencies: new[] { "Mod B" }); + Mock modD = this.GetMetadata("Mod D", dependencies: new[] { "Mod C" }); + Mock modE = this.GetMetadata("Mod E", dependencies: new[] { "Mod B" }); + Mock modF = this.GetMetadata("Mod F", dependencies: new[] { "Mod C", "Mod E" }); // act IModMetadata[] mods = new ModResolver().ProcessDependencies(new[] { modC.Object, modA.Object, modB.Object, modD.Object, modF.Object, modE.Object }).ToArray(); @@ -338,11 +338,11 @@ namespace StardewModdingAPI.Tests // ▲ │ // │ ▼ // └──── E - Mock modA = this.GetMetadataForDependencyTest("Mod A"); - Mock modB = this.GetMetadataForDependencyTest("Mod B", dependencies: new[] { "Mod A" }); - Mock modC = this.GetMetadataForDependencyTest("Mod C", dependencies: new[] { "Mod B", "Mod D" }, allowStatusChange: true); - Mock modD = this.GetMetadataForDependencyTest("Mod D", dependencies: new[] { "Mod E" }, allowStatusChange: true); - Mock modE = this.GetMetadataForDependencyTest("Mod E", dependencies: new[] { "Mod C" }, allowStatusChange: true); + Mock modA = this.GetMetadata("Mod A"); + Mock modB = this.GetMetadata("Mod B", dependencies: new[] { "Mod A" }); + Mock modC = this.GetMetadata("Mod C", dependencies: new[] { "Mod B", "Mod D" }, allowStatusChange: true); + Mock modD = this.GetMetadata("Mod D", dependencies: new[] { "Mod E" }, allowStatusChange: true); + Mock modE = this.GetMetadata("Mod E", dependencies: new[] { "Mod C" }, allowStatusChange: true); // act IModMetadata[] mods = new ModResolver().ProcessDependencies(new[] { modC.Object, modA.Object, modB.Object, modD.Object, modE.Object }).ToArray(); @@ -361,9 +361,9 @@ namespace StardewModdingAPI.Tests { // arrange // A ◀── B ◀── C D (failed) - Mock modA = this.GetMetadataForDependencyTest("Mod A"); - Mock modB = this.GetMetadataForDependencyTest("Mod B", dependencies: new[] { "Mod A" }); - Mock modC = this.GetMetadataForDependencyTest("Mod C", dependencies: new[] { "Mod B" }, allowStatusChange: true); + Mock modA = this.GetMetadata("Mod A"); + Mock modB = this.GetMetadata("Mod B", dependencies: new[] { "Mod A" }); + Mock modC = this.GetMetadata("Mod C", dependencies: new[] { "Mod B" }, allowStatusChange: true); Mock modD = new Mock(MockBehavior.Strict); modD.Setup(p => p.Manifest).Returns(null); modD.Setup(p => p.Status).Returns(ModMetadataStatus.Failed); @@ -378,13 +378,47 @@ namespace StardewModdingAPI.Tests Assert.AreSame(modB.Object, mods[2], "The load order is incorrect: mod B should be third since it needs mod A, and is needed by mod C."); Assert.AreSame(modC.Object, mods[3], "The load order is incorrect: mod C should be fourth since it needs mod B, and is needed by mod D."); } + + [Test(Description = "Assert that dependencies are failed if they don't meet the minimum version.")] + public void ProcessDependencies_WithMinVersions_FailsIfNotMet() + { + // arrange + // A 1.0 ◀── B (need A 1.1) + Mock modA = this.GetMetadata(this.GetManifest("Mod A", "1.0")); + Mock modB = this.GetMetadata(this.GetManifest("Mod B", "1.0", new ManifestDependency("Mod A", "1.1")), allowStatusChange: true); + + // act + IModMetadata[] mods = new ModResolver().ProcessDependencies(new[] { modA.Object, modB.Object }).ToArray(); + + // assert + Assert.AreEqual(2, mods.Length, 0, "Expected to get the same number of mods input."); + modB.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny()), Times.Once, "Mod B unexpectedly didn't fail even though it needs a newer version of Mod A."); + } + + [Test(Description = "Assert that dependencies are accepted if they meet the minimum version.")] + public void ProcessDependencies_WithMinVersions_SucceedsIfMet() + { + // arrange + // A 1.0 ◀── B (need A 1.0-beta) + Mock modA = this.GetMetadata(this.GetManifest("Mod A", "1.0")); + Mock modB = this.GetMetadata(this.GetManifest("Mod B", "1.0", new ManifestDependency("Mod A", "1.0-beta")), allowStatusChange: false); + + // act + IModMetadata[] mods = new ModResolver().ProcessDependencies(new[] { modA.Object, modB.Object }).ToArray(); + + // assert + Assert.AreEqual(2, mods.Length, 0, "Expected to get the same number of mods input."); + Assert.AreSame(modA.Object, mods[0], "The load order is incorrect: mod A should be first since it's needed by mod B."); + Assert.AreSame(modB.Object, mods[1], "The load order is incorrect: mod B should be second since it needs mod A."); + } + /********* ** Private methods *********/ /// Get a randomised basic manifest. /// Adjust the generated manifest. - private Manifest GetRandomManifest(Action adjust = null) + private Manifest GetManifest(Action adjust = null) { Manifest manifest = new Manifest { @@ -401,26 +435,50 @@ namespace StardewModdingAPI.Tests /// Get a randomised basic manifest. /// The mod's name and unique ID. + /// The mod version. /// The dependencies this mod requires. + private IManifest GetManifest(string uniqueID, string version, params IManifestDependency[] dependencies) + { + return this.GetManifest(manifest => + { + manifest.Name = uniqueID; + manifest.UniqueID = uniqueID; + manifest.Version = new SemanticVersion(version); + manifest.Dependencies = dependencies; + }); + } + + /// Get a randomised basic manifest. + /// The mod's name and unique ID. + private Mock GetMetadata(string uniqueID) + { + return this.GetMetadata(this.GetManifest(uniqueID, "1.0")); + } + + /// Get a randomised basic manifest. + /// The mod's name and unique ID. + /// The dependencies this mod requires. + /// Whether the code being tested is allowed to change the mod status. + private Mock GetMetadata(string uniqueID, string[] dependencies, bool allowStatusChange = false) + { + IManifest manifest = this.GetManifest(uniqueID, "1.0", dependencies?.Select(dependencyID => (IManifestDependency)new ManifestDependency(dependencyID, null)).ToArray()); + return this.GetMetadata(manifest, allowStatusChange); + } + + /// Get a randomised basic manifest. + /// The mod manifest. /// Whether the code being tested is allowed to change the mod status. - private Mock GetMetadataForDependencyTest(string uniqueID, string[] dependencies = null, bool allowStatusChange = false) + private Mock GetMetadata(IManifest manifest, bool allowStatusChange = false) { Mock mod = new Mock(MockBehavior.Strict); mod.Setup(p => p.Status).Returns(ModMetadataStatus.Found); - mod.Setup(p => p.DisplayName).Returns(uniqueID); - mod.Setup(p => p.Manifest).Returns( - this.GetRandomManifest(manifest => - { - manifest.Name = uniqueID; - manifest.UniqueID = uniqueID; - manifest.Dependencies = dependencies?.Select(dependencyID => (IManifestDependency)new ManifestDependency(dependencyID)).ToArray(); - }) - ); + mod.Setup(p => p.DisplayName).Returns(manifest.UniqueID); + mod.Setup(p => p.Manifest).Returns(manifest); if (allowStatusChange) { mod .Setup(p => p.SetStatus(It.IsAny(), It.IsAny())) - .Callback((status, message) => Console.WriteLine($"<{uniqueID} changed status: [{status}] {message}")) + .Callback((status, message) => Console.WriteLine($"<{manifest.UniqueID} changed status: [{status}] {message}")) .Returns(mod.Object); } return mod; 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.Tests/SDateTests.cs | 41 ++++++ .../StardewModdingAPI.Tests.csproj | 3 + src/StardewModdingAPI.Tests/packages.config | 1 + src/StardewModdingAPI/Utilities/SDate.cs | 150 +++++++++++++++++++++ 4 files changed, 195 insertions(+) (limited to 'src') diff --git a/src/StardewModdingAPI.Tests/SDateTests.cs b/src/StardewModdingAPI.Tests/SDateTests.cs index a4c65a98..1cce6335 100644 --- a/src/StardewModdingAPI.Tests/SDateTests.cs +++ b/src/StardewModdingAPI.Tests/SDateTests.cs @@ -83,6 +83,47 @@ namespace StardewModdingAPI.Tests return this.ParseDate(dateStr).AddDays(addDays).ToString(); } + [Test(Description = "Assert that the equality operators work as expected")] + public void EqualityOperators() + { + SDate s1 = new SDate(1, "spring", 2); + SDate s2 = new SDate(1, "spring", 2); + SDate s3 = new SDate(1, "spring", 3); + SDate s4 = new SDate(12, "spring", 2); + SDate s5 = new SDate(1, "summer", 2); + + Assert.AreEqual(true, s1 == s2); + Assert.AreNotEqual(true, s1 == s3); + Assert.AreNotEqual(true, s1 == s4); + Assert.AreNotEqual(true, s1 == s5); + } + + [Test(Description = "Assert that the comparison operators work as expected")] + public void ComparisonOperators() + { + SDate s1 = new SDate(1, "spring", 2); + SDate s2 = new SDate(1, "spring", 2); + SDate s3 = new SDate(1, "spring", 3); + SDate s4 = new SDate(12, "spring", 2); + SDate s5 = new SDate(1, "summer", 2); + SDate s6 = new SDate(1, "winter", 1); + SDate s7 = new SDate(13, "fall", 1); + + Assert.AreEqual(true, s1 <= s2); + Assert.AreEqual(true, s1 >= s2); + Assert.AreEqual(true, s1 < s4); + Assert.AreEqual(true, s1 <= s4); + Assert.AreEqual(true, s4 > s1); + Assert.AreEqual(true, s4 >= s1); + Assert.AreEqual(true, s5 > s7); + Assert.AreEqual(true, s5 >= s7); + Assert.AreEqual(true, s6 < s5); + Assert.AreEqual(true, s6 <= s5); + Assert.AreEqual(true, s1 < s5); + Assert.AreEqual(true, s1 <= s5); + Assert.AreEqual(true, s5 > s1); + Assert.AreEqual(true, s5 >= s1); + } /********* ** Private methods diff --git a/src/StardewModdingAPI.Tests/StardewModdingAPI.Tests.csproj b/src/StardewModdingAPI.Tests/StardewModdingAPI.Tests.csproj index 3ddb1326..fbce657d 100644 --- a/src/StardewModdingAPI.Tests/StardewModdingAPI.Tests.csproj +++ b/src/StardewModdingAPI.Tests/StardewModdingAPI.Tests.csproj @@ -63,6 +63,9 @@ StardewModdingAPI + + + \ No newline at end of file diff --git a/src/StardewModdingAPI.Tests/packages.config b/src/StardewModdingAPI.Tests/packages.config index ba954308..d25dae06 100644 --- a/src/StardewModdingAPI.Tests/packages.config +++ b/src/StardewModdingAPI.Tests/packages.config @@ -4,4 +4,5 @@ + \ No newline at end of file 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 fu