summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2018-12-07 13:40:44 -0500
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2018-12-07 13:40:44 -0500
commita78b1935928919694dfe8de823a1accd6d222732 (patch)
tree3f17b6087cf2749e52c1e237de17e2e9addb6c06 /src/SMAPI/Framework
parent4cd9eda1591c3908bf80b60c2902491a7595ee27 (diff)
parent8901218418693d610a17b22fe789ba6279f63446 (diff)
downloadSMAPI-a78b1935928919694dfe8de823a1accd6d222732.tar.gz
SMAPI-a78b1935928919694dfe8de823a1accd6d222732.tar.bz2
SMAPI-a78b1935928919694dfe8de823a1accd6d222732.zip
Merge branch 'develop' into stable
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs16
-rw-r--r--src/SMAPI/Framework/ContentManagers/BaseContentManager.cs20
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs27
-rw-r--r--src/SMAPI/Framework/ContentManagers/IContentManager.cs4
-rw-r--r--src/SMAPI/Framework/DeprecationManager.cs6
-rw-r--r--src/SMAPI/Framework/Events/EventManager.cs8
-rw-r--r--src/SMAPI/Framework/ModHelpers/ModHelper.cs36
-rw-r--r--src/SMAPI/Framework/ModLoading/AssemblyLoader.cs9
-rw-r--r--src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs2
-rw-r--r--src/SMAPI/Framework/ModLoading/ModWarning.cs2
-rw-r--r--src/SMAPI/Framework/SCore.cs86
-rw-r--r--src/SMAPI/Framework/SGame.cs127
-rw-r--r--src/SMAPI/Framework/SMultiplayer.cs2
-rw-r--r--src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs83
-rw-r--r--src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs13
-rw-r--r--src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs3
-rw-r--r--src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs2
-rw-r--r--src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs14
-rw-r--r--src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs3
-rw-r--r--src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs8
-rw-r--r--src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs50
-rw-r--r--src/SMAPI/Framework/WatcherCore.cs3
22 files changed, 423 insertions, 101 deletions
diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs
index 9eb7b5f9..08a32a9b 100644
--- a/src/SMAPI/Framework/ContentCoordinator.cs
+++ b/src/SMAPI/Framework/ContentCoordinator.cs
@@ -238,28 +238,30 @@ namespace StardewModdingAPI.Framework
public IEnumerable<string> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false)
{
// invalidate cache
- HashSet<string> removedAssetNames = new HashSet<string>();
+ IDictionary<string, Type> removedAssetNames = new Dictionary<string, Type>(StringComparer.InvariantCultureIgnoreCase);
foreach (IContentManager contentManager in this.ContentManagers)
{
- foreach (string name in contentManager.InvalidateCache(predicate, dispose))
- removedAssetNames.Add(name);
+ foreach (Tuple<string, Type> asset in contentManager.InvalidateCache(predicate, dispose))
+ removedAssetNames[asset.Item1] = asset.Item2;
}
// reload core game assets
int reloaded = 0;
- foreach (string key in removedAssetNames)
+ foreach (var pair in removedAssetNames)
{
- if (this.CoreAssets.Propagate(this.MainContentManager, key)) // use an intercepted content manager
+ string key = pair.Key;
+ Type type = pair.Value;
+ if (this.CoreAssets.Propagate(this.MainContentManager, key, type)) // use an intercepted content manager
reloaded++;
}
// report result
if (removedAssetNames.Any())
- this.Monitor.Log($"Invalidated {removedAssetNames.Count} asset names: {string.Join(", ", removedAssetNames.OrderBy(p => p, StringComparer.InvariantCultureIgnoreCase))}. Reloaded {reloaded} core assets.", LogLevel.Trace);
+ this.Monitor.Log($"Invalidated {removedAssetNames.Count} asset names: {string.Join(", ", removedAssetNames.Keys.OrderBy(p => p, StringComparer.InvariantCultureIgnoreCase))}. Reloaded {reloaded} core assets.", LogLevel.Trace);
else
this.Monitor.Log("Invalidated 0 cache entries.", LogLevel.Trace);
- return removedAssetNames;
+ return removedAssetNames.Keys;
}
/// <summary>Dispose held resources.</summary>
diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
index 18aae05b..724a6e1c 100644
--- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
@@ -32,12 +32,12 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>Whether the content coordinator has been disposed.</summary>
private bool IsDisposed;
- /// <summary>The language enum values indexed by locale code.</summary>
- private readonly IDictionary<string, LanguageCode> LanguageCodes;
-
/// <summary>A callback to invoke when the content manager is being disposed.</summary>
private readonly Action<BaseContentManager> OnDisposing;
+ /// <summary>The language enum values indexed by locale code.</summary>
+ protected IDictionary<string, LanguageCode> LanguageCodes { get; }
+
/*********
** Accessors
@@ -200,23 +200,25 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>Purge matched assets from the cache.</summary>
/// <param name="predicate">Matches the asset keys to invalidate.</param>
/// <param name="dispose">Whether to dispose invalidated assets. This should only be <c>true</c> when they're being invalidated as part of a dispose, to avoid crashing the game.</param>
- /// <returns>Returns the number of invalidated assets.</returns>
- public IEnumerable<string> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false)
+ /// <returns>Returns the invalidated asset names and types.</returns>
+ public IEnumerable<Tuple<string, Type>> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false)
{
- HashSet<string> removeAssetNames = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
+ Dictionary<string, Type> removeAssetNames = new Dictionary<string, Type>(StringComparer.InvariantCultureIgnoreCase);
this.Cache.Remove((key, type) =>
{
this.ParseCacheKey(key, out string assetName, out _);
- if (removeAssetNames.Contains(assetName) || predicate(assetName, type))
+ if (removeAssetNames.ContainsKey(assetName))
+ return true;
+ if (predicate(assetName, type))
{
- removeAssetNames.Add(assetName);
+ removeAssetNames[assetName] = type;
return true;
}
return false;
});
- return removeAssetNames;
+ return removeAssetNames.Select(p => Tuple.Create(p.Key, p.Value));
}
/// <summary>Dispose held resources.</summary>
diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
index a53840bc..4f3b6fbc 100644
--- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using StardewModdingAPI.Framework.Content;
+using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.Reflection;
using StardewModdingAPI.Framework.Utilities;
using StardewValley;
@@ -52,7 +53,10 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <param name="language">The language code for which to load content.</param>
public override T Load<T>(string assetName, LanguageCode language)
{
+ // normalise asset name
assetName = this.AssertAndNormaliseAssetName(assetName);
+ if (this.TryParseExplicitLanguageAssetKey(assetName, out string newAssetName, out LanguageCode newLanguage))
+ return this.Load<T>(newAssetName, newLanguage);
// get from cache
if (this.IsLoaded(assetName))
@@ -124,6 +128,29 @@ namespace StardewModdingAPI.Framework.ContentManagers
return false;
}
+ /// <summary>Parse an asset key that contains an explicit language into its asset name and language, if applicable.</summary>
+ /// <param name="rawAsset">The asset key to parse.</param>
+ /// <param name="assetName">The asset name without the language code.</param>
+ /// <param name="language">The language code removed from the asset name.</param>
+ private bool TryParseExplicitLanguageAssetKey(string rawAsset, out string assetName, out LanguageCode language)
+ {
+ if (string.IsNullOrWhiteSpace(rawAsset))
+ throw new SContentLoadException("The asset key is empty.");
+
+ // extract language code
+ int splitIndex = rawAsset.LastIndexOf('.');
+ if (splitIndex != -1 && this.LanguageCodes.TryGetValue(rawAsset.Substring(splitIndex + 1), out language))
+ {
+ assetName = rawAsset.Substring(0, splitIndex);
+ return true;
+ }
+
+ // no explicit language code found
+ assetName = rawAsset;
+ language = this.Language;
+ return false;
+ }
+
/// <summary>Load the initial asset from the registered <see cref="Loaders"/>.</summary>
/// <param name="info">The basic asset metadata.</param>
/// <returns>Returns the loaded asset metadata, or <c>null</c> if no loader matched.</returns>
diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs
index 1eb8b0ac..17618edd 100644
--- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs
@@ -80,7 +80,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>Purge matched assets from the cache.</summary>
/// <param name="predicate">Matches the asset keys to invalidate.</param>
/// <param name="dispose">Whether to dispose invalidated assets. This should only be <c>true</c> when they're being invalidated as part of a dispose, to avoid crashing the game.</param>
- /// <returns>Returns the number of invalidated assets.</returns>
- IEnumerable<string> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false);
+ /// <returns>Returns the invalidated asset names and types.</returns>
+ IEnumerable<Tuple<string, Type>> InvalidateCache(Func<string, Type, bool> predicate, bool dispose = false);
}
}
diff --git a/src/SMAPI/Framework/DeprecationManager.cs b/src/SMAPI/Framework/DeprecationManager.cs
index 0fde67ee..be564c22 100644
--- a/src/SMAPI/Framework/DeprecationManager.cs
+++ b/src/SMAPI/Framework/DeprecationManager.cs
@@ -35,6 +35,12 @@ namespace StardewModdingAPI.Framework
this.ModRegistry = modRegistry;
}
+ /// <summary>Log a deprecation warning for the old-style events.</summary>
+ public void WarnForOldEvents()
+ {
+ this.Warn("legacy events", "2.9", DeprecationLevel.Notice);
+ }
+
/// <summary>Log a deprecation warning.</summary>
/// <param name="nounPhrase">A noun phrase describing what is deprecated.</param>
/// <param name="version">The SMAPI version which deprecated it.</param>
diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs
index b9d1c453..0ad85adf 100644
--- a/src/SMAPI/Framework/Events/EventManager.cs
+++ b/src/SMAPI/Framework/Events/EventManager.cs
@@ -1,5 +1,7 @@
using System.Diagnostics.CodeAnalysis;
+#if !SMAPI_3_0_STRICT
using Microsoft.Xna.Framework.Input;
+#endif
using StardewModdingAPI.Events;
namespace StardewModdingAPI.Framework.Events
@@ -156,6 +158,7 @@ namespace StardewModdingAPI.Framework.Events
public readonly ManagedEvent<UnvalidatedUpdateTickedEventArgs> UnvalidatedUpdateTicked;
+#if !SMAPI_3_0_STRICT
/*********
** Events (old)
*********/
@@ -342,6 +345,7 @@ namespace StardewModdingAPI.Framework.Events
/// <summary>Raised after the in-game clock changes.</summary>
public readonly ManagedEvent<EventArgsIntChanged> Legacy_TimeOfDayChanged;
+#endif
/*********
@@ -354,7 +358,9 @@ namespace StardewModdingAPI.Framework.Events
{
// create shortcut initialisers
ManagedEvent<TEventArgs> ManageEventOf<TEventArgs>(string typeName, string eventName) => new ManagedEvent<TEventArgs>($"{typeName}.{eventName}", monitor, modRegistry);
+#if !SMAPI_3_0_STRICT
ManagedEvent ManageEvent(string typeName, string eventName) => new ManagedEvent($"{typeName}.{eventName}", monitor, modRegistry);
+#endif
// init events (new)
this.MenuChanged = ManageEventOf<MenuChangedEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.MenuChanged));
@@ -405,6 +411,7 @@ namespace StardewModdingAPI.Framework.Events
this.UnvalidatedUpdateTicking = ManageEventOf<UnvalidatedUpdateTickingEventArgs>(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.UnvalidatedUpdateTicking));
this.UnvalidatedUpdateTicked = ManageEventOf<UnvalidatedUpdateTickedEventArgs>(nameof(IModEvents.Specialised), nameof(ISpecialisedEvents.UnvalidatedUpdateTicked));
+#if !SMAPI_3_0_STRICT
// init events (old)
this.Legacy_LocaleChanged = ManageEventOf<EventArgsValueChanged<string>>(nameof(ContentEvents), nameof(ContentEvents.AfterLocaleChanged));
@@ -466,6 +473,7 @@ namespace StardewModdingAPI.Framework.Events
this.Legacy_AfterDayStarted = ManageEvent(nameof(TimeEvents), nameof(TimeEvents.AfterDayStarted));
this.Legacy_TimeOfDayChanged = ManageEventOf<EventArgsIntChanged>(nameof(TimeEvents), nameof(TimeEvents.TimeOfDayChanged));
+#endif
}
}
}
diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs
index 5e190e55..070d9c65 100644
--- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs
+++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs
@@ -21,8 +21,10 @@ namespace StardewModdingAPI.Framework.ModHelpers
/// <summary>Create a transitional content pack.</summary>
private readonly Func<string, IManifest, IContentPack> CreateContentPack;
+#if !SMAPI_3_0_STRICT
/// <summary>Manages deprecation warnings.</summary>
private readonly DeprecationManager DeprecationManager;
+#endif
/*********
@@ -31,8 +33,10 @@ namespace StardewModdingAPI.Framework.ModHelpers
/// <summary>The full path to the mod's folder.</summary>
public string DirectoryPath { get; }
+#if !SMAPI_3_0_STRICT
/// <summary>Encapsulates SMAPI's JSON file parsing.</summary>
private readonly JsonHelper JsonHelper;
+#endif
/// <summary>Manages access to events raised by SMAPI, which let your mod react when something happens in the game.</summary>
public IModEvents Events { get; }
@@ -94,7 +98,6 @@ namespace StardewModdingAPI.Framework.ModHelpers
// initialise
this.DirectoryPath = modDirectory;
- this.JsonHelper = jsonHelper ?? throw new ArgumentNullException(nameof(jsonHelper));
this.Content = contentHelper ?? throw new ArgumentNullException(nameof(contentHelper));
this.Data = dataHelper ?? throw new ArgumentNullException(nameof(dataHelper));
this.Input = new InputHelper(modID, inputState);
@@ -105,8 +108,11 @@ namespace StardewModdingAPI.Framework.ModHelpers
this.Translation = translationHelper ?? throw new ArgumentNullException(nameof(translationHelper));
this.ContentPacks = new Lazy<IContentPack[]>(contentPacks);
this.CreateContentPack = createContentPack;
- this.DeprecationManager = deprecationManager;
this.Events = events;
+#if !SMAPI_3_0_STRICT
+ this.JsonHelper = jsonHelper ?? throw new ArgumentNullException(nameof(jsonHelper));
+ this.DeprecationManager = deprecationManager;
+#endif
}
/****
@@ -131,6 +137,7 @@ namespace StardewModdingAPI.Framework.ModHelpers
this.Data.WriteJsonFile("config.json", config);
}
+#if !SMAPI_3_0_STRICT
/****
** Generic JSON files
****/
@@ -159,23 +166,20 @@ namespace StardewModdingAPI.Framework.ModHelpers
path = Path.Combine(this.DirectoryPath, PathUtilities.NormalisePathSeparators(path));
this.JsonHelper.WriteJsonFile(path, model);
}
+#endif
/****
** Content packs
****/
- /// <summary>Manually create a transitional content pack to support pre-SMAPI content packs. This provides a way to access legacy content packs using the SMAPI content pack APIs, but the content pack will not be visible in the log or validated by SMAPI.</summary>
+ /// <summary>Create a temporary content pack to read files from a directory. Temporary content packs will not appear in the SMAPI log and update checks will not be performed.</summary>
/// <param name="directoryPath">The absolute directory path containing the content pack files.</param>
/// <param name="id">The content pack's unique ID.</param>
/// <param name="name">The content pack name.</param>
/// <param name="description">The content pack description.</param>
/// <param name="author">The content pack author's name.</param>
/// <param name="version">The content pack version.</param>
- [Obsolete("This method supports mods which previously had their own content packs, and shouldn't be used by new mods. It will be removed in SMAPI 3.0.")]
- public IContentPack CreateTransitionalContentPack(string directoryPath, string id, string name, string description, string author, ISemanticVersion version)
+ public IContentPack CreateTemporaryContentPack(string directoryPath, string id, string name, string description, string author, ISemanticVersion version)
{
- // raise deprecation notice
- this.DeprecationManager.Warn($"{nameof(IModHelper)}.{nameof(IModHelper.CreateTransitionalContentPack)}", "2.5", DeprecationLevel.Notice);
-
// validate
if (string.IsNullOrWhiteSpace(directoryPath))
throw new ArgumentNullException(nameof(directoryPath));
@@ -200,6 +204,22 @@ namespace StardewModdingAPI.Framework.ModHelpers
return this.CreateContentPack(directoryPath, manifest);
}
+#if !SMAPI_3_0_STRICT
+ /// <summary>Manually create a transitional content pack to support pre-SMAPI content packs. This provides a way to access legacy content packs using the SMAPI content pack APIs, but the content pack will not be visible in the log or validated by SMAPI.</summary>
+ /// <param name="directoryPath">The absolute directory path containing the content pack files.</param>
+ /// <param name="id">The content pack's unique ID.</param>
+ /// <param name="name">The content pack name.</param>
+ /// <param name="description">The content pack description.</param>
+ /// <param name="author">The content pack author's name.</param>
+ /// <param name="version">The content pack version.</param>
+ [Obsolete("Use " + nameof(IModHelper) + "." + nameof(IModHelper.CreateTemporaryContentPack) + " instead")]
+ public IContentPack CreateTransitionalContentPack(string directoryPath, string id, string name, string description, string author, ISemanticVersion version)
+ {
+ this.DeprecationManager.Warn($"{nameof(IModHelper)}.{nameof(IModHelper.CreateTransitionalContentPack)}", "2.5", DeprecationLevel.Notice);
+ return this.CreateTemporaryContentPack(directoryPath, id, name, description, author, version);
+ }
+#endif
+
/// <summary>Get all content packs loaded for this mod.</summary>
public IEnumerable<IContentPack> GetContentPacks()
{
diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
index fdbfdd8d..7292cf3f 100644
--- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
+++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs
@@ -20,6 +20,9 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <summary>Encapsulates monitoring and logging.</summary>
private readonly IMonitor Monitor;
+ /// <summary>Whether to detect paranoid mode issues.</summary>
+ private readonly bool ParanoidMode;
+
/// <summary>Metadata for mapping assemblies to the current platform.</summary>
private readonly PlatformAssemblyMap AssemblyMap;
@@ -39,9 +42,11 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <summary>Construct an instance.</summary>
/// <param name="targetPlatform">The current game platform.</param>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
- public AssemblyLoader(Platform targetPlatform, IMonitor monitor)
+ /// <param name="paranoidMode">Whether to detect paranoid mode issues.</param>
+ public AssemblyLoader(Platform targetPlatform, IMonitor monitor, bool paranoidMode)
{
this.Monitor = monitor;
+ this.ParanoidMode = paranoidMode;
this.AssemblyMap = this.TrackForDisposal(Constants.GetAssemblyMap(targetPlatform));
this.AssemblyDefinitionResolver = this.TrackForDisposal(new AssemblyDefinitionResolver());
this.AssemblyDefinitionResolver.AddSearchDirectory(Constants.ExecutionPath);
@@ -275,7 +280,7 @@ namespace StardewModdingAPI.Framework.ModLoading
// find (and optionally rewrite) incompatible instructions
bool anyRewritten = false;
- IInstructionHandler[] handlers = new InstructionMetadata().GetHandlers().ToArray();
+ IInstructionHandler[] handlers = new InstructionMetadata().GetHandlers(this.ParanoidMode).ToArray();
foreach (MethodDefinition method in this.GetMethods(module))
{
// check method definition
diff --git a/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs b/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs
index f3555c2d..6592760e 100644
--- a/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs
+++ b/src/SMAPI/Framework/ModLoading/InstructionHandleResult.cs
@@ -23,7 +23,7 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <summary>The instruction is compatible, but uses the <c>dynamic</c> keyword which won't work on Linux/Mac.</summary>
DetectedDynamic,
- /// <summary>The instruction is compatible, but references <see cref="SpecialisedEvents.UnvalidatedUpdateTick"/> which may impact stability.</summary>
+ /// <summary>The instruction is compatible, but references <see cref="ISpecialisedEvents.UnvalidatedUpdateTicking"/> or <see cref="ISpecialisedEvents.UnvalidatedUpdateTicked"/> which may impact stability.</summary>
DetectedUnvalidatedUpdateTick,
/// <summary>The instruction accesses the filesystem directly.</summary>
diff --git a/src/SMAPI/Framework/ModLoading/ModWarning.cs b/src/SMAPI/Framework/ModLoading/ModWarning.cs
index c62199b2..e643cb05 100644
--- a/src/SMAPI/Framework/ModLoading/ModWarning.cs
+++ b/src/SMAPI/Framework/ModLoading/ModWarning.cs
@@ -22,7 +22,7 @@ namespace StardewModdingAPI.Framework.ModLoading
/// <summary>The mod uses the <c>dynamic</c> keyword which won't work on Linux/Mac.</summary>
UsesDynamic = 8,
- /// <summary>The mod references <see cref="SpecialisedEvents.UnvalidatedUpdateTick"/> which may impact stability.</summary>
+ /// <summary>The mod references <see cref="ISpecialisedEvents.UnvalidatedUpdateTicking"/> or <see cref="ISpecialisedEvents.UnvalidatedUpdateTicked"/> which may impact stability.</summary>
UsesUnvalidatedUpdateTick = 16,
/// <summary>The mod has no update keys set.</summary>
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index 4b95917b..800b9c09 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -99,11 +99,25 @@ namespace StardewModdingAPI.Framework
new Regex(@"^static SerializableDictionary<.+>\(\) called\.$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
};
+ /// <summary>Regex patterns which match console messages to show a more friendly error for.</summary>
+ private readonly Tuple<Regex, string, LogLevel>[] ReplaceConsolePatterns =
+ {
+ Tuple.Create(
+ new Regex(@"^System\.InvalidOperationException: Steamworks is not initialized\.", RegexOptions.Compiled | RegexOptions.CultureInvariant),
+#if SMAPI_FOR_WINDOWS
+ "Oops! Steam achievements won't work because Steam isn't loaded. You can launch the game through Steam to fix that (see 'Part 2: Configure Steam' in the install guide for more info: https://smapi.io/install).",
+#else
+ "Oops! Steam achievements won't work because Steam isn't loaded. You can launch the game through Steam to fix that.",
+#endif
+ LogLevel.Error
+ )
+ };
+
/// <summary>The mod toolkit used for generic mod interactions.</summary>
private readonly ModToolkit Toolkit = new ModToolkit();
/// <summary>The path to search for mods.</summary>
- private readonly string ModsPath;
+ private string ModsPath => Constants.ModsPath;
/*********
@@ -117,7 +131,7 @@ namespace StardewModdingAPI.Framework
// init paths
this.VerifyPath(modsPath);
this.VerifyPath(Constants.LogDir);
- this.ModsPath = modsPath;
+ Constants.ModsPath = modsPath;
// init log file
this.PurgeNormalLogs();
@@ -180,20 +194,22 @@ namespace StardewModdingAPI.Framework
// initialise SMAPI
try
{
+#if !SMAPI_3_0_STRICT
// hook up events
- ContentEvents.Init(this.EventManager);
- ControlEvents.Init(this.EventManager);
- GameEvents.Init(this.EventManager);
- GraphicsEvents.Init(this.EventManager);
- InputEvents.Init(this.EventManager);
- LocationEvents.Init(this.EventManager);
- MenuEvents.Init(this.EventManager);
- MineEvents.Init(this.EventManager);
- MultiplayerEvents.Init(this.EventManager);
- PlayerEvents.Init(this.EventManager);
- SaveEvents.Init(this.EventManager);
- SpecialisedEvents.Init(this.EventManager);
- TimeEvents.Init(this.EventManager);
+ ContentEvents.Init(this.EventManager, this.DeprecationManager);
+ ControlEvents.Init(this.EventManager, this.DeprecationManager);
+ GameEvents.Init(this.EventManager, this.DeprecationManager);
+ GraphicsEvents.Init(this.EventManager, this.DeprecationManager);
+ InputEvents.Init(this.EventManager, this.DeprecationManager);
+ LocationEvents.Init(this.EventManager, this.DeprecationManager);
+ MenuEvents.Init(this.EventManager, this.DeprecationManager);
+ MineEvents.Init(this.EventManager, this.DeprecationManager);
+ MultiplayerEvents.Init(this.EventManager, this.DeprecationManager);
+ PlayerEvents.Init(this.EventManager, this.DeprecationManager);
+ SaveEvents.Init(this.EventManager, this.DeprecationManager);
+ SpecialisedEvents.Init(this.EventManager, this.DeprecationManager);
+ TimeEvents.Init(this.EventManager, this.DeprecationManager);
+#endif
// init JSON parser
JsonConverter[] converters = {
@@ -216,7 +232,7 @@ namespace StardewModdingAPI.Framework
// override game
SGame.ConstructorHack = new SGameConstructorHack(this.Monitor, this.Reflection, this.Toolkit.JsonHelper);
- this.GameInstance = new SGame(this.Monitor, this.MonitorForGame, this.Reflection, this.EventManager, this.Toolkit.JsonHelper, this.ModRegistry, this.DeprecationManager, this.InitialiseAfterGameStart, this.Dispose);
+ this.GameInstance = new SGame(this.Monitor, this.MonitorForGame, this.Reflection, this.EventManager, this.Toolkit.JsonHelper, this.ModRegistry, this.DeprecationManager, this.OnLocaleChanged, this.InitialiseAfterGameStart, this.Dispose);
StardewValley.Program.gamePtr = this.GameInstance;
// add exit handler
@@ -239,12 +255,13 @@ namespace StardewModdingAPI.Framework
}
}).Start();
- // hook into game events
- ContentEvents.AfterLocaleChanged += (sender, e) => this.OnLocaleChanged();
-
// set window titles
this.GameInstance.Window.Title = $"Stardew Valley {Constants.GameVersion} - running SMAPI {Constants.ApiVersion}";
Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GameVersion}";
+#if SMAPI_3_0_STRICT
+ this.GameInstance.Window.Title += " [SMAPI 3.0 strict mode]";
+ Console.Title += " [SMAPI 3.0 strict mode]";
+#endif
}
catch (Exception ex)
{
@@ -348,8 +365,11 @@ namespace StardewModdingAPI.Framework
private void InitialiseAfterGameStart()
{
// add headers
+#if SMAPI_3_0_STRICT
+ this.Monitor.Log($"You're running SMAPI 3.0 strict mode, so most mods won't work correctly. If that wasn't intended, install the normal version of SMAPI from https://smapi.io instead.", LogLevel.Warn);
+#endif
if (this.Settings.DeveloperMode)
- 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);
+ this.Monitor.Log($"You have SMAPI for developers, so the console will 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)
this.Monitor.Log($"You configured SMAPI to not check for updates. Running an old version of SMAPI is not recommended. You can enable update checks by reinstalling SMAPI or editing {Constants.ApiConfigPath}.", LogLevel.Warn);
if (!this.Monitor.WriteToConsole)
@@ -409,6 +429,11 @@ namespace StardewModdingAPI.Framework
int modsLoaded = this.ModRegistry.GetAll().Count();
this.GameInstance.Window.Title = $"Stardew Valley {Constants.GameVersion} - running SMAPI {Constants.ApiVersion} with {modsLoaded} mods";
Console.Title = $"SMAPI {Constants.ApiVersion} - running Stardew Valley {Constants.GameVersion} with {modsLoaded} mods";
+#if SMAPI_3_0_STRICT
+ this.GameInstance.Window.Title += " [SMAPI 3.0 strict mode]";
+ Console.Title += " [SMAPI 3.0 strict mode]";
+#endif
+
// start SMAPI console
new Thread(this.RunConsoleLoop).Start();
@@ -701,7 +726,7 @@ namespace StardewModdingAPI.Framework
// load mods
IDictionary<IModMetadata, Tuple<string, string>> skippedMods = new Dictionary<IModMetadata, Tuple<string, string>>();
- using (AssemblyLoader modAssemblyLoader = new AssemblyLoader(Constants.Platform, this.Monitor))
+ using (AssemblyLoader modAssemblyLoader = new AssemblyLoader(Constants.Platform, this.Monitor, this.Settings.ParanoidWarnings))
{
// init
HashSet<string> suppressUpdateChecks = new HashSet<string>(this.Settings.SuppressUpdateChecks, StringComparer.InvariantCultureIgnoreCase);
@@ -891,11 +916,13 @@ namespace StardewModdingAPI.Framework
return false;
}
+#if !SMAPI_3_0_STRICT
// add deprecation warning for old version format
{
if (mod.Manifest?.Version is Toolkit.SemanticVersion version && version.IsLegacyFormat)
this.DeprecationManager.Warn(mod.DisplayName, "non-string manifest version", "2.8", DeprecationLevel.Notice);
}
+#endif
// validate dependencies
// Although dependences are validated before mods are loaded, a dependency may have failed to load.
@@ -1262,9 +1289,9 @@ namespace StardewModdingAPI.Framework
}
/// <summary>Redirect messages logged directly to the console to the given monitor.</summary>
- /// <param name="monitor">The monitor with which to log messages.</param>
+ /// <param name="gameMonitor">The monitor with which to log messages as the game.</param>
/// <param name="message">The message to log.</param>
- private void HandleConsoleMessage(IMonitor monitor, string message)
+ private void HandleConsoleMessage(IMonitor gameMonitor, string message)
{
// detect exception
LogLevel level = message.Contains("Exception") ? LogLevel.Error : LogLevel.Trace;
@@ -1273,8 +1