From 433261e7d57096bec41ae6eea16f5f18b8045be8 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 4 Aug 2018 16:18:23 -0400 Subject: fix ToSButton() methods not public --- src/SMAPI/SButton.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/SMAPI/SButton.cs b/src/SMAPI/SButton.cs index 3f95169a..bc76c91d 100644 --- a/src/SMAPI/SButton.cs +++ b/src/SMAPI/SButton.cs @@ -604,21 +604,21 @@ namespace StardewModdingAPI *********/ /// Get the equivalent for the given button. /// The keyboard button to convert. - internal static SButton ToSButton(this Keys key) + public static SButton ToSButton(this Keys key) { return (SButton)key; } /// Get the equivalent for the given button. /// The controller button to convert. - internal static SButton ToSButton(this Buttons key) + public static SButton ToSButton(this Buttons key) { return (SButton)(SButtonExtensions.ControllerOffset + key); } /// Get the equivalent for the given button. /// The Stardew Valley button to convert. - internal static SButton ToSButton(this InputButton input) + public static SButton ToSButton(this InputButton input) { // derived from InputButton constructors if (input.mouseLeft) -- cgit From a0f7a244740de91fc5104f9f33b844493cd4a3a8 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 4 Aug 2018 16:32:46 -0400 Subject: fixed false compat error when constructing multidimensional arrays --- docs/release-notes.md | 1 + .../Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index e7a06264..c215c66d 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,6 +1,7 @@ # Release notes ## 2.6.1 * For modders: + * Fixed false compatibility error when constructing multidimensional arrays. * Fixed `.ToSButton()` methods not being public. ## 2.6 diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs index cf5a3175..3a26660f 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -70,7 +70,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders // method reference MethodReference methodReference = RewriteHelper.AsMethodReference(instruction); - if (methodReference != null && this.ShouldValidate(methodReference.DeclaringType)) + if (methodReference != null && !this.IsUnsupported(methodReference) && this.ShouldValidate(methodReference.DeclaringType)) { // get potential targets MethodDefinition[] candidateMethods = methodReference.DeclaringType.Resolve()?.Methods.Where(found => found.Name == methodReference.Name).ToArray(); @@ -106,6 +106,14 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders return type != null && this.ValidateReferencesToAssemblies.Contains(type.Scope.Name); } + /// Get whether a method reference is a special case that's not currently supported (e.g. array methods). + /// The method reference. + private bool IsUnsupported(MethodReference method) + { + return + method.DeclaringType.Name.Contains("["); // array methods + } + /// Get a shorter type name for display. /// The type reference. private string GetFriendlyTypeName(TypeReference type) -- cgit From 7eca78426e46c90320be6d904cd97fa6319da1a7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 5 Aug 2018 22:11:11 -0400 Subject: mark Everytime Submarine incompatible (#574) --- docs/release-notes.md | 1 + src/SMAPI.Web/wwwroot/StardewModdingAPI.metadata.json | 5 +++++ 2 files changed, 6 insertions(+) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index c215c66d..4594815f 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -3,6 +3,7 @@ * For modders: * Fixed false compatibility error when constructing multidimensional arrays. * Fixed `.ToSButton()` methods not being public. + * Updated compatibility list. ## 2.6 * For players: diff --git a/src/SMAPI.Web/wwwroot/StardewModdingAPI.metadata.json b/src/SMAPI.Web/wwwroot/StardewModdingAPI.metadata.json index e72efb39..e00ef63e 100644 --- a/src/SMAPI.Web/wwwroot/StardewModdingAPI.metadata.json +++ b/src/SMAPI.Web/wwwroot/StardewModdingAPI.metadata.json @@ -566,6 +566,11 @@ "~2.0.6 | Status": "AssumeBroken" // broke in SMAPI 2.5 (error reflecting into SMAPI internals) }, + "Everytime Submarine": { + "ID": "MustafaDemirel.EverytimeSubmarine", + "~1.0.0 | Status": "AssumeBroken" // breaks player saves if their beach bridge is fixed + }, + "Expanded Fridge": { "ID": "Uwazouri.ExpandedFridge", "Default | UpdateKey": "Nexus:1191" -- cgit From a1a93ac4bf20458d49028ed40905e715aa1ae152 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 6 Aug 2018 22:21:10 -0400 Subject: fix spring tilesheets always used for custom festival maps (#577) --- docs/release-notes.md | 3 +++ src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 4594815f..42bd7975 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,5 +1,8 @@ # Release notes ## 2.6.1 +* For players: + * Fixed custom festival maps always using spring tilesheets. + * For modders: * Fixed false compatibility error when constructing multidimensional arrays. * Fixed `.ToSButton()` methods not being public. diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index 671dc21e..a8b24a13 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -259,7 +259,7 @@ namespace StardewModdingAPI.Framework.ModHelpers // get seasonal name (if applicable) string seasonalImageSource = null; - if (Game1.currentSeason != null) + if (Context.IsSaveLoaded && Game1.currentSeason != null) { string filename = Path.GetFileName(imageSource) ?? throw new InvalidOperationException($"The '{imageSource}' tilesheet couldn't be loaded: filename is unexpectedly null."); bool hasSeasonalPrefix = -- cgit From 8df5d79c9e01c2d665192ba5accfcd45a47e2dda Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 6 Aug 2018 22:23:18 -0400 Subject: fix ConsoleCommands item search code --- docs/release-notes.md | 2 ++ .../Framework/ItemData/ItemType.cs | 5 +--- .../Framework/ItemRepository.cs | 29 ++++++++++------------ 3 files changed, 16 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 42bd7975..691bf48e 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,8 @@ ## 2.6.1 * For players: * Fixed custom festival maps always using spring tilesheets. + * Fixed `player_add` command not recognising return scepter. + * Fixed `player_add` showing fish twice. * For modders: * Fixed false compatibility error when constructing multidimensional arrays. diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/ItemType.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/ItemType.cs index 797d4650..7ee662d0 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/ItemType.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/ItemType.cs @@ -1,4 +1,4 @@ -namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData +namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData { /// An item type that can be searched and added to the player through the console. internal enum ItemType @@ -9,9 +9,6 @@ /// A item. Boots, - /// A fish item. - Fish, - /// A flooring item. Flooring, diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs index e678d057..7a3d8694 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs @@ -37,14 +37,15 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework yield return new SearchableItem(ItemType.Tool, this.CustomIDOffset, new MilkPail()); // these don't have any sort of ID, so we'll just assign some arbitrary ones yield return new SearchableItem(ItemType.Tool, this.CustomIDOffset + 1, new Shears()); yield return new SearchableItem(ItemType.Tool, this.CustomIDOffset + 2, new Pan()); + yield return new SearchableItem(ItemType.Tool, this.CustomIDOffset + 3, new Wand()); // wallpapers for (int id = 0; id < 112; id++) - yield return new SearchableItem(ItemType.Wallpaper, id, new Wallpaper(id)); + yield return new SearchableItem(ItemType.Wallpaper, id, new Wallpaper(id) { Category = SObject.furnitureCategory }); // flooring for (int id = 0; id < 40; id++) - yield return new SearchableItem(ItemType.Flooring, id, new Wallpaper(id, isFloor: true)); + yield return new SearchableItem(ItemType.Flooring, id, new Wallpaper(id, isFloor: true) { Category = SObject.furnitureCategory }); // equipment foreach (int id in Game1.content.Load>("Data\\Boots").Keys) @@ -75,10 +76,6 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework yield return new SearchableItem(ItemType.Furniture, id, new Furniture(id, Vector2.Zero)); } - // fish - foreach (int id in Game1.content.Load>("Data\\Fish").Keys) - yield return new SearchableItem(ItemType.Fish, id, new SObject(id, 999)); - // craftables foreach (int id in Game1.bigCraftablesInformation.Keys) yield return new SearchableItem(ItemType.BigCraftable, id, new SObject(Vector2.Zero, id)); @@ -103,16 +100,16 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework yield return new SearchableItem(ItemType.Object, id, item); // fruit products - if (item.category == SObject.FruitsCategory) + if (item.Category == SObject.FruitsCategory) { // wine SObject wine = new SObject(348, 1) { Name = $"{item.Name} Wine", - Price = item.price * 3 + Price = item.Price * 3 }; wine.preserve.Value = SObject.PreserveType.Wine; - wine.preservedParentSheetIndex.Value = item.parentSheetIndex; + wine.preservedParentSheetIndex.Value = item.ParentSheetIndex; yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 2 + id, wine); // jelly @@ -122,21 +119,21 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework Price = 50 + item.Price * 2 }; jelly.preserve.Value = SObject.PreserveType.Jelly; - jelly.preservedParentSheetIndex.Value = item.parentSheetIndex; + jelly.preservedParentSheetIndex.Value = item.ParentSheetIndex; yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 3 + id, jelly); } // vegetable products - else if (item.category == SObject.VegetableCategory) + else if (item.Category == SObject.VegetableCategory) { // juice SObject juice = new SObject(350, 1) { Name = $"{item.Name} Juice", - Price = (int)(item.price * 2.25d) + Price = (int)(item.Price * 2.25d) }; juice.preserve.Value = SObject.PreserveType.Juice; - juice.preservedParentSheetIndex.Value = item.parentSheetIndex; + juice.preservedParentSheetIndex.Value = item.ParentSheetIndex; yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 4 + id, juice); // pickled @@ -146,16 +143,16 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework Price = 50 + item.Price * 2 }; pickled.preserve.Value = SObject.PreserveType.Pickle; - pickled.preservedParentSheetIndex.Value = item.parentSheetIndex; + pickled.preservedParentSheetIndex.Value = item.ParentSheetIndex; yield return new SearchableItem(ItemType.Object, this.CustomIDOffset * 5 + id, pickled); } // flower honey - else if (item.category == SObject.flowersCategory) + else if (item.Category == SObject.flowersCategory) { // get honey type SObject.HoneyType? type = null; - switch (item.parentSheetIndex) + switch (item.ParentSheetIndex) { case 376: type = SObject.HoneyType.Poppy; -- cgit From 03e679d66cc7439b0218c95cbead925786e37e86 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 8 Aug 2018 00:12:47 -0400 Subject: serialise all enums to string in JSON by default --- docs/release-notes.md | 3 ++- src/SMAPI/Program.cs | 3 --- .../Converters/StringEnumConverter.cs | 22 ---------------------- .../Serialisation/JsonHelper.cs | 7 ++++++- 4 files changed, 8 insertions(+), 27 deletions(-) delete mode 100644 src/StardewModdingAPI.Toolkit/Serialisation/Converters/StringEnumConverter.cs (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 691bf48e..d14b7459 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -3,9 +3,10 @@ * For players: * Fixed custom festival maps always using spring tilesheets. * Fixed `player_add` command not recognising return scepter. - * Fixed `player_add` showing fish twice. + * Fixed `player_add` command showing fish twice. * For modders: + * All enums in `config.json` are now serialised to string by default, since that's more user-friendly. Previously only certain predefined enums were serialised that way. Note that `config.json` files which already have integer enums will still be parsed fine. * Fixed false compatibility error when constructing multidimensional arrays. * Fixed `.ToSButton()` methods not being public. * Updated compatibility list. diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 6012b15a..999aa23c 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -193,9 +193,6 @@ namespace StardewModdingAPI // init JSON parser JsonConverter[] converters = { - new StringEnumConverter(), - new StringEnumConverter(), - new StringEnumConverter(), new ColorConverter(), new PointConverter(), new RectangleConverter() diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/StringEnumConverter.cs b/src/StardewModdingAPI.Toolkit/Serialisation/Converters/StringEnumConverter.cs deleted file mode 100644 index 13e6e3a1..00000000 --- a/src/StardewModdingAPI.Toolkit/Serialisation/Converters/StringEnumConverter.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using Newtonsoft.Json.Converters; - -namespace StardewModdingAPI.Toolkit.Serialisation.Converters -{ - /// A variant of which only converts a specified enum. - /// The enum type. - internal class StringEnumConverter : StringEnumConverter - { - /********* - ** Public methods - *********/ - /// Get whether this instance can convert the specified object type. - /// The object type. - public override bool CanConvert(Type type) - { - return - base.CanConvert(type) - && (Nullable.GetUnderlyingType(type) ?? type) == typeof(T); - } - } -} diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs b/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs index 00f334ad..3cabbab3 100644 --- a/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs +++ b/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using StardewModdingAPI.Toolkit.Serialisation.Converters; namespace StardewModdingAPI.Toolkit.Serialisation @@ -17,7 +18,11 @@ namespace StardewModdingAPI.Toolkit.Serialisation { Formatting = Formatting.Indented, ObjectCreationHandling = ObjectCreationHandling.Replace, // avoid issue where default ICollection values are duplicated each time the config is loaded - Converters = new List { new SemanticVersionConverter() } + Converters = new List + { + new SemanticVersionConverter(), + new StringEnumConverter() + } }; -- cgit From 9488d6482b03aa2227318f0028d10a44849367f6 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 9 Aug 2018 16:32:00 -0400 Subject: fix some log files not deleted on startup --- docs/release-notes.md | 1 + src/SMAPI/Constants.cs | 9 ++++++--- src/SMAPI/Program.cs | 7 +++---- 3 files changed, 10 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index d14b7459..5b0dab4c 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,7 @@ * Fixed custom festival maps always using spring tilesheets. * Fixed `player_add` command not recognising return scepter. * Fixed `player_add` command showing fish twice. + * Fixed some SMAPI logs not deleted when starting a new session. * For modders: * All enums in `config.json` are now serialised to string by default, since that's more user-friendly. Previously only certain predefined enums were serialised that way. Note that `config.json` files which already have integer enums will still be parsed fine. diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index a6cddbe4..07bb3e17 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -70,11 +70,14 @@ namespace StardewModdingAPI /// The file path for the SMAPI metadata file. internal static string ApiMetadataPath => Path.Combine(Constants.ExecutionPath, $"{typeof(Program).Assembly.GetName().Name}.metadata.json"); - /// The filename prefix for SMAPI log files. - internal static string LogNamePrefix { get; } = "SMAPI-latest"; + /// The filename prefix used for all SMAPI logs. + internal static string LogNamePrefix { get; } = "SMAPI-"; + + /// The filename for SMAPI's main log, excluding the . + internal static string LogFilename { get; } = $"{Constants.LogNamePrefix}latest"; /// The filename extension for SMAPI log files. - internal static string LogNameExtension { get; } = "txt"; + internal static string LogExtension { get; } = "txt"; /// A copy of the log leading up to the previous fatal crash, if any. internal static string FatalCrashLog => Path.Combine(Constants.LogDir, "SMAPI-crash.txt"); diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 999aa23c..5d8b267f 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -435,7 +435,7 @@ namespace StardewModdingAPI ModFolderPath = Constants.ModPath, Mods = mods }; - this.Toolkit.JsonHelper.WriteJsonFile(Path.Combine(Constants.LogDir, $"{Constants.LogNamePrefix}.metadata-dump.json"), export); + this.Toolkit.JsonHelper.WriteJsonFile(Path.Combine(Constants.LogDir, $"{Constants.LogNamePrefix}metadata-dump.json"), export); } // check for updates @@ -1060,7 +1060,6 @@ namespace StardewModdingAPI yield return "uses the 'dynamic' keyword. This won't work on Linux/Mac."; if (mask.HasFlag(ModWarning.NoUpdateKeys)) yield return "has no update keys in its manifest. SMAPI won't show update alerts for this mod."; - } /// Load a mod's entry class. @@ -1257,7 +1256,7 @@ namespace StardewModdingAPI { // default path { - FileInfo defaultFile = new FileInfo(Path.Combine(Constants.LogDir, $"{Constants.LogNamePrefix}.{Constants.LogNameExtension}")); + FileInfo defaultFile = new FileInfo(Path.Combine(Constants.LogDir, $"{Constants.LogFilename}.{Constants.LogExtension}")); if (!defaultFile.Exists) return defaultFile.FullName; } @@ -1265,7 +1264,7 @@ namespace StardewModdingAPI // get first disambiguated path for (int i = 2; i < int.MaxValue; i++) { - FileInfo file = new FileInfo(Path.Combine(Constants.LogDir, $"{Constants.LogNamePrefix}.player-{i}.{Constants.LogNameExtension}")); + FileInfo file = new FileInfo(Path.Combine(Constants.LogDir, $"{Constants.LogFilename}.player-{i}.{Constants.LogExtension}")); if (!file.Exists) return file.FullName; } -- cgit From 3d7ce99d798745ee746dafdb1f591b5c23ff16dd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 10 Aug 2018 00:59:48 -0400 Subject: revamp how mod skips & issues are displayed (#571) --- docs/release-notes.md | 1 + src/SMAPI/Program.cs | 143 +++++++++++++++++++++++++++++--------------------- 2 files changed, 85 insertions(+), 59 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 5b0dab4c..3f62b1ef 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,6 +1,7 @@ # Release notes ## 2.6.1 * For players: + * Improved how mod issues are listed in the console and log. * Fixed custom festival maps always using spring tilesheets. * Fixed `player_add` command not recognising return scepter. * Fixed `player_add` command showing fish twice. diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 5d8b267f..4e4c913a 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -11,7 +11,6 @@ using System.Security; using System.Text; using System.Text.RegularExpressions; using System.Threading; -using Microsoft.Xna.Framework.Input; #if SMAPI_FOR_WINDOWS using System.Windows.Forms; #endif @@ -32,10 +31,8 @@ using StardewModdingAPI.Toolkit; using StardewModdingAPI.Toolkit.Framework.Clients.WebApi; using StardewModdingAPI.Toolkit.Framework.ModData; using StardewModdingAPI.Toolkit.Serialisation; -using StardewModdingAPI.Toolkit.Serialisation.Converters; using StardewModdingAPI.Toolkit.Utilities; using StardewValley; -using Keys = Microsoft.Xna.Framework.Input.Keys; using Monitor = StardewModdingAPI.Framework.Monitor; using SObject = StardewValley.Object; using ThreadState = System.Threading.ThreadState; @@ -881,33 +878,15 @@ namespace StardewModdingAPI } IModMetadata[] loadedMods = this.ModRegistry.GetAll(contentPacks: false).ToArray(); - // log skipped mods - this.Monitor.Newline(); - if (skippedMods.Any()) - { - this.Monitor.Log($"Skipped {skippedMods.Count} mods:", LogLevel.Error); - foreach (var pair in skippedMods.OrderBy(p => p.Key.DisplayName)) - { - IModMetadata mod = pair.Key; - string[] reason = pair.Value; - - this.Monitor.Log($" {mod.DisplayName}{(mod.Manifest?.Version != null ? " " + mod.Manifest.Version.ToString() : "")} because {reason[0]}", LogLevel.Error); - if (reason[1] != null) - this.Monitor.Log($" {reason[1]}", LogLevel.Trace); - } - this.Monitor.Newline(); - } - // log loaded mods this.Monitor.Log($"Loaded {loadedMods.Length} mods" + (loadedMods.Length > 0 ? ":" : "."), LogLevel.Info); - foreach (IModMetadata metadata in loadedMods.OrderBy(p => p.DisplayName)) { IManifest manifest = metadata.Manifest; this.Monitor.Log( $" {metadata.DisplayName} {manifest.Version}" - + (!string.IsNullOrWhiteSpace(manifest.Author) ? $" by {manifest.Author}" : "") - + (!string.IsNullOrWhiteSpace(manifest.Description) ? $" | {manifest.Description}" : ""), + + (!string.IsNullOrWhiteSpace(manifest.Author) ? $" by {manifest.Author}" : "") + + (!string.IsNullOrWhiteSpace(manifest.Description) ? $" | {manifest.Description}" : ""), LogLevel.Info ); } @@ -933,27 +912,8 @@ namespace StardewModdingAPI this.Monitor.Newline(); } - // log warnings - { - IModMetadata[] modsWithWarnings = this.ModRegistry.GetAll().Where(p => p.Warnings != ModWarning.None).ToArray(); - if (modsWithWarnings.Any()) - { - this.Monitor.Log($"Found issues with {modsWithWarnings.Length} mods:", LogLevel.Warn); - foreach (IModMetadata metadata in modsWithWarnings) - { - string[] warnings = this.GetWarningText(metadata.Warnings).ToArray(); - if (warnings.Length == 1) - this.Monitor.Log($" {metadata.DisplayName} {warnings[0]}", LogLevel.Warn); - else - { - this.Monitor.Log($" {metadata.DisplayName}:", LogLevel.Warn); - foreach (string warning in warnings) - this.Monitor.Log(" - " + warning, LogLevel.Warn); - } - } - this.Monitor.Newline(); - } - } + // log mod warnings + this.LogModWarnings(this.ModRegistry.GetAll().ToArray(), skippedMods); // initialise translations this.ReloadTranslations(loadedMods); @@ -1044,22 +1004,87 @@ namespace StardewModdingAPI this.ModRegistry.AreAllModsInitialised = true; } - /// Get the warning text for a mod warning bit mask. - /// The mod warning bit mask. - private IEnumerable GetWarningText(ModWarning mask) + /// Write a summary of mod warnings to the console and log. + /// The loaded mods. + /// The mods which were skipped, along with the friendly and developer reasons. + private void LogModWarnings(IModMetadata[] mods, IDictionary skippedMods) { - if (mask.HasFlag(ModWarning.BrokenCodeLoaded)) - yield return "has broken code, but SMAPI is configured to allow it anyway. The mod may crash or behave unexpectedly."; - if (mask.HasFlag(ModWarning.ChangesSaveSerialiser)) - yield return "accesses the save serialiser and may break your saves."; - if (mask.HasFlag(ModWarning.PatchesGame)) - yield return "patches the game. This may cause errors or bugs in-game. If you have issues, try removing this mod first."; - if (mask.HasFlag(ModWarning.UsesUnvalidatedUpdateTick)) - yield return "bypasses normal SMAPI event protections. This may cause errors or save corruption. If you have issues, try removing this mod first."; - if (mask.HasFlag(ModWarning.UsesDynamic)) - yield return "uses the 'dynamic' keyword. This won't work on Linux/Mac."; - if (mask.HasFlag(ModWarning.NoUpdateKeys)) - yield return "has no update keys in its manifest. SMAPI won't show update alerts for this mod."; + // get mods with warnings + IModMetadata[] modsWithWarnings = mods.Where(p => p.Warnings != ModWarning.None).ToArray(); + if (!modsWithWarnings.Any() && !skippedMods.Any()) + return; + + // log intro + { + int count = modsWithWarnings.Union(skippedMods.Keys).Count(); + this.Monitor.Log($"Found {count} mod{(count == 1 ? "" : "s")} with warnings:", LogLevel.Info); + } + + // log skipped mods + if (skippedMods.Any()) + { + this.Monitor.Log(" Skipped mods", LogLevel.Error); + this.Monitor.Log(" " + "".PadRight(50, '-'), LogLevel.Error); + this.Monitor.Log(" These mods could not be added to your game.", LogLevel.Error); + this.Monitor.Newline(); + + foreach (var pair in skippedMods.OrderBy(p => p.Key.DisplayName)) + { + IModMetadata mod = pair.Key; + string[] reason = pair.Value; + + this.Monitor.Log($" - {mod.DisplayName}{(mod.Manifest?.Version != null ? " " + mod.Manifest.Version.ToString() : "")} because {reason[0]}", LogLevel.Error); + if (reason[1] != null) + this.Monitor.Log($" ({reason[1]})", LogLevel.Trace); + } + this.Monitor.Newline(); + } + + // log warnings + if (modsWithWarnings.Any()) + { + // issue block format logic + void LogWarningGroup(ModWarning warning, LogLevel logLevel, string heading, params string[] blurb) + { + IModMetadata[] matches = modsWithWarnings.Where(p => p.Warnings.HasFlag(warning)).ToArray(); + if (!matches.Any()) + return; + + this.Monitor.Log(" " + heading, logLevel); + this.Monitor.Log(" " + "".PadRight(50, '-'), logLevel); + foreach (string line in blurb) + this.Monitor.Log(" " + line, logLevel); + this.Monitor.Newline(); + foreach (IModMetadata match in matches) + this.Monitor.Log($" - {match.DisplayName}", logLevel); + this.Monitor.Newline(); + } + + // supported issues + LogWarningGroup(ModWarning.BrokenCodeLoaded, LogLevel.Error, "Broken mods", + "These mods have broken code, but you configured SMAPI to load them anyway. This may cause bugs,", + "errors, or crashes in-game." + ); + LogWarningGroup(ModWarning.ChangesSaveSerialiser, LogLevel.Warn, "Changed save serialiser", + "These mods change the save serialiser. They may corrupt your save files, or make them unusable if", + "you uninstall these mods." + ); + LogWarningGroup(ModWarning.PatchesGame, LogLevel.Info, "Patched game code", + "These mods directly change the game code. They're more likely to cause errors or bugs in-game; if", + "your game has issues, try removing these first. Otherwise you can ignore this warning." + ); + LogWarningGroup(ModWarning.UsesUnvalidatedUpdateTick, LogLevel.Info, "Bypassed safety checks", + "These mods bypass SMAPI's normal safety checks, so they're more likely to cause errors or save", + "corruption. If your game has issues, try removing these first." + ); + LogWarningGroup(ModWarning.NoUpdateKeys, LogLevel.Debug, "No update keys", + "These mods have no update keys in their manifest. SMAPI may not notify you about updates for these", + "mods. Consider notifying the mod authors about this problem." + ); + LogWarningGroup(ModWarning.UsesDynamic, LogLevel.Debug, "Not crossplatform", + "These mods use the 'dynamic' keyword, and won't work on Linux/Mac." + ); + } } /// Load a mod's entry class. -- cgit From 086587c16be6a90a47b11795d3e07e153f1e500d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 10 Aug 2018 23:56:56 -0400 Subject: add broken versions of Canon-Friendly Dialogue Expansion to compatibility list --- .../wwwroot/StardewModdingAPI.metadata.json | 26 ++++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/SMAPI.Web/wwwroot/StardewModdingAPI.metadata.json b/src/SMAPI.Web/wwwroot/StardewModdingAPI.metadata.json index e00ef63e..c95abe75 100644 --- a/src/SMAPI.Web/wwwroot/StardewModdingAPI.metadata.json +++ b/src/SMAPI.Web/wwwroot/StardewModdingAPI.metadata.json @@ -51,6 +51,23 @@ * mod is no longer compatible. */ "ModData": { + /********* + ** Content packs + *********/ + "Canon-Friendly Dialogue Expansion": { + "ID": "gizzymo.canonfriendlyexpansion", + "~1.1.1 | Status": "AssumeBroken" // causes a save crash on certain dates + }, + + "Everytime Submarine": { + "ID": "MustafaDemirel.EverytimeSubmarine", + "~1.0.0 | Status": "AssumeBroken" // breaks player saves if their beach bridge is fixed + }, + + + /********* + ** Mods + *********/ "AccessChestAnywhere": { "ID": "AccessChestAnywhere", "MapLocalVersions": { "1.1-1078": "1.1" }, @@ -566,11 +583,6 @@ "~2.0.6 | Status": "AssumeBroken" // broke in SMAPI 2.5 (error reflecting into SMAPI internals) }, - "Everytime Submarine": { - "ID": "MustafaDemirel.EverytimeSubmarine", - "~1.0.0 | Status": "AssumeBroken" // breaks player saves if their beach bridge is fixed - }, - "Expanded Fridge": { "ID": "Uwazouri.ExpandedFridge", "Default | UpdateKey": "Nexus:1191" @@ -827,7 +839,7 @@ }, "Level Extender": { - "ID": "DevinLematty.LevelExtender", + "ID": "DevinLematty.LevelExtender", "FormerIDs": "Devin Lematty.Level Extender", // changed in 1.3 "Default | UpdateKey": "Nexus:1471" }, @@ -1051,7 +1063,7 @@ }, "One Click Shed": { - "ID": "BitwiseJonMods.OneClickShedReloader", + "ID": "BitwiseJonMods.OneClickShedReloader", "Default | UpdateKey": "Nexus:2052" }, -- cgit From ef731de8318c7f01567baf2e23ae9a09789b4bdd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 11 Aug 2018 14:18:52 -0400 Subject: add --mods-path CLI argument to allow switching between mod folders (#579) --- docs/release-notes.md | 3 ++- docs/technical-docs.md | 1 + src/SMAPI/Constants.cs | 2 +- src/SMAPI/Program.cs | 37 +++++++++++++++++++++++++++++-------- 4 files changed, 33 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index d6a72b90..183d8e2e 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -8,7 +8,8 @@ * Fixed some SMAPI logs not deleted when starting a new session. * For modders: - * All enums in `config.json` are now serialised to string by default, since that's more user-friendly. Previously only certain predefined enums were serialised that way. Note that `config.json` files which already have integer enums will still be parsed fine. + * Added `--mods-path` command-line argument to allow switching between mod folders. + * All enums are now JSON-serialised by name, since that's more user-friendly. Previously only certain predefined enums were serialised that way. JSON files which already have integer enums will still be parsed fine. * Fixed false compatibility error when constructing multidimensional arrays. * Fixed `.ToSButton()` methods not being public. * Updated compatibility list. diff --git a/docs/technical-docs.md b/docs/technical-docs.md index d829baf9..ed45871a 100644 --- a/docs/technical-docs.md +++ b/docs/technical-docs.md @@ -138,6 +138,7 @@ change without warning. argument | purpose -------- | ------- `--no-terminal` | SMAPI won't write anything to the console window. (Messages will still be written to the log file.) +`--mods-path` | The path to search for mods, if not the standard `Mods` folder. This can be a path relative to the game folder (like `--mods-path "Mods (test)"`) or an absolute path. ### Compile flags SMAPI uses a small number of conditional compilation constants, which you can set by editing the diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 07bb3e17..c7cd6e41 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -89,7 +89,7 @@ namespace StardewModdingAPI internal static string UpdateMarker => Path.Combine(Constants.ExecutionPath, "StardewModdingAPI.update.marker"); /// The full path to the folder containing mods. - internal static string ModPath { get; } = Path.Combine(Constants.ExecutionPath, "Mods"); + internal static string DefaultModsPath { get; } = Path.Combine(Constants.ExecutionPath, "Mods"); /// The game's current semantic version. internal static ISemanticVersion GameVersion { get; } = new GameVersion(Constants.GetGameVersion()); diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 4e4c913a..a894e831 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -100,6 +100,9 @@ namespace StardewModdingAPI /// The mod toolkit used for generic mod interactions. private readonly ModToolkit Toolkit = new ModToolkit(); + /// The path to search for mods. + private readonly string ModsPath; + /********* ** Public methods @@ -113,18 +116,34 @@ namespace StardewModdingAPI // get flags from arguments bool writeToConsole = !args.Contains("--no-terminal"); + // get mods path from arguments + string modsPath = null; + { + int pathIndex = Array.LastIndexOf(args, "--mods-path") + 1; + if (pathIndex >= 1 && args.Length >= pathIndex) + { + modsPath = args[pathIndex]; + if (!string.IsNullOrWhiteSpace(modsPath) && !Path.IsPathRooted(modsPath)) + modsPath = Path.Combine(Constants.ExecutionPath, modsPath); + } + if (string.IsNullOrWhiteSpace(modsPath)) + modsPath = Constants.DefaultModsPath; + } + // load SMAPI - using (Program program = new Program(writeToConsole)) + using (Program program = new Program(modsPath, writeToConsole)) program.RunInteractively(); } /// Construct an instance. + /// The path to search for mods. /// Whether to output log messages to the console. - public Program(bool writeToConsole) + public Program(string modsPath, bool writeToConsole) { // init paths - this.VerifyPath(Constants.ModPath); + this.VerifyPath(modsPath); this.VerifyPath(Constants.LogDir); + this.ModsPath = modsPath; // init log file this.PurgeLogFiles(); @@ -143,7 +162,9 @@ namespace StardewModdingAPI // init logging this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GameVersion} on {EnvironmentUtility.GetFriendlyPlatformName(Constants.Platform)}", LogLevel.Info); - this.Monitor.Log($"Mods go here: {Constants.ModPath}"); + this.Monitor.Log($"Mods go here: {modsPath}"); + if (modsPath != Constants.DefaultModsPath) + this.Monitor.Log("(Using custom --mods-path argument.)", LogLevel.Trace); this.Monitor.Log($"Log started at {DateTime.UtcNow:s} UTC", LogLevel.Trace); // validate game version @@ -412,7 +433,7 @@ namespace StardewModdingAPI ModResolver resolver = new ModResolver(); // load manifests - IModMetadata[] mods = resolver.ReadManifests(toolkit, Constants.ModPath, modDatabase).ToArray(); + IModMetadata[] mods = resolver.ReadManifests(toolkit, this.ModsPath, modDatabase).ToArray(); resolver.ValidateManifests(mods, Constants.ApiVersion, toolkit.GetUpdateUrl); // process dependencies @@ -429,7 +450,7 @@ namespace StardewModdingAPI Exported = DateTime.UtcNow.ToString("O"), ApiVersion = Constants.ApiVersion.ToString(), GameVersion = Constants.GameVersion.ToString(), - ModFolderPath = Constants.ModPath, + ModFolderPath = this.ModsPath, Mods = mods }; this.Toolkit.JsonHelper.WriteJsonFile(Path.Combine(Constants.LogDir, $"{Constants.LogNamePrefix}metadata-dump.json"), export); @@ -740,7 +761,7 @@ namespace StardewModdingAPI // load content packs foreach (IModMetadata metadata in mods.Where(p => p.IsContentPack)) { - this.Monitor.Log($" {metadata.DisplayName} (content pack, {PathUtilities.GetRelativePath(Constants.ModPath, metadata.DirectoryPath)})...", LogLevel.Trace); + this.Monitor.Log($" {metadata.DisplayName} (content pack, {PathUtilities.GetRelativePath(this.ModsPath, metadata.DirectoryPath)})...", LogLevel.Trace); // show warning for missing update key if (metadata.HasManifest() && !metadata.HasUpdateKeys()) @@ -785,7 +806,7 @@ namespace StardewModdingAPI // get basic info IManifest manifest = metadata.Manifest; this.Monitor.Log(metadata.Manifest?.EntryDll != null - ? $" {metadata.DisplayName} ({PathUtilities.GetRelativePath(Constants.ModPath, metadata.DirectoryPath)}{Path.DirectorySeparatorChar}{metadata.Manifest.EntryDll})..." // don't use Path.Combine here, since EntryDLL might not be valid + ? $" {metadata.DisplayName} ({PathUtilities.GetRelativePath(this.ModsPath, metadata.DirectoryPath)}{Path.DirectorySeparatorChar}{metadata.Manifest.EntryDll})..." // don't use Path.Combine here, since EntryDLL might not be valid : $" {metadata.DisplayName}...", LogLevel.Trace); // show warnings -- cgit From b7907293349e95f84583e682f38e0eb491ac2e5d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 11 Aug 2018 20:33:21 -0400 Subject: add support for loading unpacked .json files through content API (#576) --- docs/release-notes.md | 1 + src/SMAPI/Framework/ContentCoordinator.cs | 10 ++++-- .../Framework/ContentManagers/ModContentManager.cs | 31 +++++++++++++++--- src/SMAPI/Framework/ContentPack.cs | 4 ++- src/SMAPI/Framework/ModHelpers/ModHelper.cs | 4 ++- src/SMAPI/Framework/SGame.cs | 26 +++++++-------- src/SMAPI/Framework/SGameConstructorHack.cs | 37 ++++++++++++++++++++++ src/SMAPI/Program.cs | 10 +++--- src/SMAPI/StardewModdingAPI.csproj | 1 + .../Framework/ModScanning/ModScanner.cs | 3 +- .../Serialisation/JsonHelper.cs | 15 +++++---- 11 files changed, 108 insertions(+), 34 deletions(-) create mode 100644 src/SMAPI/Framework/SGameConstructorHack.cs (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 183d8e2e..9556d58c 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -8,6 +8,7 @@ * Fixed some SMAPI logs not deleted when starting a new session. * For modders: + * Added support for `.json` data files in the content API (including Content Patcher). * Added `--mods-path` command-line argument to allow switching between mod folders. * All enums are now JSON-serialised by name, since that's more user-friendly. Previously only certain predefined enums were serialised that way. JSON files which already have integer enums will still be parsed fine. * Fixed false compatibility error when constructing multidimensional arrays. diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index d9b2109a..9eb7b5f9 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -9,6 +9,7 @@ using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.ContentManagers; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Metadata; +using StardewModdingAPI.Toolkit.Serialisation; using StardewModdingAPI.Toolkit.Utilities; using StardewValley; @@ -32,6 +33,9 @@ namespace StardewModdingAPI.Framework /// Simplifies access to private code. private readonly Reflector Reflection; + /// Encapsulates SMAPI's JSON file parsing. + private readonly JsonHelper JsonHelper; + /// The loaded content managers (including the ). private readonly IList ContentManagers = new List(); @@ -67,10 +71,12 @@ namespace StardewModdingAPI.Framework /// The current culture for which to localise content. /// Encapsulates monitoring and logging. /// Simplifies access to private code. - public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection) + /// Encapsulates SMAPI's JSON file parsing. + public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper) { this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor)); this.Reflection = reflection; + this.JsonHelper = jsonHelper; this.FullRootDirectory = Path.Combine(Constants.ExecutionPath, rootDirectory); this.ContentManagers.Add( this.MainContentManager = new GameContentManager("Game1.content", serviceProvider, rootDirectory, currentCulture, this, monitor, reflection, this.OnDisposing) @@ -92,7 +98,7 @@ namespace StardewModdingAPI.Framework /// The root directory to search for content (or null for the default). public ModContentManager CreateModContentManager(string name, string rootDirectory) { - ModContentManager manager = new ModContentManager(name, this.MainContentManager.ServiceProvider, rootDirectory, this.MainContentManager.CurrentCulture, this, this.Monitor, this.Reflection, this.OnDisposing); + ModContentManager manager = new ModContentManager(name, this.MainContentManager.ServiceProvider, rootDirectory, this.MainContentManager.CurrentCulture, this, this.Monitor, this.Reflection, this.JsonHelper, this.OnDisposing); this.ContentManagers.Add(manager); return manager; } diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 80bf37e9..24ce69ea 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -5,6 +5,7 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Reflection; +using StardewModdingAPI.Toolkit.Serialisation; using StardewValley; namespace StardewModdingAPI.Framework.ContentManagers @@ -12,6 +13,13 @@ namespace StardewModdingAPI.Framework.ContentManagers /// A content manager which handles reading files from a SMAPI mod folder with support for unpacked files. internal class ModContentManager : BaseContentManager { + /********* + ** Properties + *********/ + /// Encapsulates SMAPI's JSON file parsing. + private readonly JsonHelper JsonHelper; + + /********* ** Public methods *********/ @@ -23,9 +31,13 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The central coordinator which manages content managers. /// Encapsulates monitoring and logging. /// Simplifies access to private code. + /// Encapsulates SMAPI's JSON file parsing. /// A callback to invoke when the content manager is being disposed. - public ModContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, Action onDisposing) - : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isModFolder: true) { } + public ModContentManager(string name, IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onDisposing) + : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isModFolder: true) + { + this.JsonHelper = jsonHelper; + } /// Load an asset that has been processed by the content pipeline. /// The type of asset to load. @@ -95,9 +107,14 @@ namespace StardewModdingAPI.Framework.ContentManagers case ".xnb": return base.Load(relativePath, language); - // unpacked map - case ".tbin": - throw GetContentError($"can't read unpacked map file directly from the underlying content manager. It must be loaded through the mod's {typeof(IModHelper)}.{nameof(IModHelper.Content)} helper."); + // unpacked data + case ".json": + { + if (!this.JsonHelper.ReadJsonFileIfExists(file.FullName, out T data)) + throw GetContentError("the JSON file is invalid."); // should never happen since we check for file existence above + + return data; + } // unpacked image case ".png": @@ -114,6 +131,10 @@ namespace StardewModdingAPI.Framework.ContentManagers return (T)(object)texture; } + // unpacked map + case ".tbin": + throw GetContentError($"can't read unpacked map file directly from the underlying content manager. It must be loaded through the mod's {typeof(IModHelper)}.{nameof(IModHelper.Content)} helper."); + default: throw GetContentError($"unknown file extension '{file.Extension}'; must be one of '.png', '.tbin', or '.xnb'."); } diff --git a/src/SMAPI/Framework/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs index 4a4adb90..62d8b80d 100644 --- a/src/SMAPI/Framework/ContentPack.cs +++ b/src/SMAPI/Framework/ContentPack.cs @@ -54,7 +54,9 @@ namespace StardewModdingAPI.Framework public TModel ReadJsonFile(string path) where TModel : class { path = Path.Combine(this.DirectoryPath, PathUtilities.NormalisePathSeparators(path)); - return this.JsonHelper.ReadJsonFile(path); + return this.JsonHelper.ReadJsonFileIfExists(path, out TModel model) + ? model + : null; } /// Load content from the content pack folder (if not already cached), and return it. When loading a .png file, this must be called outside the game's draw loop. diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index d9498e83..0ba258b4 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -138,7 +138,9 @@ namespace StardewModdingAPI.Framework.ModHelpers where TModel : class { path = Path.Combine(this.DirectoryPath, PathUtilities.NormalisePathSeparators(path)); - return this.JsonHelper.ReadJsonFile(path); + return this.JsonHelper.ReadJsonFileIfExists(path, out TModel data) + ? data + : null; } /// Save to a JSON file. diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 05fedc3d..83e8c9a7 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -17,6 +17,7 @@ using StardewModdingAPI.Framework.Input; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.StateTracking; using StardewModdingAPI.Framework.Utilities; +using StardewModdingAPI.Toolkit.Serialisation; using StardewValley; using StardewValley.BellsAndWhistles; using StardewValley.Buildings; @@ -36,16 +37,6 @@ namespace StardewModdingAPI.Framework /********* ** Properties *********/ - /**** - ** Constructor hack - ****/ - /// A static instance of to use while is initialising, which happens before the constructor runs. - internal static IMonitor MonitorDuringInitialisation; - - /// A static instance of to use while is initialising, which happens before the constructor runs. - internal static Reflector ReflectorDuringInitialisation; - - /**** ** SMAPI state ****/ @@ -83,6 +74,9 @@ namespace StardewModdingAPI.Framework /// Simplifies access to private game code. private readonly Reflector Reflection; + /// Encapsulates SMAPI's JSON file parsing. + private readonly JsonHelper JsonHelper; + /**** ** Game state ****/ @@ -105,6 +99,9 @@ namespace StardewModdingAPI.Framework /********* ** Accessors *********/ + /// Static state to use while is initialising, which happens before the constructor runs. + internal static SGameConstructorHack ConstructorHack { get; set; } + /// SMAPI's content manager. public ContentCoordinator ContentCore { get; private set; } @@ -132,10 +129,13 @@ namespace StardewModdingAPI.Framework /// Encapsulates monitoring and logging. /// Simplifies access to private game code. /// Manages SMAPI events for mods. + /// Encapsulates SMAPI's JSON file parsing. /// A callback to invoke after the game finishes initialising. /// A callback to invoke when the game exits. - internal SGame(IMonitor monitor, Reflector reflection, EventManager eventManager, Action onGameInitialised, Action onGameExiting) + internal SGame(IMonitor monitor, Reflector reflection, EventManager eventManager, JsonHelper jsonHelper, Action onGameInitialised, Action onGameExiting) { + SGame.ConstructorHack = null; + // check expectations if (this.ContentCore == null) throw new InvalidOperationException($"The game didn't initialise its first content manager before SMAPI's {nameof(SGame)} constructor. This indicates an incompatible lifecycle change."); @@ -147,6 +147,7 @@ namespace StardewModdingAPI.Framework this.Monitor = monitor; this.Events = eventManager; this.Reflection = reflection; + this.JsonHelper = jsonHelper; this.OnGameInitialised = onGameInitialised; this.OnGameExiting = onGameExiting; Game1.input = new SInputState(); @@ -191,8 +192,7 @@ namespace StardewModdingAPI.Framework // NOTE: this method is called before the SGame constructor runs. Don't depend on anything being initialised at this point. if (this.ContentCore == null) { - this.ContentCore = new ContentCoordinator(serviceProvider, rootDirectory, Thread.CurrentThread.CurrentUICulture, SGame.MonitorDuringInitialisation, SGame.ReflectorDuringInitialisation); - SGame.MonitorDuringInitialisation = null; + this.ContentCore = new ContentCoordinator(serviceProvider, rootDirectory, Thread.CurrentThread.CurrentUICulture, SGame.ConstructorHack.Monitor, SGame.ConstructorHack.Reflection, SGame.ConstructorHack.JsonHelper); this.NextContentManagerIsMain = true; return this.ContentCore.CreateGameContentManager("Game1._temporaryContent"); } diff --git a/src/SMAPI/Framework/SGameConstructorHack.cs b/src/SMAPI/Framework/SGameConstructorHack.cs new file mode 100644 index 00000000..494bab99 --- /dev/null +++ b/src/SMAPI/Framework/SGameConstructorHack.cs @@ -0,0 +1,37 @@ +using StardewModdingAPI.Framework.Reflection; +using StardewModdingAPI.Toolkit.Serialisation; +using StardewValley; + +namespace StardewModdingAPI.Framework +{ + /// The static state to use while is initialising, which happens before the constructor runs. + internal class SGameConstructorHack + { + /********* + ** Accessors + *********/ + /// Encapsulates monitoring and logging. + public IMonitor Monitor { get; } + + /// Simplifies access to private game code. + public Reflector Reflection { get; } + + /// Encapsulates SMAPI's JSON file parsing. + public JsonHelper JsonHelper { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// Encapsulates monitoring and logging. + /// Simplifies access to private game code. + /// Encapsulates SMAPI's JSON file parsing. + public SGameConstructorHack(IMonitor monitor, Reflector reflection, JsonHelper jsonHelper) + { + this.Monitor = monitor; + this.Reflection = reflection; + this.JsonHelper = jsonHelper; + } + } +} diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index a894e831..634c5066 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -229,9 +229,8 @@ namespace StardewModdingAPI AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => AssemblyLoader.ResolveAssembly(e.Name); // override game - SGame.MonitorDuringInitialisation = this.Monitor; - SGame.ReflectorDuringInitialisation = this.Reflection; - this.GameInstance = new SGame(this.Monitor, this.Reflection, this.EventManager, this.InitialiseAfterGameStart, this.Dispose); + SGame.ConstructorHack = new SGameConstructorHack(this.Monitor, this.Reflection, this.Toolkit.JsonHelper); + this.GameInstance = new SGame(this.Monitor, this.Reflection, this.EventManager, this.Toolkit.JsonHelper, this.InitialiseAfterGameStart, this.Dispose); StardewValley.Program.gamePtr = this.GameInstance; // add exit handler @@ -1160,7 +1159,10 @@ namespace StardewModdingAPI string locale = Path.GetFileNameWithoutExtension(file.Name.ToLower().Trim()); try { - translations[locale] = jsonHelper.ReadJsonFile>(file.FullName); + if (jsonHelper.ReadJsonFileIfExists(file.FullName, out IDictionary data)) + translations[locale] = data; + else + metadata.LogAsMod($"Mod's i18n/{locale}.json file couldn't be parsed."); } catch (Exception ex) { diff --git a/src/SMAPI/StardewModdingAPI.csproj b/src/SMAPI/StardewModdingAPI.csproj index 0d0a5fe9..fc2d45ba 100644 --- a/src/SMAPI/StardewModdingAPI.csproj +++ b/src/SMAPI/StardewModdingAPI.csproj @@ -109,6 +109,7 @@ + diff --git a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs index de8d0f02..f1cce4a4 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/ModScanning/ModScanner.cs @@ -51,8 +51,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning { try { - manifest = this.JsonHelper.ReadJsonFile(manifestFile.FullName); - if (manifest == null) + if (!this.JsonHelper.ReadJsonFileIfExists(manifestFile.FullName, out manifest)) manifestError = "its manifest is invalid."; } catch (SParseException ex) diff --git a/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs b/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs index 3cabbab3..cc8eeb73 100644 --- a/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs +++ b/src/StardewModdingAPI.Toolkit/Serialisation/JsonHelper.cs @@ -32,10 +32,11 @@ namespace StardewModdingAPI.Toolkit.Serialisation /// Read a JSON file. /// The model type. /// The absolete file path. - /// Returns the deserialised model, or null if the file doesn't exist or is empty. - /// The given path is empty or invalid. - public TModel ReadJsonFile(string fullPath) - where TModel : class + /// The parsed content model. + /// Returns false if the file doesn't exist, else true. + /// The given is empty or invalid. + /// The file contains invalid JSON. + public bool ReadJsonFileIfExists(string fullPath, out TModel result) { // validate if (string.IsNullOrWhiteSpace(fullPath)) @@ -49,13 +50,15 @@ namespace StardewModdingAPI.Toolkit.Serialisation } catch (Exception ex) when (ex is DirectoryNotFoundException || ex is FileNotFoundException) { - return null; + result = default(TModel); + return false; } // deserialise model try { - return this.Deserialise(json); + result = this.Deserialise(json); + return true; } catch (Exception ex) { -- cgit From 9029633f7f1076415fcb9e76fd3e0e58357ddcec Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 11 Aug 2018 23:17:36 -0400 Subject: overhaul installer display (#554) The installer now validates preconditions earlier when possible, and after each step will reset the text and condense details from previous steps. This way players only see info for the current question to avoid confusion, and it's easier to add new steps. --- docs/release-notes.md | 3 + src/SMAPI.Installer/Framework/InstallerPaths.cs | 61 ++++ src/SMAPI.Installer/InteractiveInstaller.cs | 385 ++++++++++++--------- .../StardewModdingAPI.Installer.csproj | 1 + 4 files changed, 285 insertions(+), 165 deletions(-) create mode 100644 src/SMAPI.Installer/Framework/InstallerPaths.cs (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 9556d58c..342ff217 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,9 @@ ## 2.7 * For players: * Improved how mod issues are listed in the console and log. + * Revamped installer. It now... + * uses a new format that should be more intuitive for players; + * and validates requirements earlier. * Fixed custom festival maps always using spring tilesheets. * Fixed `player_add` command not recognising return scepter. * Fixed `player_add` command showing fish twice. diff --git a/src/SMAPI.Installer/Framework/InstallerPaths.cs b/src/SMAPI.Installer/Framework/InstallerPaths.cs new file mode 100644 index 00000000..d212876a --- /dev/null +++ b/src/SMAPI.Installer/Framework/InstallerPaths.cs @@ -0,0 +1,61 @@ +using System.IO; + +namespace StardewModdingAPI.Installer.Framework +{ + /// Manages paths for the SMAPI installer. + internal class InstallerPaths + { + /********* + ** Accessors + *********/ + /// The directory containing the installer files for the current platform. + public DirectoryInfo PackageDir { get; } + + /// The directory containing the installed game. + public DirectoryInfo GameDir { get; } + + /// The directory into which to install mods. + public DirectoryInfo ModsDir { get; } + + /// The full path to the directory containing the installer files for the current platform. + public string PackagePath => this.PackageDir.FullName; + + /// The full path to the directory containing the installed game. + public string GamePath => this.GameDir.FullName; + + /// The full path to the directory into which to install mods. + public string ModsPath => this.ModsDir.FullName; + + /// The full path to the installed SMAPI executable file. + public string ExecutablePath { get; } + + /// The full path to the vanilla game launcher on Linux/Mac. + public string UnixLauncherPath { get; } + + /// The full path to the installed SMAPI launcher on Linux/Mac before it's renamed. + public string UnixSmapiLauncherPath { get; } + + /// The full path to the vanilla game launcher on Linux/Mac after SMAPI is installed. + public string UnixBackupLauncherPath { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The directory path containing the installer files for the current platform. + /// The directory path for the installed game. + /// The name of the game's executable file for the current platform. + public InstallerPaths(DirectoryInfo packageDir, DirectoryInfo gameDir, string gameExecutableName) + { + this.PackageDir = packageDir; + this.GameDir = gameDir; + this.ModsDir = new DirectoryInfo(Path.Combine(gameDir.FullName, "Mods")); + + this.ExecutablePath = Path.Combine(gameDir.FullName, gameExecutableName); + this.UnixLauncherPath = Path.Combine(gameDir.FullName, "StardewValley"); + this.UnixSmapiLauncherPath = Path.Combine(gameDir.FullName, "StardewModdingAPI"); + this.UnixBackupLauncherPath = Path.Combine(gameDir.FullName, "StardewValley-original"); + } + } +} diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index f39486e1..b686e5bc 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Threading; using Microsoft.Win32; using StardewModdingApi.Installer.Enums; +using StardewModdingAPI.Installer.Framework; using StardewModdingAPI.Internal; using StardewModdingAPI.Internal.ConsoleWriting; @@ -168,6 +169,9 @@ namespace StardewModdingApi.Installer /// public void Run(string[] args) { + /********* + ** Step 1: initial setup + *********/ /**** ** Get platform & set window title ****/ @@ -175,6 +179,9 @@ namespace StardewModdingApi.Installer Console.Title = $"SMAPI {this.GetDisplayVersion(this.GetType().Assembly.GetName().Version)} installer on {platform} {EnvironmentUtility.GetFriendlyPlatformName(platform)}"; Console.WriteLine(); + /**** + ** Check if correct installer + ****/ #if SMAPI_FOR_WINDOWS if (platform == Platform.Linux || platform == Platform.Mac) { @@ -182,8 +189,39 @@ namespace StardewModdingApi.Installer Console.ReadLine(); return; } +#else + if (platform == Platform.Windows) + { + this.PrintError($"This is the installer for Linux/Mac. Run the 'install on Windows.exe' file instead."); + Console.ReadLine(); + return; + } #endif + /**** + ** Check Windows dependencies + ****/ + if (platform == Platform.Windows) + { + // .NET Framework 4.5+ + if (!this.HasNetFramework45(platform)) + { + this.PrintError(Environment.OSVersion.Version >= this.Windows7Version + ? "Please install the latest version of .NET Framework before installing SMAPI." // Windows 7+ + : "Please install .NET Framework 4.5 before installing SMAPI." // Windows Vista or earlier + ); + this.PrintError("See the download page at https://www.microsoft.com/net/download/framework for details."); + Console.ReadLine(); + return; + } + if (!this.HasXna(platform)) + { + this.PrintError("You don't seem to have XNA Framework installed. Please run the game at least once before installing SMAPI, so it can perform its first-time setup."); + Console.ReadLine(); + return; + } + } + /**** ** read command-line arguments ****/ @@ -205,230 +243,242 @@ namespace StardewModdingApi.Installer gamePathArg = args[pathIndex]; } - /**** - ** collect details - ****/ - // get game path - DirectoryInfo installDir = this.InteractivelyGetInstallPath(platform, gamePathArg); - if (installDir == null) - { - this.PrintError("Failed finding your game path."); - Console.ReadLine(); - return; - } - // get folders - DirectoryInfo packageDir = platform.IsMono() - ? new DirectoryInfo(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)) // installer runs from internal folder on Mono - : new DirectoryInfo(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "internal", "Windows")); - DirectoryInfo modsDir = new DirectoryInfo(Path.Combine(installDir.FullName, "Mods")); - var paths = new + /********* + ** Step 2: find game folder + *********/ + InstallerPaths paths; { - executable = Path.Combine(installDir.FullName, EnvironmentUtility.GetExecutableName(platform)), - unixSmapiLauncher = Path.Combine(installDir.FullName, "StardewModdingAPI"), - unixLauncher = Path.Combine(installDir.FullName, "StardewValley"), - unixLauncherBackup = Path.Combine(installDir.FullName, "StardewValley-original") - }; + /**** + ** print header + ****/ + this.PrintInfo("Hi there! I'll help you install or remove SMAPI. Just a few questions first."); + this.PrintDebug("----------------------------------------------------------------------------"); + Console.WriteLine(); - // show output - this.PrintInfo($"Your game folder: {installDir}."); + /**** + ** collect details + ****/ + // get game path + this.PrintInfo("Where is your game folder?"); + DirectoryInfo installDir = this.InteractivelyGetInstallPath(platform, gamePathArg); + if (installDir == null) + { + this.PrintError("Failed finding your game path."); + Console.ReadLine(); + return; + } - /**** - ** validate assumptions - ****/ - if (!packageDir.Exists) - { - this.PrintError(platform == Platform.Windows && packageDir.FullName.Contains(Path.GetTempPath()) && packageDir.FullName.Contains(".zip") - ? "The installer is missing some files. It looks like you're running the installer from inside the downloaded zip; make sure you unzip the downloaded file first, then run the installer from the unzipped folder." - : $"The 'internal/{packageDir.Name}' package folder is missing (should be at {packageDir})." - ); - Console.ReadLine(); - return; - } - if (!File.Exists(paths.executable)) - { - this.PrintError("The detected game install path doesn't contain a Stardew Valley executable."); - Console.ReadLine(); - return; + // get folders + DirectoryInfo packageDir = platform.IsMono() + ? new DirectoryInfo(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)) // installer runs from internal folder on Mono + : new DirectoryInfo(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "internal", "Windows")); + paths = new InstallerPaths(packageDir, installDir, EnvironmentUtility.GetExecutableName(platform)); } + Console.Clear(); - /**** - ** validate Windows dependencies - ****/ - if (platform == Platform.Windows) + + /********* + ** Step 3: validate assumptions + *********/ { - // .NET Framework 4.5+ - if (!this.HasNetFramework45(platform)) + if (!paths.PackageDir.Exists) { - this.PrintError(Environment.OSVersion.Version >= this.Windows7Version - ? "Please install the latest version of .NET Framework before installing SMAPI." // Windows 7+ - : "Please install .NET Framework 4.5 before installing SMAPI." // Windows Vista or earlier + this.PrintError(platform == Platform.Windows && paths.PackagePath.Contains(Path.GetTempPath()) && paths.PackagePath.Contains(".zip") + ? "The installer is missing some files. It looks like you're running the installer from inside the downloaded zip; make sure you unzip the downloaded file first, then run the installer from the unzipped folder." + : $"The 'internal/{paths.PackageDir.Name}' package folder is missing (should be at {paths.PackagePath})." ); - this.PrintError("See the download page at https://www.microsoft.com/net/download/framework for details."); Console.ReadLine(); return; } - if (!this.HasXna(platform)) + + if (!File.Exists(paths.ExecutablePath)) { - this.PrintError("You don't seem to have XNA Framework installed. Please run the game at least once before installing SMAPI, so it can perform its first-time setup."); + this.PrintError("The detected game install path doesn't contain a Stardew Valley executable."); Console.ReadLine(); return; } } - Console.WriteLine(); - /**** - ** ask user what to do - ****/ + /********* + ** Step 4: ask what to do + *********/ ScriptAction action; - - if (installArg) - action = ScriptAction.Install; - else if (uninstallArg) - action = ScriptAction.Uninstall; - else { - this.PrintInfo("You can...."); - this.PrintInfo("[1] Install SMAPI."); - this.PrintInfo("[2] Uninstall SMAPI."); + /**** + ** print header + ****/ + this.PrintInfo("Hi there! I'll help you install or remove SMAPI. Just one question first."); + this.PrintDebug($"Game path: {paths.GamePath}"); + this.PrintDebug("----------------------------------------------------------------------------"); Console.WriteLine(); - string choice = this.InteractivelyChoose("What do you want to do? Type 1 or 2, then press enter.", "1", "2"); - switch (choice) + /**** + ** ask what to do + ****/ + if (installArg) + action = ScriptAction.Install; + else if (uninstallArg) + action = ScriptAction.Uninstall; + else { - case "1": - action = ScriptAction.Install; - break; - case "2": - action = ScriptAction.Uninstall; - break; - default: - throw new InvalidOperationException($"Unexpected action key '{choice}'."); + this.PrintInfo("What do you want to do?"); + Console.WriteLine(); + this.PrintInfo("[1] Install SMAPI."); + this.PrintInfo("[2] Uninstall SMAPI."); + Console.WriteLine(); + + string choice = this.InteractivelyChoose("What do you want to do? Type 1 or 2, then press enter.", new[] { "1", "2" }); + switch (choice) + { + case "1": + action = ScriptAction.Install; + break; + case "2": + action = ScriptAction.Uninstall; + break; + default: + throw new InvalidOperationException($"Unexpected action key '{choice}'."); + } } - Console.WriteLine(); } + Console.Clear(); - /**** - ** Always uninstall old files - ****/ - // restore game launcher - if (platform.IsMono() && File.Exists(paths.unixLauncherBackup)) - { - this.PrintDebug("Removing SMAPI launcher..."); - this.InteractivelyDelete(paths.unixLauncher); - File.Move(paths.unixLauncherBackup, paths.unixLauncher); - } - // remove old files - string[] removePaths = this.GetUninstallPaths(installDir, modsDir) - .Where(path => Directory.Exists(path) || File.Exists(path)) - .ToArray(); - if (removePaths.Any()) + /********* + ** Step 5: apply + *********/ { - this.PrintDebug(action == ScriptAction.Install ? "Removing previous SMAPI files..." : "Removing SMAPI files..."); - foreach (string path in removePaths) - this.InteractivelyDelete(path); - } + /**** + ** print header + ****/ + this.PrintInfo($"That's all I need! I'll {action.ToString().ToLower()} SMAPI now."); + this.PrintDebug($"Game path: {paths.GamePath}"); + this.PrintDebug("----------------------------------------------------------------------------"); + Console.WriteLine(); - /**** - ** Install new files - ****/ - if (action == ScriptAction.Install) - { - // copy SMAPI files to game dir - this.PrintDebug("Adding SMAPI files..."); - foreach (FileInfo sourceFile in packageDir.EnumerateFiles().Where(this.ShouldCopyFile)) + /**** + ** Always uninstall old files + ****/ + // restore game launcher + if (platform.IsMono() && File.Exists(paths.UnixBackupLauncherPath)) { - if (sourceFile.Name == this.InstallerFileName) - continue; - - string targetPath = Path.Combine(installDir.FullName, sourceFile.Name); - this.InteractivelyDelete(targetPath); - sourceFile.CopyTo(targetPath); + this.PrintDebug("Removing SMAPI launcher..."); + this.InteractivelyDelete(paths.UnixLauncherPath); + File.Move(paths.UnixBackupLauncherPath, paths.UnixLauncherPath); } - // replace mod launcher (if possible) - if (platform.IsMono()) + // remove old files + string[] removePaths = this.GetUninstallPaths(paths.GameDir, paths.ModsDir) + .Where(path => Directory.Exists(path) || File.Exists(path)) + .ToArray(); + if (removePaths.Any()) { - this.PrintDebug("Safely replacing game launcher..."); - if (File.Exists(paths.unixLauncher)) - { - if (!File.Exists(paths.unixLauncherBackup)) - File.Move(paths.unixLauncher, paths.unixLauncherBackup); - else - this.InteractivelyDelete(paths.unixLauncher); - } - - File.Move(paths.unixSmapiLauncher, paths.unixLauncher); + this.PrintDebug(action == ScriptAction.Install ? "Removing previous SMAPI files..." : "Removing SMAPI files..."); + foreach (string path in removePaths) + this.InteractivelyDelete(path); } - // create mods directory (if needed) - if (!modsDir.Exists) + /**** + ** Install new files + ****/ + if (action == ScriptAction.Install) { - this.PrintDebug("Creating mods directory..."); - modsDir.Create(); - } + // copy SMAPI files to game dir + this.PrintDebug("Adding SMAPI files..."); + foreach (FileInfo sourceFile in paths.PackageDir.EnumerateFiles().Where(this.ShouldCopyFile)) + { + if (sourceFile.Name == this.InstallerFileName) + continue; - // add or replace bundled mods - modsDir.Create(); - DirectoryInfo packagedModsDir = new DirectoryInfo(Path.Combine(packageDir.FullName, "Mods")); - if (packagedModsDir.Exists && packagedModsDir.EnumerateDirectories().Any()) - { - this.PrintDebug("Adding bundled mods..."); + string targetPath = Path.Combine(paths.GameDir.FullName, sourceFile.Name); + this.InteractivelyDelete(targetPath); + sourceFile.CopyTo(targetPath); + } - // special case: rename Omegasis' SaveBackup mod + // replace mod launcher (if possible) + if (platform.IsMono()) { - DirectoryInfo oldFolder = new DirectoryInfo(Path.Combine(modsDir.FullName, "SaveBackup")); - DirectoryInfo newFolder = new DirectoryInfo(Path.Combine(modsDir.FullName, "AdvancedSaveBackup")); - FileInfo manifest = new FileInfo(Path.Combine(oldFolder.FullName, "manifest.json")); - if (manifest.Exists && !newFolder.Exists && File.ReadLines(manifest.FullName).Any(p => p.IndexOf("Omegasis", StringComparison.InvariantCultureIgnoreCase) != -1)) + this.PrintDebug("Safely replacing game launcher..."); + if (File.Exists(paths.UnixLauncherPath)) { - this.PrintDebug($" moving {oldFolder.Name} to {newFolder.Name}..."); - this.Move(oldFolder, newFolder.FullName); + if (!File.Exists(paths.UnixBackupLauncherPath)) + File.Move(paths.UnixLauncherPath, paths.UnixBackupLauncherPath); + else + this.InteractivelyDelete(paths.UnixLauncherPath); } + + File.Move(paths.UnixSmapiLauncherPath, paths.UnixLauncherPath); } - // add bundled mods - foreach (DirectoryInfo sourceDir in packagedModsDir.EnumerateDirectories()) + // create mods directory (if needed) + if (!paths.ModsDir.Exists) { - this.PrintDebug($" adding {sourceDir.Name}..."); + this.PrintDebug("Creating mods directory..."); + paths.ModsDir.Create(); + } - // init/clear target dir - DirectoryInfo targetDir = new DirectoryInfo(Path.Combine(modsDir.FullName, sourceDir.Name)); - if (targetDir.Exists) + // add or replace bundled mods + DirectoryInfo packagedModsDir = new DirectoryInfo(Path.Combine(paths.PackageDir.FullName, "Mods")); + if (packagedModsDir.Exists && packagedModsDir.EnumerateDirectories().Any()) + { + this.PrintDebug("Adding bundled mods..."); + + // special case: rename Omegasis' SaveBackup mod { - this.ProtectBundledFiles.TryGetValue(targetDir.Name, out HashSet protectedFiles); - foreach (FileSystemInfo entry in targetDir.EnumerateFileSystemInfos()) + DirectoryInfo oldFolder = new DirectoryInfo(Path.Combine(paths.ModsDir.FullName, "SaveBackup")); + DirectoryInfo newFolder = new DirectoryInfo(Path.Combine(paths.ModsDir.FullName, "AdvancedSaveBackup")); + FileInfo manifest = new FileInfo(Path.Combine(oldFolder.FullName, "manifest.json")); + if (manifest.Exists && !newFolder.Exists && File.ReadLines(manifest.FullName).Any(p => p.IndexOf("Omegasis", StringComparison.InvariantCultureIgnoreCase) != -1)) { - if (protectedFiles == null || !protectedFiles.Contains(entry.Name)) - this.InteractivelyDelete(entry.FullName); + this.PrintDebug($" moving {oldFolder.Name} to {newFolder.Name}..."); + this.Move(oldFolder, newFolder.FullName); } } - else - targetDir.Create(); - // copy files - foreach (FileInfo sourceFile in sourceDir.EnumerateFiles().Where(this.ShouldCopyFile)) - sourceFile.CopyTo(Path.Combine(targetDir.FullName, sourceFile.Name)); + // add bundled mods + foreach (DirectoryInfo sourceDir in packagedModsDir.EnumerateDirectories()) + { + this.PrintDebug($" adding {sourceDir.Name}..."); + + // init/clear target dir + DirectoryInfo targetDir = new DirectoryInfo(Path.Combine(paths.ModsDir.FullName, sourceDir.Name)); + if (targetDir.Exists) + { + this.ProtectBundledFiles.TryGetValue(targetDir.Name, out HashSet protectedFiles); + foreach (FileSystemInfo entry in targetDir.EnumerateFileSystemInfos()) + { + if (protectedFiles == null || !protectedFiles.Contains(entry.Name)) + this.InteractivelyDelete(entry.FullName); + } + } + else + targetDir.Create(); + + // copy files + foreach (FileInfo sourceFile in sourceDir.EnumerateFiles().Where(this.ShouldCopyFile)) + sourceFile.CopyTo(Path.Combine(targetDir.FullName, sourceFile.Name)); + } } - } - // remove obsolete appdata mods - this.InteractivelyRemoveAppDataMods(modsDir, packagedModsDir); + // remove obsolete appdata mods + this.InteractivelyRemoveAppDataMods(paths.ModsDir, packagedModsDir); + } } Console.WriteLine(); Console.WriteLine(); - /**** - ** final instructions - ****/ + + /********* + ** Step 6: final instructions + *********/ if (platform == Platform.Windows) { if (action == ScriptAction.Install) { this.PrintSuccess("SMAPI is installed! If you use Steam, set your launch options to enable achievements (see smapi.io/install):"); - this.PrintSuccess($" \"{Path.Combine(installDir.FullName, "StardewModdingAPI.exe")}\" %command%"); + this.PrintSuccess($" \"{Path.Combine(paths.GamePath, "StardewModdingAPI.exe")}\" %command%"); Console.WriteLine(); this.PrintSuccess("If you don't use Steam, launch StardewModdingAPI.exe in your game folder to play with mods."); } @@ -594,17 +644,22 @@ namespace StardewModdingApi.Installer } /// Interactively ask the user to choose a value. + /// A callback which prints a message to the console. /// The message to print. /// The allowed options (not case sensitive). - private string InteractivelyChoose(string message, params string[] options) + /// The indentation to prefix to output. + private string InteractivelyChoose(string message, string[] options, string indent = "", Action print = null) { + print = print ?? this.PrintInfo; + while (true) { - this.PrintInfo(message); + print(indent + message); + Console.Write(indent); string input = Console.ReadLine()?.Trim().ToLowerInvariant(); if (!options.Contains(input)) { - this.PrintInfo("That's not a valid option."); + print($"{indent}That's not a valid option."); continue; } return input; diff --git a/src/SMAPI.Installer/StardewModdingAPI.Installer.csproj b/src/SMAPI.Installer/StardewModdingAPI.Installer.csproj index 2ad7e82a..e82c6093 100644 --- a/src/SMAPI.Installer/StardewModdingAPI.Installer.csproj +++ b/src/SMAPI.Installer/StardewModdingAPI.Installer.csproj @@ -40,6 +40,7 @@ Properties\GlobalAssemblyInfo.cs + -- cgit From 4e49ce8547c926f13589807a1ac70a3406a1cf01 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 11 Aug 2018 23:19:21 -0400 Subject: let Linux/Mac players choose the console scheme when installing (#554) --- docs/release-notes.md | 3 +- src/SMAPI.Installer/InteractiveInstaller.cs | 94 +++++++++++++++++++++++++++-- 2 files changed, 90 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 342ff217..340ebb62 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -3,7 +3,8 @@ * For players: * Improved how mod issues are listed in the console and log. * Revamped installer. It now... - * uses a new format that should be more intuitive for players; + * uses a new format that should be more intuitive; + * lets players on Linux/Mac choose the console color scheme (SMAPI will auto-detect it on Windows); * and validates requirements earlier. * Fixed custom festival maps always using spring tilesheets. * Fixed `player_add` command not recognising return scepter. diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index b686e5bc..bb953865 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -136,7 +136,7 @@ namespace StardewModdingApi.Installer } /// Handles writing color-coded text to the console. - private readonly ColorfulConsoleWriter ConsoleWriter; + private ColorfulConsoleWriter ConsoleWriter; /********* @@ -245,7 +245,55 @@ namespace StardewModdingApi.Installer /********* - ** Step 2: find game folder + ** Step 2: choose a theme (can't auto-detect on Linux/Mac) + *********/ + MonitorColorScheme scheme = MonitorColorScheme.AutoDetect; + if (platform == Platform.Linux || platform == Platform.Mac) + { + /**** + ** print header + ****/ + this.PrintPlain("Hi there! I'll help you install or remove SMAPI. Just a few questions first."); + this.PrintPlain("----------------------------------------------------------------------------"); + Console.WriteLine(); + + /**** + ** show theme selector + ****/ + // get theme writers + var lightBackgroundWriter = new ColorfulConsoleWriter(EnvironmentUtility.DetectPlatform(), MonitorColorScheme.LightBackground); + var darkDarkgroundWriter = new ColorfulConsoleWriter(EnvironmentUtility.DetectPlatform(), MonitorColorScheme.DarkBackground); + + // print question + this.PrintPlain("Which text looks more readable?"); + Console.WriteLine(); + Console.Write(" [1] "); + lightBackgroundWriter.WriteLine("Dark text on light background", ConsoleLogLevel.Info); + Console.Write(" [2] "); + darkDarkgroundWriter.WriteLine("Light text on dark background", ConsoleLogLevel.Info); + Console.WriteLine(); + + // handle choice + string choice = this.InteractivelyChoose("Type 1 or 2, then press enter.", new[] { "1", "2" }); + switch (choice) + { + case "1": + scheme = MonitorColorScheme.LightBackground; + this.ConsoleWriter = lightBackgroundWriter; + break; + case "2": + scheme = MonitorColorScheme.DarkBackground; + this.ConsoleWriter = darkDarkgroundWriter; + break; + default: + throw new InvalidOperationException($"Unexpected action key '{choice}'."); + } + } + Console.Clear(); + + + /********* + ** Step 3: find game folder *********/ InstallerPaths paths; { @@ -253,6 +301,7 @@ namespace StardewModdingApi.Installer ** print header ****/ this.PrintInfo("Hi there! I'll help you install or remove SMAPI. Just a few questions first."); + this.PrintDebug($"Color scheme: {this.GetDisplayText(scheme)}"); this.PrintDebug("----------------------------------------------------------------------------"); Console.WriteLine(); @@ -279,7 +328,7 @@ namespace StardewModdingApi.Installer /********* - ** Step 3: validate assumptions + ** Step 4: validate assumptions *********/ { if (!paths.PackageDir.Exists) @@ -302,7 +351,7 @@ namespace StardewModdingApi.Installer /********* - ** Step 4: ask what to do + ** Step 5: ask what to do *********/ ScriptAction action; { @@ -311,6 +360,7 @@ namespace StardewModdingApi.Installer ****/ this.PrintInfo("Hi there! I'll help you install or remove SMAPI. Just one question first."); this.PrintDebug($"Game path: {paths.GamePath}"); + this.PrintDebug($"Color scheme: {this.GetDisplayText(scheme)}"); this.PrintDebug("----------------------------------------------------------------------------"); Console.WriteLine(); @@ -347,7 +397,7 @@ namespace StardewModdingApi.Installer /********* - ** Step 5: apply + ** Step 6: apply *********/ { /**** @@ -355,6 +405,7 @@ namespace StardewModdingApi.Installer ****/ this.PrintInfo($"That's all I need! I'll {action.ToString().ToLower()} SMAPI now."); this.PrintDebug($"Game path: {paths.GamePath}"); + this.PrintDebug($"Color scheme: {this.GetDisplayText(scheme)}"); this.PrintDebug("----------------------------------------------------------------------------"); Console.WriteLine(); @@ -460,6 +511,16 @@ namespace StardewModdingApi.Installer foreach (FileInfo sourceFile in sourceDir.EnumerateFiles().Where(this.ShouldCopyFile)) sourceFile.CopyTo(Path.Combine(targetDir.FullName, sourceFile.Name)); } + + // set SMAPI's color scheme if defined + if (scheme != MonitorColorScheme.AutoDetect) + { + string configPath = Path.Combine(paths.GamePath, "StardewModdingAPI.config.json"); + string text = File + .ReadAllText(configPath) + .Replace(@"""ColorScheme"": ""AutoDetect""", $@"""ColorScheme"": ""{scheme}"""); + File.WriteAllText(configPath, text); + } } // remove obsolete appdata mods @@ -471,7 +532,7 @@ namespace StardewModdingApi.Installer /********* - ** Step 6: final instructions + ** Step 7: final instructions *********/ if (platform == Platform.Windows) { @@ -510,6 +571,23 @@ namespace StardewModdingApi.Installer return str; } + /// Get the display text for a color scheme. + /// The color scheme. + private string GetDisplayText(MonitorColorScheme scheme) + { + switch (scheme) + { + case MonitorColorScheme.AutoDetect: + return "auto-detect"; + case MonitorColorScheme.DarkBackground: + return "light text on dark background"; + case MonitorColorScheme.LightBackground: + return "dark text on light background"; + default: + return scheme.ToString(); + } + } + /// Get the value of a key in the Windows HKLM registry. /// The full path of the registry key relative to HKLM. /// The name of the value. @@ -536,6 +614,10 @@ namespace StardewModdingApi.Installer return (string)openKey.GetValue(name); } + /// Print a message without formatting. + /// The text to print. + private void PrintPlain(string text) => Console.WriteLine(text); + /// Print a debug message. /// The text to print. private void PrintDebug(string text) => this.ConsoleWriter.WriteLine(text, ConsoleLogLevel.Debug); -- cgit From 99ebac7e071ae3548093f57e0f0699fb29b25bef Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 11 Aug 2018 23:52:45 -0400 Subject: add asset propagation for dialogue changes (#580) --- docs/release-notes.md | 1 + src/SMAPI/Metadata/CoreAssetPropagator.cs | 29 +++++++++++++++++++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 340ebb62..4c5671ec 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -13,6 +13,7 @@ * For modders: * Added support for `.json` data files in the content API (including Content Patcher). + * Added asset propagation for dialogue changes through the content API. * Added `--mods-path` command-line argument to allow switching between mod folders. * All enums are now JSON-serialised by name, since that's more user-friendly. Previously only certain predefined enums were serialised that way. JSON files which already have integer enums will still be parsed fine. * Fixed false compatibility error when constructing multidimensional arrays. diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 12abeb10..3821e4ab 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -320,14 +320,16 @@ namespace StardewModdingAPI.Metadata return this.ReloadNpcSprites(content, key, monster: true); if (key.StartsWith(this.GetNormalisedPath("LooseSprites\\Fence"), StringComparison.InvariantCultureIgnoreCase)) - return this.ReloadFenceTextures(content, key); + return this.ReloadFenceTextures(key); if (this.IsInFolder(key, "Portraits")) return this.ReloadNpcPortraits(content, key); // dynamic data + if (this.IsInFolder(key, "Characters\\Dialogue")) + return this.ReloadNpcDialogue(key); if (this.IsInFolder(key, "Characters\\schedules")) - return this.ReloadNpcSchedules(content, key); + return this.ReloadNpcSchedules(key); return false; } @@ -416,10 +418,9 @@ namespace StardewModdingAPI.Metadata } /// Reload the sprites for a fence type. - /// The content manager through which to reload the asset. /// The asset key to reload. /// Returns whether any textures were reloaded. - private bool ReloadFenceTextures(LocalizedContentManager content, string key) + private bool ReloadFenceTextures(string key) { // get fence type if (!int.TryParse(this.GetSegments(key)[1].Substring("Fence".Length), out int fenceType)) @@ -508,11 +509,27 @@ namespace StardewModdingAPI.Metadata /**** ** Reload data methods ****/ + /// Reload the dialogue data for matching NPCs. + /// The asset key to reload. + /// Returns whether any assets were reloaded. + private bool ReloadNpcDialogue(string key) + { + // get NPCs + string name = Path.GetFileName(key); + NPC[] villagers = this.GetCharacters().Where(npc => npc.Name == name && npc.isVillager()).ToArray(); + if (!villagers.Any()) + return false; + + // update dialogue + foreach (NPC villager in villagers) + villager.resetSeasonalDialogue(); // doesn't only affect seasonal dialogue + return true; + } + /// Reload the schedules for matching NPCs. - /// The content manager through which to reload the asset. /// The asset key to reload. /// Returns whether any assets were reloaded. - private bool ReloadNpcSchedules(LocalizedContentManager content, string key) + private bool ReloadNpcSchedules(string key) { // get NPCs string name = Path.GetFileName(key); -- cgit From a6f6b9cad7a83e19b84ef2b5b2c7c9ae54227de4 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Aug 2018 00:41:53 -0400 Subject: fix asset propagation for child sprites (#573) --- docs/release-notes.md | 2 +- src/SMAPI/Metadata/CoreAssetPropagator.cs | 37 ++++++++++++++++++++----------- 2 files changed, 25 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 4c5671ec..9ac52ab1 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -13,7 +13,7 @@ * For modders: * Added support for `.json` data files in the content API (including Content Patcher). - * Added asset propagation for dialogue changes through the content API. + * Fixed changes through the content API not propagating correctly for dialogue and child sprites. * Added `--mods-path` command-line argument to allow switching between mod folders. * All enums are now JSON-serialised by name, since that's more user-friendly. Previously only certain predefined enums were serialised that way. JSON files which already have integer enums will still be parsed fine. * Fixed false compatibility error when constructing multidimensional arrays. diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 3821e4ab..e9d66975 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -313,13 +313,10 @@ namespace StardewModdingAPI.Metadata if (this.IsInFolder(key, "Buildings")) return this.ReloadBuildings(content, key); - if (this.IsInFolder(key, "Characters")) - return this.ReloadNpcSprites(content, key, monster: false); + if (this.IsInFolder(key, "Characters") || this.IsInFolder(key, "Characters\\Monsters")) + return this.ReloadNpcSprites(content, key); - if (this.IsInFolder(key, "Characters\\Monsters")) - return this.ReloadNpcSprites(content, key, monster: true); - - if (key.StartsWith(this.GetNormalisedPath("LooseSprites\\Fence"), StringComparison.InvariantCultureIgnoreCase)) + if (this.KeyStartsWith(key, "LooseSprites\\Fence")) return this.ReloadFenceTextures(key); if (this.IsInFolder(key, "Portraits")) @@ -328,6 +325,7 @@ namespace StardewModdingAPI.Metadata // dynamic data if (this.IsInFolder(key, "Characters\\Dialogue")) return this.ReloadNpcDialogue(key); + if (this.IsInFolder(key, "Characters\\schedules")) return this.ReloadNpcSchedules(key); @@ -447,13 +445,13 @@ namespace StardewModdingAPI.Metadata /// Reload the sprites for matching NPCs. /// The content manager through which to reload the asset. /// The asset key to reload. - /// Whether to match monsters (true) or non-monsters (false). /// Returns whether any textures were reloaded. - private bool ReloadNpcSprites(LocalizedContentManager content, string key, bool monster) + private bool ReloadNpcSprites(LocalizedContentManager content, string key) { // get NPCs - string name = this.GetNpcNameFromFileName(Path.GetFileName(key)); - NPC[] characters = this.GetCharacters().Where(npc => npc.Name == name && npc.IsMonster == monster).ToArray(); + NPC[] characters = this.GetCharacters() + .Where(npc => this.GetNormalisedPath(npc.Sprite.textureName.Value) == key) + .ToArray(); if (!characters.Any()) return false; @@ -471,15 +469,20 @@ namespace StardewModdingAPI.Metadata private bool ReloadNpcPortraits(LocalizedContentManager content, string key) { // get NPCs - string name = this.GetNpcNameFromFileName(Path.GetFileName(key)); - NPC[] villagers = this.GetCharacters().Where(npc => npc.Name == name && npc.isVillager()).ToArray(); + NPC[] villagers = this.GetCharacters() + .Where(npc => npc.isVillager() && this.GetNormalisedPath($"Portraits\\{this.Reflection.GetMethod(npc, "getTextureName").Invoke()}") == key) + .ToArray(); if (!villagers.Any()) return false; // update portrait Texture2D texture = content.Load(key); foreach (NPC villager in villagers) + { + villager.resetPortrait(); villager.Portrait = texture; + } + return true; } @@ -624,6 +627,14 @@ namespace StardewModdingAPI.Metadata } } + /// Get whether a key starts with a substring after the substring is normalised. + /// The key to check. + /// The substring to normalise and find. + private bool KeyStartsWith(string key, string rawSubstring) + { + return key.StartsWith(this.GetNormalisedPath(rawSubstring), StringComparison.InvariantCultureIgnoreCase); + } + /// Get whether a normalised asset key is in the given folder. /// The normalised asset key (like Animals/cat). /// The key folder (like Animals); doesn't need to be normalised. @@ -631,7 +642,7 @@ namespace StardewModdingAPI.Metadata private bool IsInFolder(string key, string folder, bool allowSubfolders = false) { return - key.StartsWith(this.GetNormalisedPath($"{folder}\\"), StringComparison.InvariantCultureIgnoreCase) + this.KeyStartsWith(key, $"{folder}\\") && (allowSubfolders || this.CountSegments(key) == this.CountSegments(folder) + 1); } -- cgit From 13f9a4d8d2155463f9ceaa41dd15d4526927476e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Aug 2018 00:51:27 -0400 Subject: fix redundant text (#554) --- src/SMAPI.Installer/InteractiveInstaller.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index bb953865..0aac1da2 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -379,7 +379,7 @@ namespace StardewModdingApi.Installer this.PrintInfo("[2] Uninstall SMAPI."); Console.WriteLine(); - string choice = this.InteractivelyChoose("What do you want to do? Type 1 or 2, then press enter.", new[] { "1", "2" }); + string choice = this.InteractivelyChoose("Type 1 or 2, then press enter.", new[] { "1", "2" }); switch (choice) { case "1": -- cgit From f7111a2488bd16b30c28be1025e7dbf9c4ca1306 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Aug 2018 01:31:52 -0400 Subject: add asset propagation for map tilesheets (#570) --- docs/release-notes.md | 5 ++++- src/SMAPI/Metadata/CoreAssetPropagator.cs | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index 9ac52ab1..ab47b5c7 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -13,7 +13,10 @@ * For modders: * Added support for `.json` data files in the content API (including Content Patcher). - * Fixed changes through the content API not propagating correctly for dialogue and child sprites. + * Added automatic propagation when changing assets through the content API for... + * child sprites; + * dialogue; + * map tilesheets. * Added `--mods-path` command-line argument to allow switching between mod folders. * All enums are now JSON-serialised by name, since that's more user-friendly. Previously only certain predefined enums were serialised that way. JSON files which already have integer enums will still be parsed fine. * Fixed false compatibility error when constructing multidimensional arrays. diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index e9d66975..8487b6be 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -13,6 +13,7 @@ using StardewValley.Menus; using StardewValley.Objects; using StardewValley.Projectiles; using StardewValley.TerrainFeatures; +using xTile.Tiles; namespace StardewModdingAPI.Metadata { @@ -63,6 +64,23 @@ namespace StardewModdingAPI.Metadata /// Returns any non-null value to indicate an asset was loaded. private object PropagateImpl(LocalizedContentManager content, string key) { + /**** + ** Special case: current map tilesheet + ** We only need to do this for the current location, since tilesheets are reloaded when you enter a location. + ** Just in case, we should still propagate by key even if a tilesheet is matched. + ****/ + if (Game1.currentLocation?.map?.TileSheets != null) + { + foreach (TileSheet tilesheet in Game1.currentLocation.map.TileSheets) + { + if (this.GetNormalisedPath(tilesheet.ImageSource) == key) + Game1.mapDisplayDevice.LoadTileSheet(tilesheet); + } + } + + /**** + ** Propagate by key + ****/ Reflector reflection = this.Reflection; switch (key.ToLower().Replace("/", "\\")) // normalised key so we can compare statically { -- cgit From 36d20ce4ffca04cae05ef3b3fb286365ccec617d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 12 Aug 2018 01:36:29 -0400 Subject: bump minimum game version --- src/SMAPI/Constants.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index c7cd6e41..b41fb89d 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -32,7 +32,7 @@ namespace StardewModdingAPI public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("2.6.0"); /// The minimum supported version of Stardew Valley. - public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.3.27"); + public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.3.28"); /// The maximum supported version of Stardew Valley. public static ISemanticVersion MaximumGameVersion { get; } = null; -- cgit From 3299d25ee3d112d14475361eae294f64b9859efa Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 14 Aug 2018 12:07:15 -0400 Subject: drop backwards compatibility in update-check API Update checks from older versions of SMAPI are never useful now that Stardew Valley 1.3 is released: older versions of SMAPI won't launch in Stardew Valley 1.3 (so they won't check for updates), and newer versions of SMAPI/mods won't work with older versions of the game. --- docs/release-notes.md | 4 ++ src/SMAPI.Web/Controllers/ModsApiController.cs | 41 +++----------------- .../Framework/Clients/WebApi/ModEntryModel.cs | 44 ---------------------- .../Framework/Clients/WebApi/ModSeachModel.cs | 5 --- 4 files changed, 10 insertions(+), 84 deletions(-) (limited to 'src') diff --git a/docs/release-notes.md b/docs/release-notes.md index ab47b5c7..0ffcc591 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -23,6 +23,10 @@ * Fixed `.ToSButton()` methods not being public. * Updated compatibility list. +* For SMAPI developers: + * Dropped support for pre-SMAPI-2.6 update checks in the web API. + _These are no longer useful, even if the player still has earlier versions of SMAPI. Older versions of SMAPI won't launch in Stardew Valley 1.3 (so they won't check for updates), and newer versions of SMAPI/mods won't work with older versions of the game._ + ## 2.6 * For players: * Updated for Stardew Valley 1.3. diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs index b500e19d..18d55665 100644 --- a/src/SMAPI.Web/Controllers/ModsApiController.cs +++ b/src/SMAPI.Web/Controllers/ModsApiController.cs @@ -83,31 +83,25 @@ namespace StardewModdingAPI.Web.Controllers /// Fetch version metadata for the given mods. /// The mod search criteria. [HttpPost] - public async Task PostAsync([FromBody] ModSearchModel model) + public async Task> PostAsync([FromBody] ModSearchModel model) { - // parse request data - ISemanticVersion apiVersion = this.GetApiVersion(); - ModSearchEntryModel[] searchMods = this.GetSearchMods(model, apiVersion).ToArray(); + if (model?.Mods == null) + return new ModEntryModel[0]; // fetch wiki data WikiCompatibilityEntry[] wikiData = await this.GetWikiDataAsync(); - - // fetch data IDictionary mods = new Dictionary(StringComparer.CurrentCultureIgnoreCase); - foreach (ModSearchEntryModel mod in searchMods) + foreach (ModSearchEntryModel mod in model.Mods) { if (string.IsNullOrWhiteSpace(mod.ID)) continue; ModEntryModel result = await this.GetModData(mod, wikiData, model.IncludeExtendedMetadata); - result.SetBackwardsCompatibility(apiVersion); mods[mod.ID] = result; } - // return in expected structure - return apiVersion.IsNewerThan("2.6-beta.18") - ? mods.Values - : (object)mods; + // return data + return mods.Values; } @@ -231,29 +225,6 @@ namespace StardewModdingAPI.Web.Controllers return current != null && (other == null || other.IsOlderThan(current)); } - /// Get the mods for which the API should return data. - /// The search model. - /// The requested API version. - private IEnumerable GetSearchMods(ModSearchModel model, ISemanticVersion apiVersion) - { - if (model == null) - yield break; - - // yield standard entries - if (model.Mods != null) - { - foreach (ModSearchEntryModel mod in model.Mods) - yield return mod; - } - - // yield mod update keys if backwards compatible - if (model.ModKeys != null && model.ModKeys.Any() && !apiVersion.IsNewerThan("2.6-beta.17")) - { - foreach (string updateKey in model.ModKeys.Distinct()) - yield return new ModSearchEntryModel(updateKey, new[] { updateKey }); - } - } - /// Get mod data from the wiki compatibility list. private async Task GetWikiDataAsync() { diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs index f3f22b93..2aafe199 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs @@ -1,6 +1,3 @@ -using System; -using Newtonsoft.Json; - namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi { /// Metadata about a mod. @@ -26,46 +23,5 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi /// The errors that occurred while fetching update data. public string[] Errors { get; set; } = new string[0]; - - /**** - ** Backwards-compatible fields - ****/ - /// The mod's latest version number. - [Obsolete("Use " + nameof(ModEntryModel.Main))] - [JsonProperty] - internal string Version { get; private set; } - - /// The mod's web URL. - [Obsolete("Use " + nameof(ModEntryModel.Main))] - [JsonProperty] - internal string Url { get; private set; } - - /// The mod's latest optional release, if newer than . - [Obsolete("Use " + nameof(ModEntryModel.Optional))] - [JsonProperty] - internal string PreviewVersion { get; private set; } - - /// The web URL to the mod's latest optional release, if newer than . - [Obsolete("Use " + nameof(ModEntryModel.Optional))] - [JsonProperty] - internal string PreviewUrl { get; private set; } - - - /********* - ** Public methods - *********/ - /// Set backwards-compatible fields. - /// The requested API version. - public void SetBackwardsCompatibility(ISemanticVersion version) - { - if (version.IsOlderThan("2.6-beta.19")) - { - this.Version = this.Main?.Version?.ToString(); - this.Url = this.Main?.Url; - - this.PreviewVersion = this.Optional?.Version?.ToString(); - this.PreviewUrl = this.Optional?.Url; - } - } } } diff --git a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModSeachModel.cs b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModSeachModel.cs index df0d8457..e352e1cc 100644 --- a/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModSeachModel.cs +++ b/src/StardewModdingAPI.Toolkit/Framework/Clients/WebApi/ModSeachModel.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi @@ -9,10 +8,6 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi /********* ** Accessors *********/ - /// The namespaced mod keys to search. - [Obsolete] - public string[] ModKeys { get; set; } - /// The mods for which to find data. public ModSearchEntryModel[] Mods { get; set; } -- cgit From 4dd4efc96fac6a7ab66c14edead10e4fa988040d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 14 Aug 2018 12:21:31 -0400 Subject: update for SMAPI 2.7 release --- build/GlobalAssemblyInfo.cs | 4 ++-- docs/release-notes.md | 7 ++++--- src/SMAPI.Mods.ConsoleCommands/manifest.json | 5 +++-- src/SMAPI.Mods.SaveBackup/manifest.json | 5 +++-- src/SMAPI/Constants.cs | 2 +- 5 files changed, 13 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/build/GlobalAssemblyInfo.cs b/build/GlobalAssemblyInfo.cs index bc0ddf69..2e4f9373 100644 --- a/build/GlobalAssemblyInfo.cs +++ b/build/GlobalAssemblyInfo.cs @@ -1,5 +1,5 @@ using System.Reflection; [assembly: AssemblyProduct("SMAPI")] -[assembly: AssemblyVersion("2.6.0")] -[assembly: AssemblyFileVersion("2.6.0")] +[assembly: AssemblyVersion("2.7.0")] +[assembly: AssemblyFileVersion("2.7.0")] diff --git a/docs/release-notes.md b/docs/release-notes.md index 0ffcc591..133006e8 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -1,6 +1,7 @@ # Release notes ## 2.7 * For players: + * Updated for Stardew Valley 1.3.28. * Improved how mod issues are listed in the console and log. * Revamped installer. It now... * uses a new format that should be more intuitive; @@ -13,12 +14,12 @@ * For modders: * Added support for `.json` data files in the content API (including Content Patcher). - * Added automatic propagation when changing assets through the content API for... + * Added propagation for asset changes through the content API for... * child sprites; * dialogue; * map tilesheets. - * Added `--mods-path` command-line argument to allow switching between mod folders. - * All enums are now JSON-serialised by name, since that's more user-friendly. Previously only certain predefined enums were serialised that way. JSON files which already have integer enums will still be parsed fine. + * Added `--mods-path` CLI command-line argument to switch between mod folders. + * All enums are now JSON-serialised by name instead of numeric value. (Previously only a few enums were serialised that way. JSON files which already have numeric enum values will still be parsed fine.) * Fixed false compatibility error when constructing multidimensional arrays. * Fixed `.ToSButton()` methods not being public. * Updated compatibility list. diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json index f89049c6..a6c5cd88 100644 --- a/src/SMAPI.Mods.ConsoleCommands/manifest.json +++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json @@ -1,8 +1,9 @@ { "Name": "Console Commands", "Author": "SMAPI", - "Version": "2.6.0", + "Version": "2.7.0", "Description": "Adds SMAPI console commands that let you manipulate the game.", "UniqueID": "SMAPI.ConsoleCommands", - "EntryDll": "ConsoleCommands.dll" + "EntryDll": "ConsoleCommands.dll", + "MinimumApiVersion": "2.7.0" } diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json index ee0f2abb..e973b449 100644 --- a/src/SMAPI.Mods.SaveBackup/manifest.json +++ b/src/SMAPI.Mods.SaveBackup/manifest.json @@ -1,8 +1,9 @@ { "Name": "Save Backup", "Author": "SMAPI", - "Version": "2.6.0", + "Version": "2.7.0", "Description": "Automatically backs up all your saves once per day into its folder.", "UniqueID": "SMAPI.SaveBackup", - "EntryDll": "SaveBackup.dll" + "EntryDll": "SaveBackup.dll", + "MinimumApiVersion": "2.7.0" } diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index b41fb89d..bd512fb1 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -29,7 +29,7 @@ namespace StardewModdingAPI ** Public ****/ /// SMAPI's current semantic version. - public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("2.6.0"); + public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("2.7.0"); /// The minimum supported version of Stardew Valley. public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.3.28"); -- cgit