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/SMAPI') 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/SMAPI') 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 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/SMAPI') 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 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/SMAPI') 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/SMAPI') 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/SMAPI') 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 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/SMAPI') 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/SMAPI') 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 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/SMAPI') 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/SMAPI') 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 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/SMAPI') 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/SMAPI') 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 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/SMAPI') 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