summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs12
-rw-r--r--src/SMAPI/Framework/Events/EventManager.cs11
-rw-r--r--src/SMAPI/Framework/Events/ManagedEvent.cs10
-rw-r--r--src/SMAPI/Framework/Events/ModContentEvents.cs29
-rw-r--r--src/SMAPI/Framework/Events/ModEvents.cs4
-rw-r--r--src/SMAPI/Framework/SCore.cs41
6 files changed, 103 insertions, 4 deletions
diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs
index bf944e23..22ae0a18 100644
--- a/src/SMAPI/Framework/ContentCoordinator.cs
+++ b/src/SMAPI/Framework/ContentCoordinator.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.Xna.Framework.Content;
+using StardewModdingAPI.Events;
using StardewModdingAPI.Framework.Content;
using StardewModdingAPI.Framework.ContentManagers;
using StardewModdingAPI.Framework.Reflection;
@@ -70,6 +71,9 @@ namespace StardewModdingAPI.Framework
/// <summary>The language enum values indexed by locale code.</summary>
private Lazy<Dictionary<string, LocalizedContentManager.LanguageCode>> LocaleCodes;
+ /// <summary>Get the load/edit operations to apply to an asset by querying registered <see cref="IContentEvents.AssetRequested"/> event handlers.</summary>
+ private readonly Func<IAssetInfo, IList<AssetOperationGroup>> RequestAssetOperations;
+
/// <summary>The cached asset load/edit operations to apply, indexed by asset name.</summary>
private readonly TickCacheDictionary<IAssetName, AssetOperationGroup[]> AssetOperationsByKey = new();
@@ -105,13 +109,15 @@ namespace StardewModdingAPI.Framework
/// <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>
/// <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)
+ /// <param name="requestAssetOperations">Get the load/edit operations to apply to an asset by querying registered <see cref="IContentEvents.AssetRequested"/> event handlers.</param>
+ public ContentCoordinator(IServiceProvider serviceProvider, string rootDirectory, CultureInfo currentCulture, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onLoadingFirstAsset, bool aggressiveMemoryOptimizations, Func<IAssetInfo, IList<AssetOperationGroup>> requestAssetOperations)
{
this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations;
this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor));
this.Reflection = reflection;
this.JsonHelper = jsonHelper;
this.OnLoadingFirstAsset = onLoadingFirstAsset;
+ this.RequestAssetOperations = requestAssetOperations;
this.FullRootDirectory = Path.Combine(Constants.GamePath, rootDirectory);
this.ContentManagers.Add(
this.MainContentManager = new GameContentManager(
@@ -560,6 +566,10 @@ namespace StardewModdingAPI.Framework
/// <param name="info">The asset info to load or edit.</param>
private IEnumerable<AssetOperationGroup> GetAssetOperationsWithoutCache<T>(IAssetInfo info)
{
+ // new content API
+ foreach (AssetOperationGroup group in this.RequestAssetOperations(info))
+ yield return group;
+
// legacy load operations
foreach (ModLinked<IAssetLoader> loader in this.Loaders)
{
diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs
index fa4d564d..8142f00e 100644
--- a/src/SMAPI/Framework/Events/EventManager.cs
+++ b/src/SMAPI/Framework/Events/EventManager.cs
@@ -11,6 +11,13 @@ namespace StardewModdingAPI.Framework.Events
** Events
*********/
/****
+ ** Content
+ ****/
+ /// <inheritdoc cref="IContentEvents.AssetRequested" />
+ public readonly ManagedEvent<AssetRequestedEventArgs> AssetRequested;
+
+
+ /****
** Display
****/
/// <inheritdoc cref="IDisplayEvents.MenuChanged" />
@@ -189,7 +196,9 @@ namespace StardewModdingAPI.Framework.Events
return new ManagedEvent<TEventArgs>($"{typeName}.{eventName}", modRegistry, isPerformanceCritical);
}
- // init events (new)
+ // init events
+ this.AssetRequested = ManageEventOf<AssetRequestedEventArgs>(nameof(IModEvents.Content), nameof(IContentEvents.AssetRequested));
+
this.MenuChanged = ManageEventOf<MenuChangedEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.MenuChanged));
this.Rendering = ManageEventOf<RenderingEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.Rendering), isPerformanceCritical: true);
this.Rendered = ManageEventOf<RenderedEventArgs>(nameof(IModEvents.Display), nameof(IDisplayEvents.Rendered), isPerformanceCritical: true);
diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs
index a200393d..154ef659 100644
--- a/src/SMAPI/Framework/Events/ManagedEvent.cs
+++ b/src/SMAPI/Framework/Events/ManagedEvent.cs
@@ -100,6 +100,14 @@ namespace StardewModdingAPI.Framework.Events
/// <param name="match">A lambda which returns true if the event should be raised for the given mod.</param>
public void Raise(TEventArgs args, Func<IModMetadata, bool> match = null)
{
+ this.Raise((_, invoke) => invoke(args), match);
+ }
+
+ /// <summary>Raise the event and notify all handlers.</summary>
+ /// <param name="invoke">Invoke an event handler. This receives the mod which registered the handler, and should invoke the callback with the event arguments to pass it.</param>
+ /// <param name="match">A lambda which returns true if the event should be raised for the given mod.</param>
+ public void Raise(Action<IModMetadata, Action<TEventArgs>> invoke, Func<IModMetadata, bool> match = null)
+ {
// skip if no handlers
if (this.Handlers.Count == 0)
return;
@@ -128,7 +136,7 @@ namespace StardewModdingAPI.Framework.Events
try
{
- handler.Handler.Invoke(null, args);
+ invoke(handler.SourceMod, args => handler.Handler.Invoke(null, args));
}
catch (Exception ex)
{
diff --git a/src/SMAPI/Framework/Events/ModContentEvents.cs b/src/SMAPI/Framework/Events/ModContentEvents.cs
new file mode 100644
index 00000000..b4d4279c
--- /dev/null
+++ b/src/SMAPI/Framework/Events/ModContentEvents.cs
@@ -0,0 +1,29 @@
+using System;
+using StardewModdingAPI.Events;
+
+namespace StardewModdingAPI.Framework.Events
+{
+ /// <inheritdoc cref="IContentEvents" />
+ internal class ModContentEvents : ModEventsBase, IContentEvents
+ {
+ /*********
+ ** Accessors
+ *********/
+ /// <inheritdoc />
+ public event EventHandler<AssetRequestedEventArgs> AssetRequested
+ {
+ add => this.EventManager.AssetRequested.Add(value, this.Mod);
+ remove => this.EventManager.AssetRequested.Remove(value);
+ }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="mod">The mod which uses this instance.</param>
+ /// <param name="eventManager">The underlying event manager.</param>
+ internal ModContentEvents(IModMetadata mod, EventManager eventManager)
+ : base(mod, eventManager) { }
+ }
+}
diff --git a/src/SMAPI/Framework/Events/ModEvents.cs b/src/SMAPI/Framework/Events/ModEvents.cs
index 0c365d42..1fb3482c 100644
--- a/src/SMAPI/Framework/Events/ModEvents.cs
+++ b/src/SMAPI/Framework/Events/ModEvents.cs
@@ -9,6 +9,9 @@ namespace StardewModdingAPI.Framework.Events
** Accessors
*********/
/// <inheritdoc />
+ public IContentEvents Content { get; }
+
+ /// <inheritdoc />
public IDisplayEvents Display { get; }
/// <inheritdoc />
@@ -38,6 +41,7 @@ namespace StardewModdingAPI.Framework.Events
/// <param name="eventManager">The underlying event manager.</param>
public ModEvents(IModMetadata mod, EventManager eventManager)
{
+ this.Content = new ModContentEvents(mod, eventManager);
this.Display = new ModDisplayEvents(mod, eventManager);
this.GameLoop = new ModGameLoopEvents(mod, eventManager);
this.Input = new ModInputEvents(mod, eventManager);
diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs
index 342d6415..f0340cf5 100644
--- a/src/SMAPI/Framework/SCore.cs
+++ b/src/SMAPI/Framework/SCore.cs
@@ -1106,6 +1106,35 @@ namespace StardewModdingAPI.Framework
this.EventManager.DayEnding.RaiseEmpty();
}
+ /// <summary>Get the load/edit operations to apply to an asset by querying registered <see cref="IContentEvents.AssetRequested"/> event handlers.</summary>
+ /// <param name="asset">The asset info being requested.</param>
+ private IList<AssetOperationGroup> RequestAssetOperations(IAssetInfo asset)
+ {
+ List<AssetOperationGroup> operations = new();
+
+ this.EventManager.AssetRequested.Raise(
+ invoke: (mod, invoke) =>
+ {
+ AssetRequestedEventArgs args = new(mod, asset.Name);
+
+ invoke(args);
+
+ if (args.LoadOperations.Any() || args.EditOperations.Any())
+ {
+ operations.Add(
+ new AssetOperationGroup(
+ mod,
+ args.LoadOperations.Select(p => new AssetLoadOperation(mod, assetInfo => p.GetData(assetInfo))).ToArray(),
+ args.EditOperations.Select(p => new AssetEditOperation(mod, assetInfo => p.ApplyEdit(assetInfo))).ToArray()
+ )
+ );
+ }
+ }
+ );
+
+ return operations;
+ }
+
/// <summary>Raised immediately before the player returns to the title screen.</summary>
private void OnReturningToTitle()
{
@@ -1142,7 +1171,17 @@ namespace StardewModdingAPI.Framework
// Game1._temporaryContent initializing from SGame constructor
if (this.ContentCore == null)
{
- this.ContentCore = new ContentCoordinator(serviceProvider, rootDirectory, Thread.CurrentThread.CurrentUICulture, this.Monitor, this.Reflection, this.Toolkit.JsonHelper, this.InitializeBeforeFirstAssetLoaded, this.Settings.AggressiveMemoryOptimizations);
+ this.ContentCore = new ContentCoordinator(
+ serviceProvider: serviceProvider,
+ rootDirectory: rootDirectory,
+ currentCulture: Thread.CurrentThread.CurrentUICulture,
+ monitor: this.Monitor,
+ reflection: this.Reflection,
+ jsonHelper: this.Toolkit.JsonHelper,
+ onLoadingFirstAsset: this.InitializeBeforeFirstAssetLoaded,
+ aggressiveMemoryOptimizations: this.Settings.AggressiveMemoryOptimizations,
+ requestAssetOperations: this.RequestAssetOperations
+ );
if (this.ContentCore.Language != this.Translator.LocaleEnum)
this.Translator.SetLocale(this.ContentCore.GetLocale(), this.ContentCore.Language);