diff options
Diffstat (limited to 'src/SMAPI/Framework/ModHelpers')
-rw-r--r-- | src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 189 |
1 files changed, 30 insertions, 159 deletions
diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index 4f5bd2f0..2dd8a2e3 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; -using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.Exceptions; @@ -74,12 +72,12 @@ namespace StardewModdingAPI.Framework.ModHelpers this.ContentManager = contentManager; this.ModFolderPath = modFolderPath; this.ModName = modName; - this.ModFolderPathFromContent = this.GetRelativePath(contentManager.FullRootDirectory, modFolderPath); + this.ModFolderPathFromContent = this.ContentManager.GetRelativePath(modFolderPath); this.Monitor = monitor; } /// <summary>Load content from the game folder or mod folder (if not already cached), and return it. When loading a <c>.png</c> file, this must be called outside the game's draw loop.</summary> - /// <typeparam name="T">The expected data type. The main supported types are <see cref="Texture2D"/> and dictionaries; other types may be supported by the game's content pipeline.</typeparam> + /// <typeparam name="T">The expected data type. The main supported types are <see cref="Map"/>, <see cref="Texture2D"/>, and dictionaries; other types may be supported by the game's content pipeline.</typeparam> /// <param name="key">The asset key to fetch (if the <paramref name="source"/> is <see cref="ContentSource.GameContent"/>), or the local path to a content file relative to the mod folder.</param> /// <param name="source">Where to search for a matching content asset.</param> /// <exception cref="ArgumentException">The <paramref name="key"/> is empty or contains invalid characters.</exception> @@ -88,9 +86,9 @@ namespace StardewModdingAPI.Framework.ModHelpers { SContentLoadException GetContentError(string reasonPhrase) => new SContentLoadException($"{this.ModName} failed loading content asset '{key}' from {source}: {reasonPhrase}."); - this.AssertValidAssetKeyFormat(key); try { + this.ContentManager.AssertValidAssetKeyFormat(key); switch (source) { case ContentSource.GameContent: @@ -103,60 +101,32 @@ namespace StardewModdingAPI.Framework.ModHelpers throw GetContentError($"there's no matching file at path '{file.FullName}'."); // get asset path - string assetPath = this.GetModAssetPath(key, file.FullName); + string assetName = this.GetModAssetPath(key, file.FullName); // try cache - if (this.ContentManager.IsLoaded(assetPath)) - return this.ContentManager.Load<T>(assetPath); + if (this.ContentManager.IsLoaded(assetName)) + return this.ContentManager.Load<T>(assetName); - // load content - switch (file.Extension.ToLower()) + // fix map tilesheets + if (file.Extension.ToLower() == ".tbin") { - // XNB file - case ".xnb": - { - T asset = this.ContentManager.Load<T>(assetPath); - if (asset is Map) - this.FixLocalMapTilesheets(asset as Map, key); - return asset; - } - - // unpacked map - case ".tbin": - { - // validate - if (typeof(T) != typeof(Map)) - throw GetContentError($"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Map)}'."); - - // fetch & cache - FormatManager formatManager = FormatManager.Instance; - Map map = formatManager.LoadMap(file.FullName); - this.FixLocalMapTilesheets(map, key); - - // inject map - this.ContentManager.Inject(assetPath, map); - return (T)(object)map; - } - - // unpacked image - case ".png": - // validate - if (typeof(T) != typeof(Texture2D)) - throw GetContentError($"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Texture2D)}'."); - - // fetch & cache - using (FileStream stream = File.OpenRead(file.FullName)) - { - Texture2D texture = Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream); - texture = this.PremultiplyTransparency(texture); - this.ContentManager.Inject(assetPath, texture); - return (T)(object)texture; - } - - default: - throw GetContentError($"unknown file extension '{file.Extension}'; must be one of '.png', '.tbin', or '.xnb'."); + // validate + if (typeof(T) != typeof(Map)) + throw GetContentError($"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Map)}'."); + + // fetch & cache + FormatManager formatManager = FormatManager.Instance; + Map map = formatManager.LoadMap(file.FullName); + this.FixLocalMapTilesheets(map, key); + + // inject map + this.ContentManager.Inject(assetName, map, this.ContentManager); + return (T)(object)map; } + // load through content manager + return this.ContentManager.Load<T>(assetName); + default: throw GetContentError($"unknown content source '{source}'."); } @@ -264,8 +234,8 @@ namespace StardewModdingAPI.Framework.ModHelpers try { string key = - this.TryLoadTilesheetImageSource(relativeMapFolder, seasonalImageSource) - ?? this.TryLoadTilesheetImageSource(relativeMapFolder, imageSource); + this.GetTilesheetAssetName(relativeMapFolder, seasonalImageSource) + ?? this.GetTilesheetAssetName(relativeMapFolder, imageSource); if (key != null) { tilesheet.ImageSource = key; @@ -282,33 +252,22 @@ namespace StardewModdingAPI.Framework.ModHelpers } } - /// <summary>Load a tilesheet image source if the file exists.</summary> - /// <param name="relativeMapFolder">The folder path containing the map, relative to the mod folder.</param> + /// <summary>Get the actual asset name for a tilesheet.</summary> + /// <param name="modRelativeMapFolder">The folder path containing the map, relative to the mod folder.</param> /// <param name="imageSource">The tilesheet image source to load.</param> - /// <returns>Returns the loaded asset key (if it was loaded successfully).</returns> + /// <returns>Returns the asset name.</returns> /// <remarks>See remarks on <see cref="FixLocalMapTilesheets"/>.</remarks> - private string TryLoadTilesheetImageSource(string relativeMapFolder, string imageSource) + private string GetTilesheetAssetName(string modRelativeMapFolder, string imageSource) { if (imageSource == null) return null; // check relative to map file { - string localKey = Path.Combine(relativeMapFolder, imageSource); + string localKey = Path.Combine(modRelativeMapFolder, imageSource); FileInfo localFile = this.GetModFile(localKey); if (localFile.Exists) - { - try - { - this.Load<Texture2D>(localKey); - } - catch (Exception ex) - { - throw new ContentLoadException($"The local '{imageSource}' tilesheet couldn't be loaded.", ex); - } - return this.GetActualAssetKey(localKey); - } } // check relative to content folder @@ -343,18 +302,6 @@ namespace StardewModdingAPI.Framework.ModHelpers return null; } - /// <summary>Assert that the given key has a valid format.</summary> - /// <param name="key">The asset key to check.</param> - /// <exception cref="ArgumentException">The asset key is empty or contains invalid characters.</exception> - [SuppressMessage("ReSharper", "UnusedParameter.Local", Justification = "Parameter is only used for assertion checks by design.")] - private void AssertValidAssetKeyFormat(string key) - { - if (string.IsNullOrWhiteSpace(key)) - throw new ArgumentException("The asset key or local path is empty."); - if (key.Intersect(Path.GetInvalidPathChars()).Any()) - throw new ArgumentException("The asset key or local path contains invalid characters."); - } - /// <summary>Get a file from the mod folder.</summary> /// <param name="path">The asset path relative to the mod folder.</param> private FileInfo GetModFile(string path) @@ -400,81 +347,5 @@ namespace StardewModdingAPI.Framework.ModHelpers return absolutePath; #endif } - - /// <summary>Get a directory path relative to a given root.</summary> - /// <param name="rootPath">The root path from which the path should be relative.</param> - /// <param name="targetPath">The target file path.</param> - private string GetRelativePath(string rootPath, string targetPath) - { - // convert to URIs - Uri from = new Uri(rootPath + "/"); - Uri to = new Uri(targetPath + "/"); - if (from.Scheme != to.Scheme) - throw new InvalidOperationException($"Can't get path for '{targetPath}' relative to '{rootPath}'."); - - // get relative path - return Uri.UnescapeDataString(from.MakeRelativeUri(to).ToString()) - .Replace(Path.DirectorySeparatorChar == '/' ? '\\' : '/', Path.DirectorySeparatorChar); // use correct separator for platform - } - - /// <summary>Premultiply a texture's alpha values to avoid transparency issues in the game. This is only possible if the game isn't currently drawing.</summary> - /// <param name="texture">The texture to premultiply.</param> - /// <returns>Returns a premultiplied texture.</returns> - /// <remarks>Based on <a href="https://gist.github.com/Layoric/6255384">code by Layoric</a>.</remarks> - private Texture2D PremultiplyTransparency(Texture2D texture) - { - // validate - if (Context.IsInDrawLoop) - throw new NotSupportedException("Can't load a PNG file while the game is drawing to the screen. Make sure you load content outside the draw loop."); - - // process texture - SpriteBatch spriteBatch = Game1.spriteBatch; - GraphicsDevice gpu = Game1.graphics.GraphicsDevice; - using (RenderTarget2D renderTarget = new RenderTarget2D(Game1.graphics.GraphicsDevice, texture.Width, texture.Height)) - { - // create blank render target to premultiply - gpu.SetRenderTarget(renderTarget); - gpu.Clear(Color.Black); - - // multiply each color by the source alpha, and write just the color values into the final texture - spriteBatch.Begin(SpriteSortMode.Immediate, new BlendState - { - ColorDestinationBlend = Blend.Zero, - ColorWriteChannels = ColorWriteChannels.Red | ColorWriteChannels.Green | ColorWriteChannels.Blue, - AlphaDestinationBlend = Blend.Zero, - AlphaSourceBlend = Blend.SourceAlpha, - ColorSourceBlend = Blend.SourceAlpha - }); - spriteBatch.Draw(texture, texture.Bounds, Color.White); - spriteBatch.End(); - - // copy the alpha values from the source texture into the final one without multiplying them - spriteBatch.Begin(SpriteSortMode.Immediate, new BlendState - { - ColorWriteChannels = ColorWriteChannels.Alpha, - AlphaDestinationBlend = Blend.Zero, - ColorDestinationBlend = Blend.Zero, - AlphaSourceBlend = Blend.One, - ColorSourceBlend = Blend.One - }); - spriteBatch.Draw(texture, texture.Bounds, Color.White); - spriteBatch.End(); - - // release GPU - gpu.SetRenderTarget(null); - - // extract premultiplied data - Color[] data = new Color[texture.Width * texture.Height]; - renderTarget.GetData(data); - - // unset texture from GPU to regain control - gpu.Textures[0] = null; - - // update texture with premultiplied data - texture.SetData(data); - } - - return texture; - } } } |