diff options
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r-- | src/SMAPI/Framework/ContentCoordinator.cs | 12 | ||||
-rw-r--r-- | src/SMAPI/Framework/Events/EventManager.cs | 11 | ||||
-rw-r--r-- | src/SMAPI/Framework/Events/ManagedEvent.cs | 10 | ||||
-rw-r--r-- | src/SMAPI/Framework/Events/ModContentEvents.cs | 29 | ||||
-rw-r--r-- | src/SMAPI/Framework/Events/ModEvents.cs | 4 | ||||
-rw-r--r-- | src/SMAPI/Framework/SCore.cs | 41 |
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); |