summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/ContentCoordinator.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework/ContentCoordinator.cs')
-rw-r--r--src/SMAPI/Framework/ContentCoordinator.cs123
1 files changed, 106 insertions, 17 deletions
diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs
index 397a9d90..c2614001 100644
--- a/src/SMAPI/Framework/ContentCoordinator.cs
+++ b/src/SMAPI/Framework/ContentCoordinator.cs
@@ -5,10 +5,14 @@ using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.Xna.Framework.Content;
+using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI.Framework.Content;
+using StardewModdingAPI.Framework.ContentManagers;
using StardewModdingAPI.Framework.Reflection;
+using StardewModdingAPI.Framework.Utilities;
using StardewModdingAPI.Metadata;
using StardewValley;
+using xTile;
namespace StardewModdingAPI.Framework
{
@@ -18,6 +22,9 @@ namespace StardewModdingAPI.Framework
/*********
** Properties
*********/
+ /// <summary>An asset key prefix for assets from SMAPI mod folders.</summary>
+ private readonly string ManagedPrefix = "SMAPI";
+
/// <summary>Encapsulates monitoring and logging.</summary>
private readonly IMonitor Monitor;
@@ -28,7 +35,7 @@ namespace StardewModdingAPI.Framework
private readonly Reflector Reflection;
/// <summary>The loaded content managers (including the <see cref="MainContentManager"/>).</summary>
- private readonly IList<SContentManager> ContentManagers = new List<SContentManager>();
+ private readonly IList<IContentManager> ContentManagers = new List<IContentManager>();
/// <summary>Whether the content coordinator has been disposed.</summary>
private bool IsDisposed;
@@ -38,7 +45,7 @@ namespace StardewModdingAPI.Framework
** Accessors
*********/
/// <summary>The primary content manager used for most assets.</summary>
- public SContentManager MainContentManager { get; private set; }
+ public GameContentManager MainContentManager { get; private set; }
/// <summary>The current language as a constant.</summary>
public LocalizedContentManager.LanguageCode Language => this.MainContentManager.Language;
@@ -68,28 +75,110 @@ namespace StardewModdingAPI.Framework
this.Reflection = reflection;
this.FullRootDirectory = Path.Combine(Constants.ExecutionPath, rootDirectory);
this.ContentManagers.Add(
- this.MainContentManager = new SContentManager("Game1.content", serviceProvider, rootDirectory, currentCulture, this, monitor, reflection, this.OnDisposing, isModFolder: false)
+ this.MainContentManager = new GameContentManager("Game1.content", serviceProvider, rootDirectory, currentCulture, this, monitor, reflection, this.OnDisposing)
);
- this.CoreAssets = new CoreAssetPropagator(this.MainContentManager.NormaliseAssetName, reflection);
+ this.CoreAssets = new CoreAssetPropagator(this.MainContentManager.AssertAndNormaliseAssetName, reflection);
+ }
+
+ /// <summary>Get a new content manager which handles reading files from the game content folder with support for interception.</summary>
+ /// <param name="name">A name for the mod manager. Not guaranteed to be unique.</param>
+ public GameContentManager CreateGameContentManager(string name)
+ {
+ GameContentManager manager = new GameContentManager(name, this.MainContentManager.ServiceProvider, this.MainContentManager.RootDirectory, this.MainContentManager.CurrentCulture, this, this.Monitor, this.Reflection, this.OnDisposing);
+ this.ContentManagers.Add(manager);
+ return manager;
}
- /// <summary>Get a new content manager which defers loading to the content core.</summary>
+ /// <summary>Get a new content manager which handles reading files from a SMAPI mod folder with support for unpacked files.</summary>
/// <param name="name">A name for the mod manager. Not guaranteed to be unique.</param>
- /// <param name="isModFolder">Whether this content manager is wrapped around a mod folder.</param>
- /// <param name="rootDirectory">The root directory to search for content (or <c>null</c>. for the default)</param>
- public SContentManager CreateContentManager(string name, bool isModFolder, string rootDirectory = null)
+ /// <param name="rootDirectory">The root directory to search for content (or <c>null</c> for the default).</param>
+ public ModContentManager CreateModContentManager(string name, string rootDirectory)
{
- SContentManager manager = new SContentManager(name, this.MainContentManager.ServiceProvider, rootDirectory ?? this.MainContentManager.RootDirectory, this.MainContentManager.CurrentCulture, this, this.Monitor, this.Reflection, this.OnDisposing, isModFolder);
+ ModContentManager manager = new ModContentManager(name, this.MainContentManager.ServiceProvider, rootDirectory, this.MainContentManager.CurrentCulture, this, this.Monitor, this.Reflection, this.OnDisposing);
this.ContentManagers.Add(manager);
return manager;
}
/// <summary>Get the current content locale.</summary>
- public string GetLocale() => this.MainContentManager.GetLocale(LocalizedContentManager.CurrentLanguageCode);
+ public string GetLocale()
+ {
+ return this.MainContentManager.GetLocale(LocalizedContentManager.CurrentLanguageCode);
+ }
+
+ /// <summary>Get whether this asset is mapped to a mod folder.</summary>
+ /// <param name="key">The asset key.</param>
+ public bool IsManagedAssetKey(string key)
+ {
+ return key.StartsWith(this.ManagedPrefix);
+ }
+
+ /// <summary>Parse a managed SMAPI asset key which maps to a mod folder.</summary>
+ /// <param name="key">The asset key.</param>
+ /// <param name="contentManagerID">The unique name for the content manager which should load this asset.</param>
+ /// <param name="relativePath">The relative path within the mod folder.</param>
+ /// <returns>Returns whether the asset was parsed successfully.</returns>
+ public bool TryParseManagedAssetKey(string key, out string contentManagerID, out string relativePath)
+ {
+ contentManagerID = null;
+ relativePath = null;
+
+ // not a managed asset
+ if (!key.StartsWith(this.ManagedPrefix))
+ return false;
- /// <summary>Convert an absolute file path into a appropriate asset name.</summary>
- /// <param name="absolutePath">The absolute path to the file.</param>
- public string GetAssetNameFromFilePath(string absolutePath) => this.MainContentManager.GetAssetNameFromFilePath(absolutePath, ContentSource.GameContent);
+ // parse
+ string[] parts = PathUtilities.GetSegments(key, 3);
+ if (parts.Length != 3) // managed key prefix, mod id, relative path
+ return false;
+ contentManagerID = Path.Combine(parts[0], parts[1]);
+ relativePath = parts[2];
+ return true;
+ }
+
+ /// <summary>Get the managed asset key prefix for a mod.</summary>
+ /// <param name="modID">The mod's unique ID.</param>
+ public string GetManagedAssetPrefix(string modID)
+ {
+ return Path.Combine(this.ManagedPrefix, modID.ToLower());
+ }
+
+ /// <summary>Get a copy of an asset from a mod folder.</summary>
+ /// <typeparam name="T">The asset type.</typeparam>
+ /// <param name="internalKey">The internal asset key.</param>
+ /// <param name="contentManagerID">The unique name for the content manager which should load this asset.</param>
+ /// <param name="relativePath">The internal SMAPI asset key.</param>
+ /// <param name="language">The language code for which to load content.</param>
+ public T LoadAndCloneManagedAsset<T>(string internalKey, string contentManagerID, string relativePath, LocalizedContentManager.LanguageCode language)
+ {
+ // get content manager
+ IContentManager contentManager = this.ContentManagers.FirstOrDefault(p => p.Name == contentManagerID);
+ if (contentManager == null)
+ throw new InvalidOperationException($"The '{contentManagerID}' prefix isn't handled by any mod.");
+
+ // get cloned asset
+ T data = contentManager.Load<T>(internalKey, language);
+ switch (data as object)
+ {
+ case Texture2D source:
+ {
+ int[] pixels = new int[source.Width * source.Height];
+ source.GetData(pixels);
+
+ Texture2D clone = new Texture2D(source.GraphicsDevice, source.Width, source.Height);
+ clone.SetData(pixels);
+ return (T)(object)clone;
+ }
+
+ case Dictionary<string, string> source:
+ return (T)(object)new Dictionary<string, string>(source);
+
+ case Dictionary<int, string> source:
+ return (T)(object)new Dictionary<int, string>(source);
+
+ default:
+ return data;
+ }
+ }
/// <summary>Purge assets from the cache that match one of the interceptors.</summary>
/// <param name="editors">The asset editors for which to purge matching assets.</param>
@@ -129,7 +218,7 @@ namespace StardewModdingAPI.Framework
string locale = this.GetLocale();
return this.InvalidateCache((assetName, type) =>
{
- IAssetInfo info = new AssetInfo(locale, assetName, type, this.MainContentManager.NormaliseAssetName);
+ IAssetInfo info = new AssetInfo(locale, assetName, type, this.MainContentManager.AssertAndNormaliseAssetName);
return predicate(info);
});
}
@@ -142,7 +231,7 @@ namespace StardewModdingAPI.Framework
{
// invalidate cache
HashSet<string> removedAssetNames = new HashSet<string>();
- foreach (SContentManager contentManager in this.ContentManagers)
+ foreach (IContentManager contentManager in this.ContentManagers)
{
foreach (string name in contentManager.InvalidateCache(predicate, dispose))
removedAssetNames.Add(name);
@@ -172,7 +261,7 @@ namespace StardewModdingAPI.Framework
this.IsDisposed = true;
this.Monitor.Log("Disposing the content coordinator. Content managers will no longer be usable after this point.", LogLevel.Trace);
- foreach (SContentManager contentManager in this.ContentManagers)
+ foreach (IContentManager contentManager in this.ContentManagers)
contentManager.Dispose();
this.ContentManagers.Clear();
this.MainContentManager = null;
@@ -184,7 +273,7 @@ namespace StardewModdingAPI.Framework
*********/
/// <summary>A callback invoked when a content manager is disposed.</summary>
/// <param name="contentManager">The content manager being disposed.</param>
- private void OnDisposing(SContentManager contentManager)
+ private void OnDisposing(IContentManager contentManager)
{
if (this.IsDisposed)
return;