summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework/Content
diff options
context:
space:
mode:
Diffstat (limited to 'src/SMAPI/Framework/Content')
-rw-r--r--src/SMAPI/Framework/Content/AssetData.cs4
-rw-r--r--src/SMAPI/Framework/Content/AssetDataForDictionary.cs4
-rw-r--r--src/SMAPI/Framework/Content/AssetDataForImage.cs30
-rw-r--r--src/SMAPI/Framework/Content/AssetDataForMap.cs4
-rw-r--r--src/SMAPI/Framework/Content/AssetDataForObject.cs12
-rw-r--r--src/SMAPI/Framework/Content/AssetInfo.cs16
-rw-r--r--src/SMAPI/Framework/Content/AssetInterceptorChange.cs4
-rw-r--r--src/SMAPI/Framework/Content/AssetName.cs173
8 files changed, 212 insertions, 35 deletions
diff --git a/src/SMAPI/Framework/Content/AssetData.cs b/src/SMAPI/Framework/Content/AssetData.cs
index 5c90d83b..05be8a3b 100644
--- a/src/SMAPI/Framework/Content/AssetData.cs
+++ b/src/SMAPI/Framework/Content/AssetData.cs
@@ -25,11 +25,11 @@ namespace StardewModdingAPI.Framework.Content
*********/
/// <summary>Construct an instance.</summary>
/// <param name="locale">The content's locale code, if the content is localized.</param>
- /// <param name="assetName">The normalized asset name being read.</param>
+ /// <param name="assetName">The asset name being read.</param>
/// <param name="data">The content data being read.</param>
/// <param name="getNormalizedPath">Normalizes an asset key to match the cache key.</param>
/// <param name="onDataReplaced">A callback to invoke when the data is replaced (if any).</param>
- public AssetData(string locale, string assetName, TValue data, Func<string, string> getNormalizedPath, Action<TValue> onDataReplaced)
+ public AssetData(string locale, IAssetName assetName, TValue data, Func<string, string> getNormalizedPath, Action<TValue> onDataReplaced)
: base(locale, assetName, data.GetType(), getNormalizedPath)
{
this.Data = data;
diff --git a/src/SMAPI/Framework/Content/AssetDataForDictionary.cs b/src/SMAPI/Framework/Content/AssetDataForDictionary.cs
index 26cbff5a..735b651c 100644
--- a/src/SMAPI/Framework/Content/AssetDataForDictionary.cs
+++ b/src/SMAPI/Framework/Content/AssetDataForDictionary.cs
@@ -11,11 +11,11 @@ namespace StardewModdingAPI.Framework.Content
*********/
/// <summary>Construct an instance.</summary>
/// <param name="locale">The content's locale code, if the content is localized.</param>
- /// <param name="assetName">The normalized asset name being read.</param>
+ /// <param name="assetName">The asset name being read.</param>
/// <param name="data">The content data being read.</param>
/// <param name="getNormalizedPath">Normalizes an asset key to match the cache key.</param>
/// <param name="onDataReplaced">A callback to invoke when the data is replaced (if any).</param>
- public AssetDataForDictionary(string locale, string assetName, IDictionary<TKey, TValue> data, Func<string, string> getNormalizedPath, Action<IDictionary<TKey, TValue>> onDataReplaced)
+ public AssetDataForDictionary(string locale, IAssetName assetName, IDictionary<TKey, TValue> data, Func<string, string> getNormalizedPath, Action<IDictionary<TKey, TValue>> onDataReplaced)
: base(locale, assetName, data, getNormalizedPath, onDataReplaced) { }
}
}
diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs
index 529fb93a..b0f1b5c7 100644
--- a/src/SMAPI/Framework/Content/AssetDataForImage.cs
+++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs
@@ -21,11 +21,11 @@ namespace StardewModdingAPI.Framework.Content
*********/
/// <summary>Construct an instance.</summary>
/// <param name="locale">The content's locale code, if the content is localized.</param>
- /// <param name="assetName">The normalized asset name being read.</param>
+ /// <param name="assetName">The asset name being read.</param>
/// <param name="data">The content data being read.</param>
/// <param name="getNormalizedPath">Normalizes an asset key to match the cache key.</param>
/// <param name="onDataReplaced">A callback to invoke when the data is replaced (if any).</param>
- public AssetDataForImage(string locale, string assetName, Texture2D data, Func<string, string> getNormalizedPath, Action<Texture2D> onDataReplaced)
+ public AssetDataForImage(string locale, IAssetName assetName, Texture2D data, Func<string, string> getNormalizedPath, Action<Texture2D> onDataReplaced)
: base(locale, assetName, data, getNormalizedPath, onDataReplaced) { }
/// <inheritdoc />
@@ -41,39 +41,40 @@ namespace StardewModdingAPI.Framework.Content
targetArea ??= new Rectangle(0, 0, Math.Min(sourceArea.Value.Width, target.Width), Math.Min(sourceArea.Value.Height, target.Height));
// validate
- if (sourceArea.Value.X < 0 || sourceArea.Value.Y < 0 || sourceArea.Value.Right > source.Width || sourceArea.Value.Bottom > source.Height)
+ if (!source.Bounds.Contains(sourceArea.Value))
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)
+ if (!target.Bounds.Contains(targetArea.Value))
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)
+ if (sourceArea.Value.Size != targetArea.Value.Size)
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];
+ Color[] sourceData = GC.AllocateUninitializedArray<Color>(pixelCount);
source.GetData(0, sourceArea, sourceData, 0, pixelCount);
// merge data in overlay mode
if (patchMode == PatchMode.Overlay)
{
// get target data
- Color[] targetData = new Color[pixelCount];
+ Color[] targetData = GC.AllocateUninitializedArray<Color>(pixelCount);
target.GetData(0, targetArea, targetData, 0, pixelCount);
// merge pixels
- 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 above = sourceData[i];
Color below = targetData[i];
// shortcut transparency
- if (above.A < AssetDataForImage.MinOpacity)
+ if (above.A < MinOpacity)
+ {
+ sourceData[i] = below;
continue;
- if (below.A < AssetDataForImage.MinOpacity)
+ }
+ if (below.A < MinOpacity)
{
- newData[i] = above;
+ sourceData[i] = above;
continue;
}
@@ -84,14 +85,13 @@ namespace StardewModdingAPI.Framework.Content
// Note: don't use named arguments here since they're different between
// Linux/macOS and Windows.
float alphaBelow = 1 - (above.A / 255f);
- newData[i] = new Color(
+ sourceData[i] = new Color(
(int)(above.R + (below.R * alphaBelow)), // r
(int)(above.G + (below.G * alphaBelow)), // g
(int)(above.B + (below.B * alphaBelow)), // b
Math.Max(above.A, below.A) // a
);
}
- sourceData = newData;
}
// patch target texture
@@ -105,7 +105,7 @@ namespace StardewModdingAPI.Framework.Content
return false;
Texture2D original = this.Data;
- Texture2D texture = new Texture2D(Game1.graphics.GraphicsDevice, Math.Max(original.Width, minWidth), Math.Max(original.Height, minHeight));
+ Texture2D texture = new(Game1.graphics.GraphicsDevice, Math.Max(original.Width, minWidth), Math.Max(original.Height, minHeight));
this.ReplaceWith(texture);
this.PatchImage(original);
return true;
diff --git a/src/SMAPI/Framework/Content/AssetDataForMap.cs b/src/SMAPI/Framework/Content/AssetDataForMap.cs
index 0a5fa7e7..26e4986e 100644
--- a/src/SMAPI/Framework/Content/AssetDataForMap.cs
+++ b/src/SMAPI/Framework/Content/AssetDataForMap.cs
@@ -18,11 +18,11 @@ namespace StardewModdingAPI.Framework.Content
*********/
/// <summary>Construct an instance.</summary>
/// <param name="locale">The content's locale code, if the content is localized.</param>
- /// <param name="assetName">The normalized asset name being read.</param>
+ /// <param name="assetName">The asset name being read.</param>
/// <param name="data">The content data being read.</param>
/// <param name="getNormalizedPath">Normalizes an asset key to match the cache key.</param>
/// <param name="onDataReplaced">A callback to invoke when the data is replaced (if any).</param>
- public AssetDataForMap(string locale, string assetName, Map data, Func<string, string> getNormalizedPath, Action<Map> onDataReplaced)
+ public AssetDataForMap(string locale, IAssetName assetName, Map data, Func<string, string> getNormalizedPath, Action<Map> onDataReplaced)
: base(locale, assetName, data, getNormalizedPath, onDataReplaced) { }
/// <inheritdoc />
diff --git a/src/SMAPI/Framework/Content/AssetDataForObject.cs b/src/SMAPI/Framework/Content/AssetDataForObject.cs
index b7e8dfeb..d91873ae 100644
--- a/src/SMAPI/Framework/Content/AssetDataForObject.cs
+++ b/src/SMAPI/Framework/Content/AssetDataForObject.cs
@@ -13,10 +13,10 @@ namespace StardewModdingAPI.Framework.Content
*********/
/// <summary>Construct an instance.</summary>
/// <param name="locale">The content's locale code, if the content is localized.</param>
- /// <param name="assetName">The normalized asset name being read.</param>
+ /// <param name="assetName">The asset name being read.</param>
/// <param name="data">The content data being read.</param>
/// <param name="getNormalizedPath">Normalizes an asset key to match the cache key.</param>
- public AssetDataForObject(string locale, string assetName, object data, Func<string, string> getNormalizedPath)
+ public AssetDataForObject(string locale, IAssetName assetName, object data, Func<string, string> getNormalizedPath)
: base(locale, assetName, data, getNormalizedPath, onDataReplaced: null) { }
/// <summary>Construct an instance.</summary>
@@ -24,24 +24,24 @@ namespace StardewModdingAPI.Framework.Content
/// <param name="data">The content data being read.</param>
/// <param name="getNormalizedPath">Normalizes an asset key to match the cache key.</param>
public AssetDataForObject(IAssetInfo info, object data, Func<string, string> getNormalizedPath)
- : this(info.Locale, info.AssetName, data, getNormalizedPath) { }
+ : this(info.Locale, info.Name, data, getNormalizedPath) { }
/// <inheritdoc />
public IAssetDataForDictionary<TKey, TValue> AsDictionary<TKey, TValue>()
{
- return new AssetDataForDictionary<TKey, TValue>(this.Locale, this.AssetName, this.GetData<IDictionary<TKey, TValue>>(), this.GetNormalizedPath, this.ReplaceWith);
+ return new AssetDataForDictionary<TKey, TValue>(this.Locale, this.Name, this.GetData<IDictionary<TKey, TValue>>(), this.GetNormalizedPath, this.ReplaceWith);
}
/// <inheritdoc />
public IAssetDataForImage AsImage()
{
- return new AssetDataForImage(this.Locale, this.AssetName, this.GetData<Texture2D>(), this.GetNormalizedPath, this.ReplaceWith);
+ return new AssetDataForImage(this.Locale, this.Name, this.GetData<Texture2D>(), this.GetNormalizedPath, this.ReplaceWith);
}
/// <inheritdoc />
public IAssetDataForMap AsMap()
{
- return new AssetDataForMap(this.Locale, this.AssetName, this.GetData<Map>(), this.GetNormalizedPath, this.ReplaceWith);
+ return new AssetDataForMap(this.Locale, this.Name, this.GetData<Map>(), this.GetNormalizedPath, this.ReplaceWith);
}
/// <inheritdoc />
diff --git a/src/SMAPI/Framework/Content/AssetInfo.cs b/src/SMAPI/Framework/Content/AssetInfo.cs
index d8106439..6a5b4f31 100644
--- a/src/SMAPI/Framework/Content/AssetInfo.cs
+++ b/src/SMAPI/Framework/Content/AssetInfo.cs
@@ -20,7 +20,11 @@ namespace StardewModdingAPI.Framework.Content
public string Locale { get; }
/// <inheritdoc />
- public string AssetName { get; }
+ public IAssetName Name { get; }
+
+ /// <inheritdoc />
+ [Obsolete($"Use {nameof(Name)} instead.")]
+ public string AssetName => this.Name.Name;
/// <inheritdoc />
public Type DataType { get; }
@@ -31,22 +35,22 @@ namespace StardewModdingAPI.Framework.Content
*********/
/// <summary>Construct an instance.</summary>
/// <param name="locale">The content's locale code, if the content is localized.</param>
- /// <param name="assetName">The normalized asset name being read.</param>
+ /// <param name="assetName">The asset name being read.</param>
/// <param name="type">The content type being read.</param>
/// <param name="getNormalizedPath">Normalizes an asset key to match the cache key.</param>
- public AssetInfo(string locale, string assetName, Type type, Func<string, string> getNormalizedPath)
+ public AssetInfo(string locale, IAssetName assetName, Type type, Func<string, string> getNormalizedPath)
{
this.Locale = locale;
- this.AssetName = assetName;
+ this.Name = assetName;
this.DataType = type;
this.GetNormalizedPath = getNormalizedPath;
}
/// <inheritdoc />
+ [Obsolete($"Use {nameof(Name)}.{nameof(IAssetName.IsEquivalentTo)} instead.")]
public bool AssetNameEquals(string path)
{
- path = this.GetNormalizedPath(path);
- return this.AssetName.Equals(path, StringComparison.OrdinalIgnoreCase);
+ return this.Name.IsEquivalentTo(path);
}
diff --git a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs
index 10488b84..981eed40 100644
--- a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs
+++ b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs
@@ -70,7 +70,7 @@ namespace StardewModdingAPI.Framework.Content
}
catch (Exception ex)
{
- this.Mod.LogAsMod($"Mod failed when checking whether it could edit asset '{asset.AssetName}'. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
+ this.Mod.LogAsMod($"Mod failed when checking whether it could edit asset '{asset.Name}'. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
}
}
@@ -84,7 +84,7 @@ namespace StardewModdingAPI.Framework.Content
}
catch (Exception ex)
{
- this.Mod.LogAsMod($"Mod failed when checking whether it could load asset '{asset.AssetName}'. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
+ this.Mod.LogAsMod($"Mod failed when checking whether it could load asset '{asset.Name}'. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
}
}
diff --git a/src/SMAPI/Framework/Content/AssetName.cs b/src/SMAPI/Framework/Content/AssetName.cs
new file mode 100644
index 00000000..992647f8
--- /dev/null
+++ b/src/SMAPI/Framework/Content/AssetName.cs
@@ -0,0 +1,173 @@
+using System;
+using StardewModdingAPI.Toolkit.Utilities;
+using StardewValley;
+
+namespace StardewModdingAPI.Framework.Content
+{
+ /// <summary>An asset name that can be loaded through the content pipeline.</summary>
+ internal class AssetName : IAssetName
+ {
+ /*********
+ ** Fields
+ *********/
+ /// <summary>A lowercase version of <see cref="Name"/> used for consistent hash codes and equality checks.</summary>
+ private readonly string ComparableName;
+
+
+ /*********
+ ** Accessors
+ *********/
+ /// <inheritdoc />
+ public string Name { get; }
+
+ /// <inheritdoc />
+ public string BaseName { get; }
+
+ /// <inheritdoc />
+ public string LocaleCode { get; }
+
+ /// <inheritdoc />
+ public LocalizedContentManager.LanguageCode? LanguageCode { get; }
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Construct an instance.</summary>
+ /// <param name="baseName">The base asset name without the locale code.</param>
+ /// <param name="localeCode">The locale code specified in the <see cref="Name"/>, if it's a valid code recognized by the game content.</param>
+ /// <param name="languageCode">The language code matching the <see cref="LocaleCode"/>, if applicable.</param>
+ public AssetName(string baseName, string localeCode, LocalizedContentManager.LanguageCode? languageCode)
+ {
+ // validate
+ if (string.IsNullOrWhiteSpace(baseName))
+ throw new ArgumentException("The asset name can't be null or empty.", nameof(baseName));
+ if (string.IsNullOrWhiteSpace(localeCode))
+ localeCode = null;
+
+ // set base values
+ this.BaseName = PathUtilities.NormalizeAssetName(baseName);
+ this.LocaleCode = localeCode;
+ this.LanguageCode = languageCode;
+
+ // set derived values
+ this.Name = localeCode != null
+ ? string.Concat(this.BaseName, '.', this.LocaleCode)
+ : this.BaseName;
+ this.ComparableName = this.Name.ToLowerInvariant();
+ }
+
+ /// <summary>Parse a raw asset name into an instance.</summary>
+ /// <param name="rawName">The raw asset name to parse.</param>
+ /// <param name="parseLocale">Get the language code for a given locale, if it's valid.</param>
+ /// <exception cref="ArgumentException">The <paramref name="rawName"/> is null or empty.</exception>
+ public static AssetName Parse(string rawName, Func<string, LocalizedContentManager.LanguageCode?> parseLocale)
+ {
+ if (string.IsNullOrWhiteSpace(rawName))
+ throw new ArgumentException("The asset name can't be null or empty.", nameof(rawName));
+
+ string baseName = rawName;
+ string localeCode = null;
+ LocalizedContentManager.LanguageCode? languageCode = null;
+
+ int lastPeriodIndex = rawName.LastIndexOf('.');
+ if (lastPeriodIndex > 0 && rawName.Length > lastPeriodIndex + 1)
+ {
+ string possibleLocaleCode = rawName[(lastPeriodIndex + 1)..];
+ LocalizedContentManager.LanguageCode? possibleLanguageCode = parseLocale(possibleLocaleCode);
+
+ if (possibleLanguageCode != null)
+ {
+ baseName = rawName[..lastPeriodIndex];
+ localeCode = possibleLocaleCode;
+ languageCode = possibleLanguageCode;
+ }
+ }
+
+ return new AssetName(baseName, localeCode, languageCode);
+ }
+
+ /// <inheritdoc />
+ public bool IsEquivalentTo(string assetName, bool useBaseName = false)
+ {
+ // empty asset key is never equivalent
+ if (string.IsNullOrWhiteSpace(assetName))
+ return false;
+
+ assetName = PathUtilities.NormalizeAssetName(assetName);
+
+ string compareTo = useBaseName ? this.BaseName : this.Name;
+ return compareTo.Equals(assetName, StringComparison.OrdinalIgnoreCase);
+ }
+
+ /// <inheritdoc />
+ public bool StartsWith(string prefix, bool allowPartialWord = true, bool allowSubfolder = true)
+ {
+ // asset keys never start with null
+ if (prefix is null)
+ return false;
+
+ // asset keys can't have a leading slash, but NormalizeAssetName will trim them
+ {
+ string trimmed = prefix.TrimStart();
+ if (trimmed.StartsWith('/') || trimmed.StartsWith('\\'))
+ return false;
+ }
+
+ // normalize prefix
+ {
+ string normalized = PathUtilities.NormalizeAssetName(prefix);
+
+ string trimmed = prefix.TrimEnd();
+ if (trimmed.EndsWith('/') || trimmed.EndsWith('\\'))
+ normalized += PathUtilities.PreferredAssetSeparator;
+
+ prefix = normalized;
+ }
+
+ // compare
+ return
+ this.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
+ && (
+ allowPartialWord
+ || this.Name.Length == prefix.Length
+ || !char.IsLetterOrDigit(prefix[^1]) // last character in suffix is word separator
+ || !char.IsLetterOrDigit(this.Name[prefix.Length]) // or first character after it is
+ )
+ && (
+ allowSubfolder
+ || this.Name.Length == prefix.Length
+ || !this.Name[prefix.Length..].Contains(PathUtilities.PreferredAssetSeparator)
+ );
+ }
+
+
+ public bool IsDirectlyUnderPath(string assetFolder)
+ {
+ return this.StartsWith(assetFolder + "/", allowPartialWord: false, allowSubfolder: false);
+ }
+
+ /// <inheritdoc />
+ public bool Equals(IAssetName other)
+ {
+ return other switch
+ {
+ null => false,
+ AssetName otherImpl => this.ComparableName == otherImpl.ComparableName,
+ _ => StringComparer.OrdinalIgnoreCase.Equals(this.Name, other.Name)
+ };
+ }
+
+ /// <inheritdoc />
+ public override int GetHashCode()
+ {
+ return this.ComparableName.GetHashCode();
+ }
+
+ /// <inheritdoc />
+ public override string ToString()
+ {
+ return this.Name;
+ }
+ }
+}