diff options
author | Jesse Plamondon-Willard <github@jplamondonw.com> | 2017-07-08 12:54:06 -0400 |
---|---|---|
committer | Jesse Plamondon-Willard <github@jplamondonw.com> | 2017-07-08 12:54:06 -0400 |
commit | 1edd98aef027faa768f56cf0b3591e64e20ba096 (patch) | |
tree | aec210e2b44c9654f29572dd084206a4598896e1 /src/StardewModdingAPI/Program.cs | |
parent | 36930ffd7d363d6afd7f8cac4918c7d1c1c3e339 (diff) | |
parent | 8743c4115aa142113d791f2d2cd9ba811dcada2c (diff) | |
download | SMAPI-1edd98aef027faa768f56cf0b3591e64e20ba096.tar.gz SMAPI-1edd98aef027faa768f56cf0b3591e64e20ba096.tar.bz2 SMAPI-1edd98aef027faa768f56cf0b3591e64e20ba096.zip |
Merge branch 'develop' into stable
Diffstat (limited to 'src/StardewModdingAPI/Program.cs')
-rw-r--r-- | src/StardewModdingAPI/Program.cs | 335 |
1 files changed, 244 insertions, 91 deletions
diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index d75d5193..97e18322 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -4,6 +4,8 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.ExceptionServices; +using System.Security; using System.Threading; #if SMAPI_FOR_WINDOWS using System.Management; @@ -15,6 +17,7 @@ using StardewModdingAPI.Events; using StardewModdingAPI.Framework; using StardewModdingAPI.Framework.Logging; using StardewModdingAPI.Framework.Models; +using StardewModdingAPI.Framework.ModHelpers; using StardewModdingAPI.Framework.ModLoading; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Serialisation; @@ -43,7 +46,7 @@ namespace StardewModdingAPI private readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource(); /// <summary>Simplifies access to private game code.</summary> - private readonly IReflectionHelper Reflection = new ReflectionHelper(); + private readonly Reflector Reflection = new Reflector(); /// <summary>The underlying game instance.</summary> private SGame GameInstance; @@ -115,6 +118,7 @@ namespace StardewModdingAPI } /// <summary>Launch SMAPI.</summary> + [HandleProcessCorruptedStateExceptions, SecurityCritical] // let try..catch handle corrupted state exceptions public void RunInteractively() { // initialise SMAPI @@ -123,7 +127,9 @@ namespace StardewModdingAPI // init logging this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)} on {this.GetFriendlyPlatformName()}", LogLevel.Info); this.Monitor.Log($"Mods go here: {Constants.ModPath}"); +#if !SMAPI_2_0 this.Monitor.Log("Preparing SMAPI..."); +#endif // validate paths this.VerifyPath(Constants.ModPath); @@ -207,7 +213,11 @@ namespace StardewModdingAPI } // start game +#if SMAPI_2_0 + this.Monitor.Log("Starting game...", LogLevel.Trace); +#else this.Monitor.Log("Starting game..."); +#endif try { this.IsGameRunning = true; @@ -224,6 +234,7 @@ namespace StardewModdingAPI } } +#if !SMAPI_2_0 /// <summary>Get a monitor for legacy code which doesn't have one passed in.</summary> [Obsolete("This method should only be used when needed for backwards compatibility.")] internal IMonitor GetLegacyMonitorForMod() @@ -231,6 +242,7 @@ namespace StardewModdingAPI string modName = this.ModRegistry.GetModFromStack() ?? "unknown"; return this.GetSecondaryMonitor(modName); } +#endif /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary> public void Dispose() @@ -321,17 +333,18 @@ namespace StardewModdingAPI this.DeprecationManager = new DeprecationManager(this.Monitor, this.ModRegistry); this.CommandManager = new CommandManager(); +#if !SMAPI_2_0 // inject compatibility shims #pragma warning disable 618 Command.Shim(this.CommandManager, this.DeprecationManager, this.ModRegistry); Config.Shim(this.DeprecationManager); Log.Shim(this.DeprecationManager, this.GetSecondaryMonitor("legacy mod"), this.ModRegistry); Mod.Shim(this.DeprecationManager); - ContentEvents.Shim(this.ModRegistry, this.Monitor); GameEvents.Shim(this.DeprecationManager); PlayerEvents.Shim(this.DeprecationManager); TimeEvents.Shim(this.DeprecationManager); #pragma warning restore 618 +#endif // redirect direct console output { @@ -344,6 +357,9 @@ namespace StardewModdingAPI if (this.Settings.DeveloperMode) { this.Monitor.ShowTraceInConsole = true; +#if SMAPI_2_0 + this.Monitor.ShowFullStampInConsole = true; +#endif this.Monitor.Log($"You configured SMAPI to run in developer mode. The console may be much more verbose. You can disable developer mode by installing the non-developer version of SMAPI, or by editing {Constants.ApiConfigPath}.", LogLevel.Info); } if (!this.Settings.CheckForUpdates) @@ -355,19 +371,23 @@ namespace StardewModdingAPI // validate XNB integrity if (!this.ValidateContentIntegrity()) - this.Monitor.Log("SMAPI found problems in the game's XNB files which may cause errors or crashes while you're playing. Consider uninstalling XNB mods or reinstalling the game.", LogLevel.Warn); + this.Monitor.Log("SMAPI found problems in your game's content files which are likely to cause errors or crashes. Consider uninstalling XNB mods or reinstalling the game.", LogLevel.Error); // load mods - int modsLoaded; { +#if SMAPI_2_0 + this.Monitor.Log("Loading mod metadata...", LogLevel.Trace); +#else this.Monitor.Log("Loading mod metadata..."); +#endif ModResolver resolver = new ModResolver(); // load manifests - IModMetadata[] mods = resolver.ReadManifests(Constants.ModPath, new JsonHelper(), this.Settings.ModCompatibility).ToArray(); + IModMetadata[] mods = resolver.ReadManifests(Constants.ModPath, new JsonHelper(), this.Settings.ModCompatibility, this.Settings.DisabledMods).ToArray(); resolver.ValidateManifests(mods, Constants.ApiVersion); // check for deprecated metadata +#if !SMAPI_2_0 IList<Action> deprecationWarnings = new List<Action>(); foreach (IModMetadata mod in mods.Where(m => m.Status != ModMetadataStatus.Failed)) { @@ -389,7 +409,7 @@ namespace StardewModdingAPI // per-save directories if ((mod.Manifest as Manifest)?.PerSaveConfigs == true) { - deprecationWarnings.Add(() => this.DeprecationManager.Warn(mod.DisplayName, $"{nameof(Manifest)}.{nameof(Manifest.PerSaveConfigs)}", "1.0", DeprecationLevel.Info)); + deprecationWarnings.Add(() => this.DeprecationManager.Warn(mod.DisplayName, $"{nameof(Manifest)}.{nameof(Manifest.PerSaveConfigs)}", "1.0", DeprecationLevel.PendingRemoval)); try { string psDir = Path.Combine(mod.DirectoryPath, "psconfigs"); @@ -403,14 +423,19 @@ namespace StardewModdingAPI } } } +#endif // process dependencies mods = resolver.ProcessDependencies(mods).ToArray(); // load mods - modsLoaded = this.LoadMods(mods, new JsonHelper(), this.ContentManager, deprecationWarnings); +#if SMAPI_2_0 + this.LoadMods(mods, new JsonHelper(), this.ContentManager); +#else + this.LoadMods(mods, new JsonHelper(), this.ContentManager, deprecationWarnings); foreach (Action warning in deprecationWarnings) warning(); +#endif } if (this.Monitor.IsExiting) { @@ -419,6 +444,7 @@ namespace StardewModdingAPI } // update window titles + int modsLoaded = this.ModRegistry.GetMods().Count(); this.GameInstance.Window.Title = $"Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)} - running SMAPI {Constants.ApiVersion} with {modsLoaded} mods"; Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GetGameDisplayVersion(Constants.GameVersion)} with {modsLoaded} mods"; @@ -443,7 +469,9 @@ namespace StardewModdingAPI private void RunConsoleLoop() { // prepare console +#if !SMAPI_2_0 this.Monitor.Log("Starting console..."); +#endif this.Monitor.Log("Type 'help' for help, or 'help <cmd>' for a command's usage", LogLevel.Info); this.CommandManager.Add("SMAPI", "help", "Lists command documentation.\n\nUsage: help\nLists all available commands.\n\nUsage: help <cmd>\n- cmd: The name of a command whose documentation to display.", this.HandleCommand); this.CommandManager.Add("SMAPI", "reload_i18n", "Reloads translation files for all mods.\n\nUsage: reload_i18n", this.HandleCommand); @@ -483,20 +511,21 @@ namespace StardewModdingAPI /// <returns>Returns whether all integrity checks passed.</returns> private bool ValidateContentIntegrity() { - this.Monitor.Log("Detecting common issues..."); + this.Monitor.Log("Detecting common issues...", LogLevel.Trace); bool issuesFound = false; - // object format (commonly broken by outdated files) { - void LogIssue(int id, string issue) => this.Monitor.Log($"Detected issue: item #{id} in Content\\Data\\ObjectInformation is invalid ({issue}).", LogLevel.Warn); + // detect issues + bool hasObjectIssues = false; + void LogIssue(int id, string issue) => this.Monitor.Log($@"Detected issue: item #{id} in Content\Data\ObjectInformation.xnb is invalid ({issue}).", LogLevel.Trace); foreach (KeyValuePair<int, string> entry in Game1.objectInformation) { // must not be empty if (string.IsNullOrWhiteSpace(entry.Value)) { LogIssue(entry.Key, "entry is empty"); - issuesFound = true; + hasObjectIssues = true; continue; } @@ -505,7 +534,7 @@ namespace StardewModdingAPI if (fields.Length < SObject.objectInfoDescriptionIndex + 1) { LogIssue(entry.Key, "too few fields for an object"); - issuesFound = true; + hasObjectIssues = true; continue; } @@ -516,11 +545,18 @@ namespace StardewModdingAPI if (fields.Length < SObject.objectInfoBuffDurationIndex + 1) { LogIssue(entry.Key, "too few fields for a cooking item"); - issuesFound = true; + hasObjectIssues = true; } break; } } + + // log error + if (hasObjectIssues) + { + issuesFound = true; + this.Monitor.Log(@"Your Content\Data\ObjectInformation.xnb file seems to be broken or outdated.", LogLevel.Warn); + } } return !issuesFound; @@ -567,125 +603,221 @@ namespace StardewModdingAPI /// <param name="mods">The mods to load.</param> /// <param name="jsonHelper">The JSON helper with which to read mods' JSON files.</param> /// <param name="contentManager">The content manager to use for mod content.</param> +#if !SMAPI_2_0 /// <param name="deprecationWarnings">A list to populate with any deprecation warnings.</param> - /// <returns>Returns the number of mods successfully loaded.</returns> - private int LoadMods(IModMetadata[] mods, JsonHelper jsonHelper, SContentManager contentManager, IList<Action> deprecationWarnings) + private void LoadMods(IModMetadata[] mods, JsonHelper jsonHelper, SContentManager contentManager, IList<Action> deprecationWarnings) +#else + private void LoadMods(IModMetadata[] mods, JsonHelper jsonHelper, SContentManager contentManager) +#endif { +#if SMAPI_2_0 + this.Monitor.Log("Loading mods...", LogLevel.Trace); +#else this.Monitor.Log("Loading mods..."); - void LogSkip(IModMetadata mod, string reasonPhrase, LogLevel level = LogLevel.Error) => this.Monitor.Log($"Skipped {mod.DisplayName} because {reasonPhrase}", level); - +#endif // load mod assemblies - int modsLoaded = 0; - AssemblyLoader modAssemblyLoader = new AssemblyLoader(Constants.TargetPlatform, this.Monitor); - AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => modAssemblyLoader.ResolveAssembly(e.Name); - foreach (IModMetadata metadata in mods) + IDictionary<IModMetadata, string> skippedMods = new Dictionary<IModMetadata, string>(); { - // validate status - if (metadata.Status == ModMetadataStatus.Failed) - { - LogSkip(metadata, metadata.Error); - continue; - } - - // get basic info - IManifest manifest = metadata.Manifest; - string assemblyPath = Path.Combine(metadata.DirectoryPath, metadata.Manifest.EntryDll); + void TrackSkip(IModMetadata mod, string reasonPhrase) => skippedMods[mod] = reasonPhrase; - // preprocess & load mod assembly - Assembly modAssembly; - try - { - modAssembly = modAssemblyLoader.Load(assemblyPath, assumeCompatible: metadata.Compatibility?.Compatibility == ModCompatibilityType.AssumeCompatible); - } - catch (IncompatibleInstructionException ex) - { - LogSkip(metadata, $"it's not compatible with the latest version of the game (detected {ex.NounPhrase}). Please check for a newer version of the mod (you have v{manifest.Version})."); - continue; - } - catch (Exception ex) + AssemblyLoader modAssemblyLoader = new AssemblyLoader(Constants.TargetPlatform, this.Monitor); + AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => modAssemblyLoader.ResolveAssembly(e.Name); + foreach (IModMetadata metadata in mods) { - LogSkip(metadata, $"its DLL '{manifest.EntryDll}' couldn't be loaded:\n{ex.GetLogSummary()}"); - continue; - } + // get basic info + IManifest manifest = metadata.Manifest; + string assemblyPath = metadata.Manifest?.EntryDll != null + ? Path.Combine(metadata.DirectoryPath, metadata.Manifest.EntryDll) + : null; + this.Monitor.Log(assemblyPath != null + ? $"Loading {metadata.DisplayName} from {assemblyPath.Replace(Constants.ModPath, "").TrimStart(Path.DirectorySeparatorChar)}..." + : $"Loading {metadata.DisplayName}...", LogLevel.Trace); + + // validate status + if (metadata.Status == ModMetadataStatus.Failed) + { + this.Monitor.Log($" Failed: {metadata.Error}", LogLevel.Trace); + TrackSkip(metadata, metadata.Error); + continue; + } - // validate assembly - try - { - int modEntries = modAssembly.DefinedTypes.Count(type => typeof(Mod).IsAssignableFrom(type) && !type.IsAbstract); - if (modEntries == 0) + // preprocess & load mod assembly + Assembly modAssembly; + try { - LogSkip(metadata, $"its DLL has no '{nameof(Mod)}' subclass."); + modAssembly = modAssemblyLoader.Load(assemblyPath, assumeCompatible: metadata.Compatibility?.Compatibility == ModCompatibilityType.AssumeCompatible); + } + catch (IncompatibleInstructionException ex) + { + TrackSkip(metadata, $"it's not compatible with the latest version of the game or SMAPI (detected {ex.NounPhrase}). Please check for a newer version of the mod."); continue; } - if (modEntries > 1) + catch (Exception ex) { - LogSkip(metadata, $"its DLL contains multiple '{nameof(Mod)}' subclasses."); + TrackSkip(metadata, $"its DLL '{manifest.EntryDll}' couldn't be loaded:\n{ex.GetLogSummary()}"); continue; } - } - catch (Exception ex) - { - LogSkip(metadata, $"its DLL couldn't be loaded:\n{ex.GetLogSummary()}"); - continue; - } - // initialise mod - try - { - // get implementation - TypeInfo modEntryType = modAssembly.DefinedTypes.First(type => typeof(Mod).IsAssignableFrom(type) && !type.IsAbstract); - Mod mod = (Mod)modAssembly.CreateInstance(modEntryType.ToString()); - if (mod == null) + // validate assembly + try { - LogSkip(metadata, "its entry class couldn't be instantiated."); + int modEntries = modAssembly.DefinedTypes.Count(type => typeof(Mod).IsAssignableFrom(type) && !type.IsAbstract); + if (modEntries == 0) + { + TrackSkip(metadata, $"its DLL has no '{nameof(Mod)}' subclass."); + continue; + } + if (modEntries > 1) + { + TrackSkip(metadata, $"its DLL contains multiple '{nameof(Mod)}' subclasses."); + continue; + } + } + catch (Exception ex) + { + TrackSkip(metadata, $"its DLL couldn't be loaded:\n{ex.GetLogSummary()}"); continue; } - // inject data - mod.ModManifest = manifest; - mod.Helper = new ModHelper(metadata.DisplayName, metadata.DirectoryPath, jsonHelper, this.ModRegistry, this.CommandManager, contentManager, this.Reflection); - mod.Monitor = this.GetSecondaryMonitor(metadata.DisplayName); - mod.PathOnDisk = metadata.DirectoryPath; - - // track mod - metadata.SetMod(mod); - this.ModRegistry.Add(metadata); - modsLoaded++; - this.Monitor.Log($"Loaded {metadata.DisplayName} by {manifest.Author}, v{manifest.Version} | {manifest.Description}", LogLevel.Info); + // initialise mod + try + { + // get implementation + TypeInfo modEntryType = modAssembly.DefinedTypes.First(type => typeof(Mod).IsAssignableFrom(type) && !type.IsAbstract); + Mod mod = (Mod)modAssembly.CreateInstance(modEntryType.ToString()); + if (mod == null) + { + TrackSkip(metadata, "its entry class couldn't be instantiated."); + continue; + } + +#if !SMAPI_2_0 + // prevent mods from using SMAPI 2.0 content interception before release + // ReSharper disable SuspiciousTypeConversion.Global + if (mod is IAssetEditor || mod is IAssetLoader) + { + TrackSkip(metadata, $"its entry class implements {nameof(IAssetEditor)} or {nameof(IAssetLoader)}. These are part of a prototype API that isn't available for mods to use yet."); + } + // ReSharper restore SuspiciousTypeConversion.Global +#endif + + // inject data + { + ICommandHelper commandHelper = new CommandHelper(manifest.UniqueID, metadata.DisplayName, this.CommandManager); + IContentHelper contentHelper = new ContentHelper(contentManager, metadata.DirectoryPath, manifest.UniqueID, metadata.DisplayName); + IReflectionHelper reflectionHelper = new ReflectionHelper(manifest.UniqueID, this.Reflection); + IModRegistry modRegistryHelper = new ModRegistryHelper(manifest.UniqueID, this.ModRegistry); + ITranslationHelper translationHelper = new TranslationHelper(manifest.UniqueID, manifest.Name, contentManager.GetLocale(), contentManager.GetCurrentLanguage()); + + mod.ModManifest = manifest; + mod.Helper = new ModHelper(manifest.UniqueID, metadata.DirectoryPath, jsonHelper, contentHelper, commandHelper, modRegistryHelper, reflectionHelper, translationHelper); + mod.Monitor = this.GetSecondaryMonitor(metadata.DisplayName); +#if !SMAPI_2_0 + mod.PathOnDisk = metadata.DirectoryPath; +#endif + } + + // track mod + metadata.SetMod(mod); + this.ModRegistry.Add(metadata); + } + catch (Exception ex) + { + TrackSkip(metadata, $"initialisation failed:\n{ex.GetLogSummary()}"); + } } - catch (Exception ex) + } + IModMetadata[] loadedMods = this.ModRegistry.GetMods().ToArray(); + + // log skipped mods +#if SMAPI_2_0 + this.Monitor.Newline(); +#endif + if (skippedMods.Any()) + { + this.Monitor.Log($"Skipped {skippedMods.Count} mods:", LogLevel.Error); + foreach (var pair in skippedMods.OrderBy(p => p.Key.DisplayName)) { - LogSkip(metadata, $"initialisation failed:\n{ex.GetLogSummary()}"); + IModMetadata mod = pair.Key; + string reason = pair.Value; + + if (mod.Manifest?.Version != null) + this.Monitor.Log($" {mod.DisplayName} {mod.Manifest.Version} because {reason}", LogLevel.Error); + else + this.Monitor.Log($" {mod.DisplayName} because {reason}", LogLevel.Error); } +#if SMAPI_2_0 + this.Monitor.Newline(); +#endif } + // 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}" : ""), + LogLevel.Info + ); + } +#if SMAPI_2_0 + this.Monitor.Newline(); +#endif + // initialise translations this.ReloadTranslations(); // initialise loaded mods - foreach (IModMetadata metadata in this.ModRegistry.GetMods()) + foreach (IModMetadata metadata in loadedMods) { + // add interceptors + if (metadata.Mod.Helper.Content is ContentHelper helper) + { + this.ContentManager.Editors[metadata] = helper.ObservableAssetEditors; + this.ContentManager.Loaders[metadata] = helper.ObservableAssetLoaders; + } + + // call entry method try { IMod mod = metadata.Mod; - - // call entry methods - (mod as Mod)?.Entry(); // deprecated since 1.0 mod.Entry(mod.Helper); +#if !SMAPI_2_0 + (mod as Mod)?.Entry(); // deprecated since 1.0 // raise deprecation warning for old Entry() methods if (this.DeprecationManager.IsVirtualMethodImplemented(mod.GetType(), typeof(Mod), nameof(Mod.Entry), new[] { typeof(object[]) })) - deprecationWarnings.Add(() => this.DeprecationManager.Warn(metadata.DisplayName, $"{nameof(Mod)}.{nameof(Mod.Entry)}(object[]) instead of {nameof(Mod)}.{nameof(Mod.Entry)}({nameof(IModHelper)})", "1.0", DeprecationLevel.Info)); + deprecationWarnings.Add(() => this.DeprecationManager.Warn(metadata.DisplayName, $"{nameof(Mod)}.{nameof(Mod.Entry)}(object[]) instead of {nameof(Mod)}.{nameof(Mod.Entry)}({nameof(IModHelper)})", "1.0", DeprecationLevel.PendingRemoval)); +#endif } catch (Exception ex) { - this.Monitor.Log($"The {metadata.DisplayName} mod failed on entry initialisation. It will still be loaded, but may not function correctly.\n{ex.GetLogSummary()}", LogLevel.Warn); + this.Monitor.Log($"{metadata.DisplayName} failed on entry and might not work correctly. Technical details:\n{ex.GetLogSummary()}", LogLevel.Error); } } - // print result - this.Monitor.Log($"Loaded {modsLoaded} mods."); - return modsLoaded; + // reset cache when needed + // only register listeners after Entry to avoid repeatedly reloading assets during load + foreach (IModMetadata metadata in loadedMods) + { + if (metadata.Mod.Helper.Content is ContentHelper helper) + { + helper.ObservableAssetEditors.CollectionChanged += (sender, e) => + { + if (e.NewItems.Count > 0) + this.ContentManager.Reset(); + }; + helper.ObservableAssetLoaders.CollectionChanged += (sender, e) => + { + if (e.NewItems.Count > 0) + this.ContentManager.Reset(); + }; + } + } + this.ContentManager.Reset(); } /// <summary>Reload translations for all mods.</summary> @@ -737,8 +869,22 @@ namespace StardewModdingAPI } else { +#if SMAPI_2_0 + string message = "The following commands are registered:\n"; + IGrouping<string, string>[] groups = (from command in this.CommandManager.GetAll() orderby command.ModName, command.Name group command.Name by command.ModName).ToArray(); + foreach (var group in groups) + { + string modName = group.Key; + string[] commandNames = group.ToArray(); + message += $"{modName}:\n {string.Join("\n ", commandNames)}\n\n"; + } + message += "For more information about a command, type 'help command_name'."; + + this.Monitor.Log(message, LogLevel.Info); +#else this.Monitor.Log("The following commands are registered: " + string.Join(", ", this.CommandManager.GetAll().Select(p => p.Name)) + ".", LogLevel.Info); this.Monitor.Log("For more information about a command, type 'help command_name'.", LogLevel.Info); +#endif } break; @@ -783,7 +929,14 @@ namespace StardewModdingAPI /// <param name="name">The name of the module which will log messages with this instance.</param> private Monitor GetSecondaryMonitor(string name) { - return new Monitor(name, this.ConsoleManager, this.LogFile, this.CancellationTokenSource) { WriteToConsole = this.Monitor.WriteToConsole, ShowTraceInConsole = this.Settings.DeveloperMode }; + return new Monitor(name, this.ConsoleManager, this.LogFile, this.CancellationTokenSource) + { + WriteToConsole = this.Monitor.WriteToConsole, + ShowTraceInConsole = this.Settings.DeveloperMode, +#if SMAPI_2_0 + ShowFullStampInConsole = this.Settings.DeveloperMode +#endif + }; } /// <summary>Get a human-readable name for the current platform.</summary> |