summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2021-02-21 23:05:17 -0500
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2021-02-21 23:05:17 -0500
commitdb011ee751bdfb8bbd9abbeb706966db4c4e2461 (patch)
tree4c1ca1c2fb2db41ac0d8b337e2f17209b4ad66f5 /src
parentf1505b0ebe5c8fa7cf3c160231f0ed7d1bd6619a (diff)
parent9c4c10d2d22cbf67ccadbd35fdf1ffced0541cc2 (diff)
downloadSMAPI-db011ee751bdfb8bbd9abbeb706966db4c4e2461.tar.gz
SMAPI-db011ee751bdfb8bbd9abbeb706966db4c4e2461.tar.bz2
SMAPI-db011ee751bdfb8bbd9abbeb706966db4c4e2461.zip
Merge branch 'develop' into stable
Diffstat (limited to 'src')
-rw-r--r--src/SMAPI.Installer/assets/windows-install.bat2
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs20
-rw-r--r--src/SMAPI.Mods.ConsoleCommands/manifest.json4
-rw-r--r--src/SMAPI.Mods.ErrorHandler/ModEntry.cs29
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs67
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs (renamed from src/SMAPI.Mods.ErrorHandler/Patches/EventErrorPatch.cs)14
-rw-r--r--src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs63
-rw-r--r--src/SMAPI.Mods.ErrorHandler/manifest.json4
-rw-r--r--src/SMAPI.Mods.SaveBackup/manifest.json4
-rw-r--r--src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs6
-rw-r--r--src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs15
-rw-r--r--src/SMAPI.Web/Controllers/ModsController.cs2
-rw-r--r--src/SMAPI.Web/wwwroot/schemas/content-patcher.json4
-rw-r--r--src/SMAPI/Constants.cs2
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs46
-rw-r--r--src/SMAPI/Framework/ContentManagers/BaseContentManager.cs85
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManager.cs57
-rw-r--r--src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs47
-rw-r--r--src/SMAPI/Framework/ContentManagers/IContentManager.cs10
-rw-r--r--src/SMAPI/Framework/ContentManagers/ModContentManager.cs26
-rw-r--r--src/SMAPI/Framework/Logging/LogManager.cs24
-rw-r--r--src/SMAPI/Framework/Models/SConfig.cs6
-rw-r--r--src/SMAPI/Framework/SCore.cs5
-rw-r--r--src/SMAPI/Metadata/CoreAssetPropagator.cs116
-rw-r--r--src/SMAPI/Program.cs31
-rw-r--r--src/SMAPI/SMAPI.config.json6
26 files changed, 515 insertions, 180 deletions
diff --git a/src/SMAPI.Installer/assets/windows-install.bat b/src/SMAPI.Installer/assets/windows-install.bat
index d02dd4c6..2cc54e80 100644
--- a/src/SMAPI.Installer/assets/windows-install.bat
+++ b/src/SMAPI.Installer/assets/windows-install.bat
@@ -4,5 +4,5 @@ if not errorlevel 1 (
echo Oops! It looks like you're running the installer from inside a zip file. Make sure you unzip the download first.
pause
) else (
- start /WAIT /B internal/windows-install.exe
+ start /WAIT /B ./internal/windows-install.exe
)
diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs
index 6782e38a..2d4b4565 100644
--- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs
+++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs
@@ -1,5 +1,5 @@
-using System;
using System.Linq;
+using Microsoft.Xna.Framework;
using StardewValley;
namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
@@ -45,12 +45,8 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
/// <param name="time">The time of day.</param>
private void SafelySetTime(int time)
{
- // define conversion between game time and TimeSpan
- TimeSpan ToTimeSpan(int value) => new TimeSpan(0, value / 100, value % 100, 0);
- int FromTimeSpan(TimeSpan span) => (span.Hours * 100) + span.Minutes;
-
// transition to new time
- int intervals = (int)((ToTimeSpan(time) - ToTimeSpan(Game1.timeOfDay)).TotalMinutes / 10);
+ int intervals = Utility.CalculateMinutesBetweenTimes(Game1.timeOfDay, time) / 10;
if (intervals > 0)
{
for (int i = 0; i < intervals; i++)
@@ -60,10 +56,20 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World
{
for (int i = 0; i > intervals; i--)
{
- Game1.timeOfDay = FromTimeSpan(ToTimeSpan(Game1.timeOfDay).Subtract(TimeSpan.FromMinutes(20))); // offset 20 minutes so game updates to next interval
+ Game1.timeOfDay = Utility.ModifyTime(Game1.timeOfDay, -20); // offset 20 mins so game updates to next interval
Game1.performTenMinuteClockUpdate();
}
}
+
+ // reset ambient light
+ // White is the default non-raining color. If it's raining or dark out, UpdateGameClock
+ // below will update it automatically.
+ Game1.outdoorLight = Color.White;
+ Game1.ambientLight = Color.White;
+
+ // run clock update (to correct lighting, etc)
+ Game1.gameTimeInterval = 0;
+ Game1.UpdateGameClock(Game1.currentGameTime);
}
}
}
diff --git a/src/SMAPI.Mods.ConsoleCommands/manifest.json b/src/SMAPI.Mods.ConsoleCommands/manifest.json
index 10611e08..aa3d6ceb 100644
--- a/src/SMAPI.Mods.ConsoleCommands/manifest.json
+++ b/src/SMAPI.Mods.ConsoleCommands/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Console Commands",
"Author": "SMAPI",
- "Version": "3.9.1",
+ "Version": "3.9.2",
"Description": "Adds SMAPI console commands that let you manipulate the game.",
"UniqueID": "SMAPI.ConsoleCommands",
"EntryDll": "ConsoleCommands.dll",
- "MinimumApiVersion": "3.9.1"
+ "MinimumApiVersion": "3.9.2"
}
diff --git a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs
index 2f6f1939..d9426d75 100644
--- a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs
+++ b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs
@@ -26,21 +26,17 @@ namespace StardewModdingAPI.Mods.ErrorHandler
public override void Entry(IModHelper helper)
{
// get SMAPI core types
- SCore core = SCore.Instance;
- LogManager logManager = core.GetType().GetField("LogManager", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(core) as LogManager;
- if (logManager == null)
- {
- this.Monitor.Log($"Can't access SMAPI's internal log manager. Error-handling patches won't be applied.", LogLevel.Error);
- return;
- }
+ IMonitor monitorForGame = this.GetMonitorForGame();
// apply patches
new GamePatcher(this.Monitor).Apply(
- new EventErrorPatch(logManager.MonitorForGame),
- new DialogueErrorPatch(logManager.MonitorForGame, this.Helper.Reflection),
+ new DialogueErrorPatch(monitorForGame, this.Helper.Reflection),
+ new EventPatches(monitorForGame),
+ new GameLocationPatches(monitorForGame),
new ObjectErrorPatch(),
new LoadErrorPatch(this.Monitor, this.OnSaveContentRemoved),
- new ScheduleErrorPatch(logManager.MonitorForGame),
+ new ScheduleErrorPatch(monitorForGame),
+ new SpriteBatchValidationPatches(),
new UtilityErrorPatches()
);
@@ -61,7 +57,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler
/// <summary>The method invoked when a save is loaded.</summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event arguments.</param>
- public void OnSaveLoaded(object sender, SaveLoadedEventArgs e)
+ private void OnSaveLoaded(object sender, SaveLoadedEventArgs e)
{
// show in-game warning for removed save content
if (this.IsSaveContentRemoved)
@@ -70,5 +66,16 @@ namespace StardewModdingAPI.Mods.ErrorHandler
Game1.addHUDMessage(new HUDMessage(this.Helper.Translation.Get("warn.invalid-content-removed"), HUDMessage.error_type));
}
}
+
+ /// <summary>Get the monitor with which to log game errors.</summary>
+ private IMonitor GetMonitorForGame()
+ {
+ SCore core = SCore.Instance;
+ LogManager logManager = core.GetType().GetField("LogManager", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(core) as LogManager;
+ if (logManager == null)
+ this.Monitor.Log("Can't access SMAPI's internal log manager. Some game errors may be reported as being from Error Handler.", LogLevel.Error);
+
+ return logManager?.MonitorForGame ?? this.Monitor;
+ }
}
}
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs
new file mode 100644
index 00000000..a15c1d32
--- /dev/null
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/EventPatches.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+#if HARMONY_2
+using HarmonyLib;
+#else
+using Harmony;
+#endif
+using StardewModdingAPI.Framework.Patching;
+using StardewValley;
+
+namespace StardewModdingAPI.Mods.ErrorHandler.Patches
+{
+ /// <summary>Harmony patches for <see cref="Event"/> which intercept errors to log more details.</summary>
+ /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
+ [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
+ [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
+ internal class EventPatches : IHarmonyPatch
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>Writes messages to the console and log file on behalf of the game.</summary>
+ private static IMonitor MonitorForGame;
+
+
+ /*********
+ ** Accessors
+ *********/
+ /// <inheritdoc />
+ public string Name => nameof(EventPatches);
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="monitorForGame">Writes messages to the console and log file on behalf of the game.</param>
+ public EventPatches(IMonitor monitorForGame)
+ {
+ EventPatches.MonitorForGame = monitorForGame;
+ }
+
+ /// <inheritdoc />
+#if HARMONY_2
+ public void Apply(Harmony harmony)
+#else
+ public void Apply(HarmonyInstance harmony)
+#endif
+ {
+ harmony.Patch(
+ original: AccessTools.Method(typeof(Event), nameof(Event.LogErrorAndHalt)),
+ postfix: new HarmonyMethod(this.GetType(), nameof(EventPatches.After_Event_LogErrorAndHalt))
+ );
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>The method to call after <see cref="Event.LogErrorAndHalt"/>.</summary>
+ /// <param name="e">The exception being logged.</param>
+ private static void After_Event_LogErrorAndHalt(Exception e)
+ {
+ EventPatches.MonitorForGame.Log(e.ToString(), LogLevel.Error);
+ }
+ }
+}
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/EventErrorPatch.cs b/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs
index fabc6cad..c10f2de7 100644
--- a/src/SMAPI.Mods.ErrorHandler/Patches/EventErrorPatch.cs
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatches.cs
@@ -15,7 +15,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
/// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
[SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
- internal class EventErrorPatch : IHarmonyPatch
+ internal class GameLocationPatches : IHarmonyPatch
{
/*********
** Fields
@@ -28,7 +28,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
** Accessors
*********/
/// <inheritdoc />
- public string Name => nameof(EventErrorPatch);
+ public string Name => nameof(GameLocationPatches);
/*********
@@ -36,9 +36,9 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
*********/
/// <summary>Construct an instance.</summary>
/// <param name="monitorForGame">Writes messages to the console and log file on behalf of the game.</param>
- public EventErrorPatch(IMonitor monitorForGame)
+ public GameLocationPatches(IMonitor monitorForGame)
{
- EventErrorPatch.MonitorForGame = monitorForGame;
+ GameLocationPatches.MonitorForGame = monitorForGame;
}
/// <inheritdoc />
@@ -55,7 +55,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
{
harmony.Patch(
original: AccessTools.Method(typeof(GameLocation), "checkEventPrecondition"),
- prefix: new HarmonyMethod(this.GetType(), nameof(EventErrorPatch.Before_GameLocation_CheckEventPrecondition))
+ prefix: new HarmonyMethod(this.GetType(), nameof(GameLocationPatches.Before_GameLocation_CheckEventPrecondition))
);
}
#endif
@@ -89,7 +89,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
/// <returns>Returns whether to execute the original method.</returns>
private static bool Before_GameLocation_CheckEventPrecondition(GameLocation __instance, ref int __result, string precondition, MethodInfo __originalMethod)
{
- const string key = nameof(EventErrorPatch.Before_GameLocation_CheckEventPrecondition);
+ const string key = nameof(GameLocationPatches.Before_GameLocation_CheckEventPrecondition);
if (!PatchHelper.StartIntercept(key))
return true;
@@ -101,7 +101,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches
catch (TargetInvocationException ex)
{
__result = -1;
- EventErrorPatch.MonitorForGame.Log($"Failed parsing event precondition ({precondition}):\n{ex.InnerException}", LogLevel.Error);
+ GameLocationPatches.MonitorForGame.Log($"Failed parsing event precondition ({precondition}):\n{ex.InnerException}", LogLevel.Error);
return false;
}
finally
diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs b/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs
new file mode 100644
index 00000000..0211cfb1
--- /dev/null
+++ b/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchValidationPatches.cs
@@ -0,0 +1,63 @@
+#if HARMONY_2
+using HarmonyLib;
+#else
+using Harmony;
+#endif
+using System;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Xna.Framework.Graphics;
+using StardewModdingAPI.Framework.Patching;
+
+namespace StardewModdingAPI.Mods.ErrorHandler.Patches
+{
+ /// <summary>Harmony patch for <see cref="SpriteBatch"/> to validate textures earlier.</summary>
+ /// <remarks>Patch methods must be static for Harmony to work correctly. See the Harmony documentation before renaming patch arguments.</remarks>
+ [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
+ [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "Argument names are defined by Harmony and methods are named for clarity.")]
+ internal class SpriteBatchValidationPatches : IHarmonyPatch
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <inheritdoc />
+ public string Name => nameof(SpriteBatchValidationPatches);
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <inheritdoc />
+#if HARMONY_2
+ public void Apply(Harmony harmony)
+#else
+ public void Apply(HarmonyInstance harmony)
+#endif
+ {
+ harmony.Patch(
+#if SMAPI_FOR_WINDOWS
+ original: AccessTools.Method(typeof(SpriteBatch), "InternalDraw"),
+#else
+ original: AccessTools.Method(typeof(SpriteBatch), "CheckValid", new[] { typeof(Texture2D) }),
+#endif
+ postfix: new HarmonyMethod(this.GetType(), nameof(SpriteBatchValidationPatches.After_SpriteBatch_CheckValid))
+ );
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+#if SMAPI_FOR_WINDOWS
+ /// <summary>The method to call instead of <see cref="SpriteBatch.InternalDraw"/>.</summary>
+ /// <param name="texture">The texture to validate.</param>
+#else
+ /// <summary>The method to call instead of <see cref="SpriteBatch.CheckValid"/>.</summary>
+ /// <param name="texture">The texture to validate.</param>
+#endif
+ private static void After_SpriteBatch_CheckValid(Texture2D texture)
+ {
+ if (texture?.IsDisposed == true)
+ throw new ObjectDisposedException("Cannot draw this texture because it's disposed.");
+ }
+ }
+}
diff --git a/src/SMAPI.Mods.ErrorHandler/manifest.json b/src/SMAPI.Mods.ErrorHandler/manifest.json
index bb9942d1..b6df0f49 100644
--- a/src/SMAPI.Mods.ErrorHandler/manifest.json
+++ b/src/SMAPI.Mods.ErrorHandler/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Error Handler",
"Author": "SMAPI",
- "Version": "3.9.1",
+ "Version": "3.9.2",
"Description": "Handles some common vanilla errors to log more useful info or avoid breaking the game.",
"UniqueID": "SMAPI.ErrorHandler",
"EntryDll": "ErrorHandler.dll",
- "MinimumApiVersion": "3.9.1"
+ "MinimumApiVersion": "3.9.2"
}
diff --git a/src/SMAPI.Mods.SaveBackup/manifest.json b/src/SMAPI.Mods.SaveBackup/manifest.json
index 95ee5144..4d2003e2 100644
--- a/src/SMAPI.Mods.SaveBackup/manifest.json
+++ b/src/SMAPI.Mods.SaveBackup/manifest.json
@@ -1,9 +1,9 @@
{
"Name": "Save Backup",
"Author": "SMAPI",
- "Version": "3.9.1",
+ "Version": "3.9.2",
"Description": "Automatically backs up all your saves once per day into its folder.",
"UniqueID": "SMAPI.SaveBackup",
"EntryDll": "SaveBackup.dll",
- "MinimumApiVersion": "3.9.1"
+ "MinimumApiVersion": "3.9.2"
}
diff --git a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs
index 89a22eaf..da312471 100644
--- a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs
+++ b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs
@@ -50,13 +50,13 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki
doc.LoadHtml(html);
// fetch game versions
- string stableVersion = doc.DocumentNode.SelectSingleNode("div[@class='game-stable-version']")?.InnerText;
- string betaVersion = doc.DocumentNode.SelectSingleNode("div[@class='game-beta-version']")?.InnerText;
+ string stableVersion = doc.DocumentNode.SelectSingleNode("//div[@class='game-stable-version']")?.InnerText;
+ string betaVersion = doc.DocumentNode.SelectSingleNode("//div[@class='game-beta-version']")?.InnerText;
if (betaVersion == stableVersion)
betaVersion = null;
// find mod entries
- HtmlNodeCollection modNodes = doc.DocumentNode.SelectNodes("table[@id='mod-list']//tr[@class='mod']");
+ HtmlNodeCollection modNodes = doc.DocumentNode.SelectNodes("//table[@id='mod-list']//tr[@class='mod']");
if (modNodes == null)
throw new InvalidOperationException("Can't parse wiki compatibility list, no mods found.");
diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs
index 86a97016..fd206d9d 100644
--- a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs
+++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs
@@ -177,12 +177,17 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning
}
// get mod type
- ModType type = ModType.Invalid;
- if (manifest != null)
+ ModType type;
{
- type = !string.IsNullOrWhiteSpace(manifest.ContentPackFor?.UniqueID)
- ? ModType.ContentPack
- : ModType.Smapi;
+ bool isContentPack = !string.IsNullOrWhiteSpace(manifest?.ContentPackFor?.UniqueID);
+ bool isSmapi = !string.IsNullOrWhiteSpace(manifest?.EntryDll);
+
+ if (isContentPack == isSmapi)
+ type = ModType.Invalid;
+ else if (isContentPack)
+ type = ModType.ContentPack;
+ else
+ type = ModType.Smapi;
}
// build result
diff --git a/src/SMAPI.Web/Controllers/ModsController.cs b/src/SMAPI.Web/Controllers/ModsController.cs
index 24e36709..c62ed605 100644
--- a/src/SMAPI.Web/Controllers/ModsController.cs
+++ b/src/SMAPI.Web/Controllers/ModsController.cs
@@ -62,7 +62,7 @@ namespace StardewModdingAPI.Web.Controllers
mods: this.Cache
.GetWikiMods()
.Select(mod => new ModModel(mod.Data))
- .OrderBy(p => Regex.Replace(p.Name.ToLower(), "[^a-z0-9]", "")), // ignore case, spaces, and special characters when sorting
+ .OrderBy(p => Regex.Replace((p.Name ?? "").ToLower(), "[^a-z0-9]", "")), // ignore case, spaces, and special characters when sorting
lastUpdated: metadata.LastUpdated,
isStale: this.Cache.IsStale(metadata.LastUpdated, this.StaleMinutes)
);
diff --git a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json
index 92149f4d..21514979 100644
--- a/src/SMAPI.Web/wwwroot/schemas/content-patcher.json
+++ b/src/SMAPI.Web/wwwroot/schemas/content-patcher.json
@@ -11,9 +11,9 @@
"title": "Format version",
"description": "The format version. You should always use the latest version to enable the latest features and avoid obsolete behavior.",
"type": "string",
- "const": "1.19.0",
+ "const": "1.20.0",
"@errorMessages": {
- "const": "Incorrect value '@value'. This should be set to the latest format version, currently '1.19.0'."
+ "const": "Incorrect value '@value'. This should be set to the latest format version, currently '1.20.0'."
}
},
"ConfigSchema": {
diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs
index 57c40bbf..54fb54ab 100644
--- a/src/SMAPI/Constants.cs
+++ b/src/SMAPI/Constants.cs
@@ -54,7 +54,7 @@ namespace StardewModdingAPI
** Public
****/
/// <summary>SMAPI's current semantic version.</summary>
- public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.9.1");
+ public static ISemanticVersion ApiVersion { get; } = new Toolkit.SemanticVersion("3.9.2");
/// <summary>The minimum supported version of Stardew Valley.</summary>
public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.5.4");
diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs
index 77dd6c72..32195fff 100644
--- a/src/SMAPI/Framework/ContentCoordinator.cs
+++ b/src/SMAPI/Framework/ContentCoordinator.cs
@@ -26,6 +26,9 @@ namespace StardewModdingAPI.Framework
/// <summary>An asset key prefix for assets from SMAPI mod folders.</summary>
private readonly string ManagedPrefix = "SMAPI";
+ /// <summary>Whether to enable more aggressive memory optimizations.</summary>
+ private readonly bool AggressiveMemoryOptimizations;
+
/// <summary>Encapsulates monitoring and logging.</summary>
private readonly IMonitor Monitor;
@@ -51,7 +54,7 @@ namespace StardewModdingAPI.Framework
private bool IsDisposed;
/// <summary>A lock used to prevent asynchronous changes to the content manager list.</summary>
- /// <remarks>The game may adds content managers in asynchronous threads (e.g. when populating the load screen).</remarks>
+ /// <remarks>The game may add content managers in asynchronous threads (e.g. when populating the load screen).</remarks>
private readonly ReaderWriterLockSlim ContentManagerLock = new ReaderWriterLockSlim();
/// <summary>A cache of ordered tilesheet IDs used by vanilla maps.</summary>
@@ -91,8 +94,10 @@ namespace StardewModdingAPI.Framework
/// <param name="reflection">Simplifies access to private code.</param>
/// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param>
/// <param name="onLoadingFirstAsset">A callback to invoke the first time *any* game content manager loads an asset.</param>
- public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset)
+ /// <param name="aggressiveMemoryOptimizations">Whether to enable more aggressive memory optimizations.</param>
+ public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, bool aggressiveMemoryOptimizations)
{
+ this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations;
this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor));
this.Reflection = reflection;
this.JsonHelper = jsonHelper;
@@ -108,11 +113,25 @@ namespace StardewModdingAPI.Framework
monitor: monitor,
reflection: reflection,
onDisposing: this.OnDisposing,
- onLoadingFirstAsset: onLoadingFirstAsset
+ onLoadingFirstAsset: onLoadingFirstAsset,
+ aggressiveMemoryOptimizations: aggressiveMemoryOptimizations
)
);
+ var contentManagerForAssetPropagation = new GameContentManagerForAssetPropagation(
+ name: nameof(GameContentManagerForAssetPropagation),
+ serviceProvider: serviceProvider,
+ rootDirectory: rootDirectory,
+ currentCulture: currentCulture,
+ coordinator: this,
+ monitor: monitor,
+ reflection: reflection,
+ onDisposing: this.OnDisposing,
+ onLoadingFirstAsset: onLoadingFirstAsset,
+ aggressiveMemoryOptimizations: aggressiveMemoryOptimizations
+ );
+ this.ContentManagers.Add(contentManagerForAssetPropagation);
this.VanillaContentManager = new LocalizedContentManager(serviceProvider, rootDirectory);
- this.CoreAssets = new CoreAssetPropagator(this.MainContentManager.AssertAndNormalizeAssetName, reflection);
+ this.CoreAssets = new CoreAssetPropagator(this.MainContentManager, contentManagerForAssetPropagation, reflection, aggressiveMemoryOptimizations);
}
/// <summary>Get a new content manager which handles reading files from the game content folder with support for interception.</summary>
@@ -130,7 +149,8 @@ namespace StardewModdingAPI.Framework
monitor: this.Monitor,
reflection: this.Reflection,
onDisposing: this.OnDisposing,
- onLoadingFirstAsset: this.OnLoadingFirstAsset
+ onLoadingFirstAsset: this.OnLoadingFirstAsset,
+ aggressiveMemoryOptimizations: this.AggressiveMemoryOptimizations
);
this.ContentManagers.Add(manager);
return manager;
@@ -157,7 +177,8 @@ namespace StardewModdingAPI.Framework
monitor: this.Monitor,
reflection: this.Reflection,
jsonHelper: this.JsonHelper,
- onDisposing: this.OnDisposing
+ onDisposing: this.OnDisposing,
+ aggressiveMemoryOptimizations: this.AggressiveMemoryOptimizations
);
this.ContentManagers.Add(manager);
return manager;
@@ -182,6 +203,17 @@ namespace StardewModdingAPI.Framework
});
}
+ /// <summary>Clean up when the player is returning to the title screen.</summary>
+ /// <remarks>This is called after the player returns to the title screen, but before <see cref="Game1.CleanupReturningToTitle"/> runs.</remarks>
+ public void OnReturningToTitleScreen()
+ {
+ this.ContentManagerLock.InReadLock(() =>
+ {
+ foreach (IContentManager contentManager in this.ContentManagers)
+ contentManager.OnReturningToTitleScreen();
+ });
+ }
+
/// <summary>Get whether this asset is mapped to a mod folder.</summary>
/// <param name="key">The asset key.</param>
public bool IsManagedAssetKey(string key)
@@ -290,7 +322,7 @@ namespace StardewModdingAPI.Framework
// reload core game assets
if (removedAssets.Any())
{
- IDictionary<string, bool> propagated = this.CoreAssets.Propagate(this.MainContentManager, removedAssets.ToDictionary(p => p.Key, p => p.Value)); // use an intercepted content manager
+ IDictionary<string, bool> propagated = this.CoreAssets.Propagate(removedAssets.ToDictionary(p => p.Key, p => p.Value)); // use an intercepted content manager
this.Monitor.Log($"Invalidated {removedAssets.Count} asset names ({string.Join(", ", removedAssets.Keys.OrderBy(p => p, StringComparer.OrdinalIgnoreCase))}); propagated {propagated.Count(p => p.Value)} core assets.", LogLevel.Trace);
}
else
diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
index 92264f8c..1a64dab8 100644
--- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs
@@ -5,12 +5,12 @@ using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Linq;
-using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Framework.Content;
using StardewModdingAPI.Framework.Exceptions;
using StardewModdingAPI.Framework.Reflection;
using StardewValley;
+using xTile;
namespace StardewModdingAPI.Framework.ContentManagers
{
@@ -29,6 +29,9 @@ namespace StardewModdingAPI.Framework.ContentManagers
/// <summary>Encapsulates monitoring and logging.</summary>
protected readonly IMonitor Monitor;
+ /// <summary>Whether to enable more aggressive memory optimizations.</summary>
+ protected readonly bool AggressiveMemoryOptimizations;
+
/// <summary>Whether the content coordinator has been disposed.</summary>
private bool IsDisposed;
@@ -49,16 +52,16 @@ namespace StardewModdingAPI.Framework.ContentManagers
/*********
** Accessors
*********/
- /// <summary>A name for the mod manager. Not guaranteed to be unique.</summary>
+ /// <inheritdoc />
public string Name { get; }
- /// <summary>The cu