From b2e88bccf63545d950f4cbf47a0466321c614245 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 8 Mar 2017 15:25:30 -0500 Subject: add dictionary/image content helpers for more intuitive usage (#173) --- .../Content/ContentEventHelperForImage.cs | 70 ++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/StardewModdingAPI/Framework/Content/ContentEventHelperForImage.cs (limited to 'src/StardewModdingAPI/Framework/Content/ContentEventHelperForImage.cs') diff --git a/src/StardewModdingAPI/Framework/Content/ContentEventHelperForImage.cs b/src/StardewModdingAPI/Framework/Content/ContentEventHelperForImage.cs new file mode 100644 index 00000000..23760f0f --- /dev/null +++ b/src/StardewModdingAPI/Framework/Content/ContentEventHelperForImage.cs @@ -0,0 +1,70 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace StardewModdingAPI.Framework.Content +{ + /// Encapsulates access and changes to dictionary content being read from a data file. + internal class ContentEventHelperForImage : ContentEventBaseHelper, IContentEventHelperForImage + { + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The content's locale code, if the content is localised. + /// The normalised asset name being read. + /// The content data being read. + /// Normalises an asset key to match the cache key. + public ContentEventHelperForImage(string locale, string assetName, Texture2D data, Func getNormalisedPath) + : base(locale, assetName, data, getNormalisedPath) { } + + /// Overwrite part of the image. + /// The image to patch into the content. + /// The part of the to copy (or null to take the whole texture). This must be within the bounds of the texture. + /// The part of the content to patch (or null to patch the whole texture). The original content within this area will be erased. This must be within the bounds of the existing spritesheet. + /// Indicates how an image should be patched. + /// One of the arguments is null. + /// The is outside the bounds of the spritesheet. + public void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace) + { + // get texture + Texture2D target = this.Data; + + // get areas + sourceArea = sourceArea ?? new Rectangle(0, 0, source.Width, source.Height); + targetArea = targetArea ?? new Rectangle(0, 0, Math.Min(sourceArea.Value.Width, target.Width), Math.Min(sourceArea.Value.Height, target.Height)); + + // validate + if (source == null) + throw new ArgumentNullException(nameof(source), "Can't patch from a null source texture."); + if (sourceArea.Value.X < 0 || sourceArea.Value.Y < 0 || sourceArea.Value.Right > source.Width || sourceArea.Value.Bottom > source.Height) + throw new ArgumentOutOfRangeException(nameof(sourceArea), "The source area is outside the bounds of the source texture."); + if (targetArea.Value.X < 0 || targetArea.Value.Y < 0 || targetArea.Value.Right > target.Width || targetArea.Value.Bottom > target.Height) + throw new ArgumentOutOfRangeException(nameof(targetArea), "The target area is outside the bounds of the target texture."); + if (sourceArea.Value.Width != targetArea.Value.Width || sourceArea.Value.Height != targetArea.Value.Height) + throw new InvalidOperationException("The source and target areas must be the same size."); + + // get source data + int pixelCount = sourceArea.Value.Width * sourceArea.Value.Height; + Color[] sourceData = new Color[pixelCount]; + source.GetData(0, sourceArea, sourceData, 0, pixelCount); + + // merge data in overlay mode + if (patchMode == PatchMode.Overlay) + { + Color[] newData = new Color[targetArea.Value.Width * targetArea.Value.Height]; + target.GetData(0, targetArea, newData, 0, newData.Length); + for (int i = 0; i < sourceData.Length; i++) + { + Color pixel = sourceData[i]; + if (pixel.A != 0) // not transparent + newData[i] = pixel; + } + sourceData = newData; + } + + // patch target texture + target.SetData(0, targetArea, sourceData, 0, pixelCount); + } + } +} -- cgit From e3522edddd0636116bd4772b4e4dbfce9c7c0f8d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 10 Mar 2017 12:00:11 -0500 Subject: extend base content helper to support null content (#173) --- .../Framework/Content/ContentEventBaseHelper.cs | 98 ------------------ .../Framework/Content/ContentEventData.cs | 111 +++++++++++++++++++++ .../Framework/Content/ContentEventHelper.cs | 2 +- .../Content/ContentEventHelperForDictionary.cs | 2 +- .../Content/ContentEventHelperForImage.cs | 2 +- src/StardewModdingAPI/IContentEventData.cs | 3 + src/StardewModdingAPI/StardewModdingAPI.csproj | 4 +- 7 files changed, 119 insertions(+), 103 deletions(-) delete mode 100644 src/StardewModdingAPI/Framework/Content/ContentEventBaseHelper.cs create mode 100644 src/StardewModdingAPI/Framework/Content/ContentEventData.cs (limited to 'src/StardewModdingAPI/Framework/Content/ContentEventHelperForImage.cs') diff --git a/src/StardewModdingAPI/Framework/Content/ContentEventBaseHelper.cs b/src/StardewModdingAPI/Framework/Content/ContentEventBaseHelper.cs deleted file mode 100644 index a89fe337..00000000 --- a/src/StardewModdingAPI/Framework/Content/ContentEventBaseHelper.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.Xna.Framework.Graphics; - -namespace StardewModdingAPI.Framework.Content -{ - /// Base implementation for a content helper which encapsulates access and changes to content being read from a data file. - /// The interface value type. - internal class ContentEventBaseHelper : EventArgs, IContentEventData - { - /********* - ** Properties - *********/ - /// Normalises an asset key to match the cache key. - protected readonly Func GetNormalisedPath; - - - /********* - ** Accessors - *********/ - /// The content's locale code, if the content is localised. - public string Locale { get; } - - /// The normalised asset name being read. The format may change between platforms; see to compare with a known path. - public string AssetName { get; } - - /// The content data being read. - public TValue Data { get; protected set; } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The content's locale code, if the content is localised. - /// The normalised asset name being read. - /// The content data being read. - /// Normalises an asset key to match the cache key. - public ContentEventBaseHelper(string locale, string assetName, TValue data, Func getNormalisedPath) - { - this.Locale = locale; - this.AssetName = assetName; - this.Data = data; - this.GetNormalisedPath = getNormalisedPath; - } - - /// Get whether the asset name being loaded matches a given name after normalisation. - /// The expected asset path, relative to the game's content folder and without the .xnb extension or locale suffix (like 'Data\ObjectInformation'). - public bool IsAssetName(string path) - { - path = this.GetNormalisedPath(path); - return this.AssetName.Equals(path, StringComparison.InvariantCultureIgnoreCase); - } - - /// Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game. - /// The new content value. - /// The is null. - /// The 's type is not compatible with the loaded asset's type. - public void ReplaceWith(TValue value) - { - if (value == null) - throw new ArgumentNullException(nameof(value), "Can't set a loaded asset to a null value."); - if (!this.Data.GetType().IsInstanceOfType(value)) - throw new InvalidCastException($"Can't replace loaded asset of type {this.GetFriendlyTypeName(this.Data.GetType())} with value of type {this.GetFriendlyTypeName(value.GetType())}. The new type must be compatible to prevent game errors."); - - this.Data = value; - } - - - /********* - ** Protected methods - *********/ - /// Get a human-readable type name. - /// The type to name. - protected string GetFriendlyTypeName(Type type) - { - // dictionary - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) - { - Type[] genericArgs = type.GetGenericArguments(); - return $"Dictionary<{this.GetFriendlyTypeName(genericArgs[0])}, {this.GetFriendlyTypeName(genericArgs[1])}>"; - } - - // texture - if (type == typeof(Texture2D)) - return type.Name; - - // native type - if (type == typeof(int)) - return "int"; - if (type == typeof(string)) - return "string"; - - // default - return type.FullName; - } - } -} diff --git a/src/StardewModdingAPI/Framework/Content/ContentEventData.cs b/src/StardewModdingAPI/Framework/Content/ContentEventData.cs new file mode 100644 index 00000000..1a1779d4 --- /dev/null +++ b/src/StardewModdingAPI/Framework/Content/ContentEventData.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework.Graphics; + +namespace StardewModdingAPI.Framework.Content +{ + /// Base implementation for a content helper which encapsulates access and changes to content being read from a data file. + /// The interface value type. + internal class ContentEventData : EventArgs, IContentEventData + { + /********* + ** Properties + *********/ + /// Normalises an asset key to match the cache key. + protected readonly Func GetNormalisedPath; + + + /********* + ** Accessors + *********/ + /// The content's locale code, if the content is localised. + public string Locale { get; } + + /// The normalised asset name being read. The format may change between platforms; see to compare with a known path. + public string AssetName { get; } + + /// The content data being read. + public TValue Data { get; protected set; } + + /// The content data type. + public Type DataType { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The content's locale code, if the content is localised. + /// The normalised asset name being read. + /// The content data being read. + /// Normalises an asset key to match the cache key. + public ContentEventData(string locale, string assetName, TValue data, Func getNormalisedPath) + : this(locale, assetName, data, data.GetType(), getNormalisedPath) { } + + /// Construct an instance. + /// The content's locale code, if the content is localised. + /// The normalised asset name being read. + /// The content data being read. + /// The content data type being read. + /// Normalises an asset key to match the cache key. + public ContentEventData(string locale, string assetName, TValue data, Type dataType, Func getNormalisedPath) + { + this.Locale = locale; + this.AssetName = assetName; + this.Data = data; + this.DataType = dataType; + this.GetNormalisedPath = getNormalisedPath; + } + + /// Get whether the asset name being loaded matches a given name after normalisation. + /// The expected asset path, relative to the game's content folder and without the .xnb extension or locale suffix (like 'Data\ObjectInformation'). + public bool IsAssetName(string path) + { + path = this.GetNormalisedPath(path); + return this.AssetName.Equals(path, StringComparison.InvariantCultureIgnoreCase); + } + + /// Replace the entire content value with the given value. This is generally not recommended, since it may break compatibility with other mods or different versions of the game. + /// The new content value. + /// The is null. + /// The 's type is not compatible with the loaded asset's type. + public void ReplaceWith(TValue value) + { + if (value == null) + throw new ArgumentNullException(nameof(value), "Can't set a loaded asset to a null value."); + if (!this.DataType.IsInstanceOfType(value)) + throw new InvalidCastException($"Can't replace loaded asset of type {this.GetFriendlyTypeName(this.DataType)} with value of type {this.GetFriendlyTypeName(value.GetType())}. The new type must be compatible to prevent game errors."); + + this.Data = value; + } + + + /********* + ** Protected methods + *********/ + /// Get a human-readable type name. + /// The type to name. + protected string GetFriendlyTypeName(Type type) + { + // dictionary + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + Type[] genericArgs = type.GetGenericArguments(); + return $"Dictionary<{this.GetFriendlyTypeName(genericArgs[0])}, {this.GetFriendlyTypeName(genericArgs[1])}>"; + } + + // texture + if (type == typeof(Texture2D)) + return type.Name; + + // native type + if (type == typeof(int)) + return "int"; + if (type == typeof(string)) + return "string"; + + // default + return type.FullName; + } + } +} diff --git a/src/StardewModdingAPI/Framework/Content/ContentEventHelper.cs b/src/StardewModdingAPI/Framework/Content/ContentEventHelper.cs index 26292cab..9bf1ea17 100644 --- a/src/StardewModdingAPI/Framework/Content/ContentEventHelper.cs +++ b/src/StardewModdingAPI/Framework/Content/ContentEventHelper.cs @@ -5,7 +5,7 @@ using Microsoft.Xna.Framework.Graphics; namespace StardewModdingAPI.Framework.Content { /// Encapsulates access and changes to content being read from a data file. - internal class ContentEventHelper : ContentEventBaseHelper, IContentEventHelper + internal class ContentEventHelper : ContentEventData, IContentEventHelper { /********* ** Public methods diff --git a/src/StardewModdingAPI/Framework/Content/ContentEventHelperForDictionary.cs b/src/StardewModdingAPI/Framework/Content/ContentEventHelperForDictionary.cs index 8a8a4845..26f059e4 100644 --- a/src/StardewModdingAPI/Framework/Content/ContentEventHelperForDictionary.cs +++ b/src/StardewModdingAPI/Framework/Content/ContentEventHelperForDictionary.cs @@ -5,7 +5,7 @@ using System.Linq; namespace StardewModdingAPI.Framework.Content { /// Encapsulates access and changes to dictionary content being read from a data file. - internal class ContentEventHelperForDictionary : ContentEventBaseHelper>, IContentEventHelperForDictionary + internal class ContentEventHelperForDictionary : ContentEventData>, IContentEventHelperForDictionary { /********* ** Public methods diff --git a/src/StardewModdingAPI/Framework/Content/ContentEventHelperForImage.cs b/src/StardewModdingAPI/Framework/Content/ContentEventHelperForImage.cs index 23760f0f..da30590b 100644 --- a/src/StardewModdingAPI/Framework/Content/ContentEventHelperForImage.cs +++ b/src/StardewModdingAPI/Framework/Content/ContentEventHelperForImage.cs @@ -5,7 +5,7 @@ using Microsoft.Xna.Framework.Graphics; namespace StardewModdingAPI.Framework.Content { /// Encapsulates access and changes to dictionary content being read from a data file. - internal class ContentEventHelperForImage : ContentEventBaseHelper, IContentEventHelperForImage + internal class ContentEventHelperForImage : ContentEventData, IContentEventHelperForImage { /********* ** Public methods diff --git a/src/StardewModdingAPI/IContentEventData.cs b/src/StardewModdingAPI/IContentEventData.cs index a21861d2..7e2d4df1 100644 --- a/src/StardewModdingAPI/IContentEventData.cs +++ b/src/StardewModdingAPI/IContentEventData.cs @@ -18,6 +18,9 @@ namespace StardewModdingAPI /// The content data being read. TValue Data { get; } + /// The content data type. + Type DataType { get; } + /********* ** Public methods diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 445f6f37..ab808948 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -150,7 +150,7 @@ - + @@ -165,10 +165,10 @@ + - -- cgit