using System; using StardewModdingAPI.Toolkit.Utilities; using StardewValley; namespace StardewModdingAPI.Framework.Content { /// An asset name that can be loaded through the content pipeline. internal class AssetName : IAssetName { /********* ** Fields *********/ /// A lowercase version of used for consistent hash codes and equality checks. private readonly string ComparableName; /********* ** Accessors *********/ /// public string Name { get; } /// public string BaseName { get; } /// public string LocaleCode { get; } /// public LocalizedContentManager.LanguageCode? LanguageCode { get; } /********* ** Public methods *********/ /// Construct an instance. /// The base asset name without the locale code. /// The locale code specified in the , if it's a valid code recognized by the game content. /// The language code matching the , if applicable. 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(); } /// Parse a raw asset name into an instance. /// The raw asset name to parse. /// Get the language code for a given locale, if it's valid. /// The is null or empty. public static AssetName Parse(string rawName, Func 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); } /// 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); } /// 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); } /// public bool Equals(IAssetName other) { return other switch { null => false, AssetName otherImpl => this.ComparableName == otherImpl.ComparableName, _ => StringComparer.OrdinalIgnoreCase.Equals(this.Name, other.Name) }; } /// public override int GetHashCode() { return this.ComparableName.GetHashCode(); } /// public override string ToString() { return this.Name; } } }