diff options
| author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2022-04-13 21:07:43 -0400 |
|---|---|---|
| committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2022-04-13 21:07:43 -0400 |
| commit | 4adf8611131a5d86b15f017a42a0366837d14528 (patch) | |
| tree | 879ba8dfc546382c3795e3d13a623b0e17f96469 | |
| parent | 5b24fff4771dd11c627ae20c827599fe37fa89ad (diff) | |
| download | SMAPI-4adf8611131a5d86b15f017a42a0366837d14528.tar.gz SMAPI-4adf8611131a5d86b15f017a42a0366837d14528.tar.bz2 SMAPI-4adf8611131a5d86b15f017a42a0366837d14528.zip | |
enable nullable annotations in the rest of SMAPI core (#837)
39 files changed, 679 insertions, 549 deletions
diff --git a/src/SMAPI.Tests/Utilities/KeybindListTests.cs b/src/SMAPI.Tests/Utilities/KeybindListTests.cs index 060c93ed..c4c086de 100644 --- a/src/SMAPI.Tests/Utilities/KeybindListTests.cs +++ b/src/SMAPI.Tests/Utilities/KeybindListTests.cs @@ -21,12 +21,12 @@ namespace SMAPI.Tests.Utilities public void TryParse_SimpleValue(SButton button) { // act - bool success = KeybindList.TryParse($"{button}", out KeybindList parsed, out string[] errors); + bool success = KeybindList.TryParse($"{button}", out KeybindList? parsed, out string[] errors); // assert Assert.IsTrue(success, "Parsing unexpectedly failed."); Assert.IsNotNull(parsed, "The parsed result should not be null."); - Assert.AreEqual(parsed.ToString(), $"{button}"); + Assert.AreEqual(parsed!.ToString(), $"{button}"); Assert.IsNotNull(errors, message: "The errors should never be null."); Assert.IsEmpty(errors, message: "The input bindings incorrectly reported errors."); } @@ -47,14 +47,14 @@ namespace SMAPI.Tests.Utilities public string TryParse_MultiValues(string? input) { // act - bool success = KeybindList.TryParse(input, out KeybindList parsed, out string[] errors); + bool success = KeybindList.TryParse(input, out KeybindList? parsed, out string[] errors); // assert Assert.IsTrue(success, "Parsing unexpectedly failed."); Assert.IsNotNull(parsed, "The parsed result should not be null."); Assert.IsNotNull(errors, message: "The errors should never be null."); Assert.IsEmpty(errors, message: "The input bindings incorrectly reported errors."); - return parsed.ToString(); + return parsed!.ToString(); } /// <summary>Assert invalid values are rejected.</summary> @@ -67,7 +67,7 @@ namespace SMAPI.Tests.Utilities public void TryParse_InvalidValues(string input, string expectedError) { // act - bool success = KeybindList.TryParse(input, out KeybindList parsed, out string[] errors); + bool success = KeybindList.TryParse(input, out KeybindList? parsed, out string[] errors); // assert Assert.IsFalse(success, "Parsing unexpectedly succeeded."); diff --git a/src/SMAPI/Events/ModMessageReceivedEventArgs.cs b/src/SMAPI/Events/ModMessageReceivedEventArgs.cs index 671bdf38..84a27d18 100644 --- a/src/SMAPI/Events/ModMessageReceivedEventArgs.cs +++ b/src/SMAPI/Events/ModMessageReceivedEventArgs.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Framework.Networking; using StardewModdingAPI.Toolkit.Serialization; @@ -47,8 +45,10 @@ namespace StardewModdingAPI.Events /// <summary>Read the message data into the given model type.</summary> /// <typeparam name="TModel">The message model type.</typeparam> public TModel ReadAs<TModel>() + where TModel : notnull { - return this.Message.Data.ToObject<TModel>(this.JsonHelper.GetSerializer()); + return this.Message.Data.ToObject<TModel>(this.JsonHelper.GetSerializer()) + ?? throw new InvalidOperationException($"Can't read empty mod message data as a {typeof(TModel).FullName} value."); } } } diff --git a/src/SMAPI/Framework/CommandManager.cs b/src/SMAPI/Framework/CommandManager.cs index 80c08f34..d3b9c8ee 100644 --- a/src/SMAPI/Framework/CommandManager.cs +++ b/src/SMAPI/Framework/CommandManager.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using StardewModdingAPI.Framework.Commands; @@ -39,9 +38,9 @@ namespace StardewModdingAPI.Framework /// <exception cref="ArgumentNullException">The <paramref name="name"/> or <paramref name="callback"/> is null or empty.</exception> /// <exception cref="FormatException">The <paramref name="name"/> is not a valid format.</exception> /// <exception cref="ArgumentException">There's already a command with that name.</exception> - public CommandManager Add(IModMetadata mod, string name, string documentation, Action<string, string[]> callback) + public CommandManager Add(IModMetadata? mod, string name, string documentation, Action<string, string[]> callback) { - name = this.GetNormalizedName(name); + name = this.GetNormalizedName(name)!; // null-checked below // validate format if (string.IsNullOrWhiteSpace(name)) @@ -72,10 +71,13 @@ namespace StardewModdingAPI.Framework /// <summary>Get a command by its unique name.</summary> /// <param name="name">The command name.</param> /// <returns>Returns the matching command, or <c>null</c> if not found.</returns> - public Command Get(string name) + public Command? Get(string? name) { - name = this.GetNormalizedName(name); - this.Commands.TryGetValue(name, out Command command); + name = this.GetNormalizedName(name)!; + if (string.IsNullOrWhiteSpace(name)) + return null; + + this.Commands.TryGetValue(name, out Command? command); return command; } @@ -94,7 +96,7 @@ namespace StardewModdingAPI.Framework /// <param name="command">The command which can handle the input.</param> /// <param name="screenId">The screen ID on which to run the command.</param> /// <returns>Returns true if the input was successfully parsed and matched to a command; else false.</returns> - public bool TryParse(string input, out string name, out string[] args, out Command command, out int screenId) + public bool TryParse(string? input, [NotNullWhen(true)] out string? name, [NotNullWhen(true)] out string[]? args, [NotNullWhen(true)] out Command? command, out int screenId) { // ignore if blank if (string.IsNullOrWhiteSpace(input)) @@ -108,7 +110,7 @@ namespace StardewModdingAPI.Framework // parse input args = this.ParseArgs(input); - name = this.GetNormalizedName(args[0]); + name = this.GetNormalizedName(args[0])!; args = args.Skip(1).ToArray(); // get screen ID argument @@ -116,7 +118,7 @@ namespace StardewModdingAPI.Framework for (int i = 0; i < args.Length; i++) { // consume arg & set screen ID - if (this.TryParseScreenId(args[i], out int rawScreenId, out string error)) + if (this.TryParseScreenId(args[i], out int rawScreenId, out string? error)) { args = args.Take(i).Concat(args.Skip(i + 1)).ToArray(); screenId = rawScreenId; @@ -140,15 +142,15 @@ namespace StardewModdingAPI.Framework /// <param name="name">The command name.</param> /// <param name="arguments">The command arguments.</param> /// <returns>Returns whether a matching command was triggered.</returns> - public bool Trigger(string name, string[] arguments) + public bool Trigger(string? name, string[] arguments) { // get normalized name - name = this.GetNormalizedName(name); - if (name == null) + name = this.GetNormalizedName(name)!; + if (string.IsNullOrWhiteSpace(name)) return false; // get command - if (this.Commands.TryGetValue(name, out Command command)) + if (this.Commands.TryGetValue(name, out Command? command)) { command.Callback.Invoke(name, arguments); return true; @@ -191,7 +193,7 @@ namespace StardewModdingAPI.Framework /// <param name="screen">The parsed screen ID, if any.</param> /// <param name="error">The error which indicates an invalid screen ID, if applicable.</param> /// <returns>Returns whether the screen ID was parsed successfully.</returns> - private bool TryParseScreenId(string arg, out int screen, out string error) + private bool TryParseScreenId(string arg, out int screen, out string? error) { screen = -1; error = null; @@ -220,7 +222,7 @@ namespace StardewModdingAPI.Framework /// <summary>Get a normalized command name.</summary> /// <param name="name">The command name.</param> - private string GetNormalizedName(string name) + private string? GetNormalizedName(string? name) { name = name?.Trim().ToLower(); return !string.IsNullOrWhiteSpace(name) diff --git a/src/SMAPI/Framework/Commands/HelpCommand.cs b/src/SMAPI/Framework/Commands/HelpCommand.cs index eb6c74f5..65dc3bce 100644 --- a/src/SMAPI/Framework/Commands/HelpCommand.cs +++ b/src/SMAPI/Framework/Commands/HelpCommand.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Linq; namespace StardewModdingAPI.Framework.Commands @@ -41,7 +39,7 @@ namespace StardewModdingAPI.Framework.Commands { if (args.Any()) { - Command result = this.CommandManager.Get(args[0]); + Command? result = this.CommandManager.Get(args[0]); if (result == null) monitor.Log("There's no command with that name. Type 'help' by itself for more info.", LogLevel.Error); else @@ -63,10 +61,10 @@ namespace StardewModdingAPI.Framework.Commands + "--------------\n" + "The following commands are registered. For more info about a command, type 'help command_name'.\n\n"; - IGrouping<string, string>[] groups = (from command in this.CommandManager.GetAll() orderby command.Mod?.DisplayName, command.Name group command.Name by command.Mod?.DisplayName).ToArray(); + IGrouping<string, string>[] groups = (from command in this.CommandManager.GetAll() orderby command.Mod?.DisplayName, command.Name group command.Name by command.Mod?.DisplayName ?? "SMAPI").ToArray(); foreach (var group in groups) { - string modName = group.Key ?? "SMAPI"; + string modName = group.Key; string[] commandNames = group.ToArray(); message += $"{modName}:\n {string.Join("\n ", commandNames)}\n\n"; } diff --git a/src/SMAPI/Framework/Content/AssetDataForMap.cs b/src/SMAPI/Framework/Content/AssetDataForMap.cs index 93148277..133dcc6c 100644 --- a/src/SMAPI/Framework/Content/AssetDataForMap.cs +++ b/src/SMAPI/Framework/Content/AssetDataForMap.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -35,7 +33,7 @@ namespace StardewModdingAPI.Framework.Content /// <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> /// <param name="reflection">Simplifies access to private code.</param> - public AssetDataForMap(string locale, IAssetName assetName, Map data, Func<string, string> getNormalizedPath, Action<Map> onDataReplaced, Reflector reflection) + public AssetDataForMap(string? locale, IAssetName assetName, Map data, Func<string, string> getNormalizedPath, Action<Map> onDataReplaced, Reflector reflection) : base(locale, assetName, data, getNormalizedPath, onDataReplaced) { this.Reflection = reflection; @@ -126,8 +124,7 @@ namespace StardewModdingAPI.Framework.Content foreach (Layer sourceLayer in source.Layers) { // get layer - Layer targetLayer = sourceToTargetLayers[sourceLayer]; - if (targetLayer == null) + if (!sourceToTargetLayers.TryGetValue(sourceLayer, out Layer? targetLayer)) { target.AddLayer(targetLayer = new Layer(sourceLayer.Id, target, target.Layers[0].LayerSize, Layer.m_tileSize)); sourceToTargetLayers[sourceLayer] = target.GetLayer(sourceLayer.Id); @@ -137,11 +134,13 @@ namespace StardewModdingAPI.Framework.Content targetLayer.Properties.CopyFrom(sourceLayer.Properties); // create new tile - Tile sourceTile = sourceLayer.Tiles[sourcePos.X, sourcePos.Y]; - Tile newTile = sourceTile != null - ? this.CreateTile(sourceTile, targetLayer, tilesheetMap[sourceTile.TileSheet]) - : null; - newTile?.Properties.CopyFrom(sourceTile.Properties); + Tile? sourceTile = sourceLayer.Tiles[sourcePos.X, sourcePos.Y]; + Tile? newTile = null; + if (sourceTile != null) + { + newTile = this.CreateTile(sourceTile, targetLayer, tilesheetMap[sourceTile.TileSheet]); + newTile?.Properties.CopyFrom(sourceTile.Properties); + } // replace tile if (newTile != null || replaceByLayer || replaceAll) @@ -195,7 +194,7 @@ namespace StardewModdingAPI.Framework.Content /// <param name="sourceTile">The source tile to copy.</param> /// <param name="targetLayer">The target layer.</param> /// <param name="targetSheet">The target tilesheet.</param> - private Tile CreateTile(Tile sourceTile, Layer targetLayer, TileSheet targetSheet) + private Tile? CreateTile(Tile sourceTile, Layer targetLayer, TileSheet targetSheet) { switch (sourceTile) { @@ -220,7 +219,7 @@ namespace StardewModdingAPI.Framework.Content } /// <summary>Normalize a map tilesheet path for comparison. This value should *not* be used as the actual tilesheet path.</summary> /// <param name="path">The path to normalize.</param> - private string NormalizeTilesheetPathForComparison(string path) + private string NormalizeTilesheetPathForComparison(string? path) { if (string.IsNullOrWhiteSpace(path)) return string.Empty; diff --git a/src/SMAPI/Framework/Content/AssetName.cs b/src/SMAPI/Framework/Content/AssetName.cs index 4d583d82..4c691b9a 100644 --- a/src/SMAPI/Framework/Content/AssetName.cs +++ b/src/SMAPI/Framework/Content/AssetName.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Toolkit.Utilities; using StardewValley; @@ -26,7 +24,7 @@ namespace StardewModdingAPI.Framework.Content public string BaseName { get; } /// <inheritdoc /> - public string LocaleCode { get; } + public string? LocaleCode { get; } /// <inheritdoc /> public LocalizedContentManager.LanguageCode? LanguageCode { get; } @@ -39,7 +37,7 @@ namespace StardewModdingAPI.Framework.Content /// <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) + public AssetName(string baseName, string? localeCode, LocalizedContentManager.LanguageCode? languageCode) { // validate if (string.IsNullOrWhiteSpace(baseName)) @@ -69,7 +67,7 @@ namespace StardewModdingAPI.Framework.Content throw new ArgumentException("The asset name can't be null or empty.", nameof(rawName)); string baseName = rawName; - string localeCode = null; + string? localeCode = null; LocalizedContentManager.LanguageCode? languageCode = null; int lastPeriodIndex = rawName.LastIndexOf('.'); @@ -90,7 +88,7 @@ namespace StardewModdingAPI.Framework.Content } /// <inheritdoc /> - public bool IsEquivalentTo(string assetName, bool useBaseName = false) + public bool IsEquivalentTo(string? assetName, bool useBaseName = false) { // empty asset key is never equivalent if (string.IsNullOrWhiteSpace(assetName)) @@ -103,7 +101,7 @@ namespace StardewModdingAPI.Framework.Content } /// <inheritdoc /> - public bool IsEquivalentTo(IAssetName assetName, bool useBaseName = false) + public bool IsEquivalentTo(IAssetName? assetName, bool useBaseName = false) { if (useBaseName) return this.BaseName.Equals(assetName?.BaseName, StringComparison.OrdinalIgnoreCase); @@ -115,7 +113,7 @@ namespace StardewModdingAPI.Framework.Content } /// <inheritdoc /> - public bool StartsWith(string prefix, bool allowPartialWord = true, bool allowSubfolder = true) + public bool StartsWith(string? prefix, bool allowPartialWord = true, bool allowSubfolder = true) { // asset keys never start with null if (prefix is null) @@ -157,8 +155,11 @@ namespace StardewModdingAPI.Framework.Content /// <inheritdoc /> - public bool IsDirectlyUnderPath(string assetFolder) + public bool IsDirectlyUnderPath(string? assetFolder) { + if (assetFolder is null) + return false; + return this.StartsWith(assetFolder + "/", allowPartialWord: false, allowSubfolder: false); } @@ -171,7 +172,7 @@ namespace StardewModdingAPI.Framework.Content } /// <inheritdoc /> - public bool Equals(IAssetName other) + public bool Equals(IAssetName? other) { return other switch { diff --git a/src/SMAPI/Framework/Content/ContentCache.cs b/src/SMAPI/Framework/Content/ContentCache.cs index 4e620d28..d8862eb3 100644 --- a/src/SMAPI/Framework/Content/ContentCache.cs +++ b/src/SMAPI/Framework/Content/ContentCache.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Linq; using StardewModdingAPI.Framework.Reflection; @@ -46,7 +45,8 @@ namespace StardewModdingAPI.Framework.Content /// <param name="reflection">Simplifies access to private game code.</param> public ContentCache(LocalizedContentManager contentManager, Reflector reflection) { - this.Cache = reflection.GetField<Dictionary<string, object>>(contentManager, "loadedAssets").GetValue(); + this.Cache = reflection.GetField<Dictionary<string, object>>(contentManager, "loadedAssets").GetValue() + ?? throw new InvalidOperationException("Can't initialize content cache: required field 'loadedAssets' is missing."); } /**** @@ -66,7 +66,8 @@ namespace StardewModdingAPI.Framework.Content /// <summary>Normalize path separators in an asset name.</summary> /// <param name="path">The file path to normalize.</param> [Pure] - public string NormalizePathSeparators(string path) + [return: NotNullIfNotNull("path")] + public string? NormalizePathSeparators(string? path) { return PathUtilities.NormalizeAssetName(path); } @@ -93,7 +94,7 @@ namespace StardewModdingAPI.Framework.Content public bool Remove(string key, bool dispose) { // get entry - if (!this.Cache.TryGetValue(key, out object value)) + if (!this.Cache.TryGetValue(key, out object? value)) return false; // dispose & remove entry diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index f1ccab48..5ae5313d 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -99,11 +97,13 @@ namespace StardewModdingAPI.Framework.ContentManagers this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations; // get asset data - this.BaseDisposableReferences = reflection.GetField<List<IDisposable>>(this, "disposableAssets").GetValue(); + this.BaseDisposableReferences = reflection.GetField<List<IDisposable>>(this, "disposableAssets").GetValue() + ?? throw new InvalidOperationException("Can't initialize content manager: the required 'disposableAssets' field wasn't found."); } /// <inheritdoc /> public virtual bool DoesAssetExist<T>(IAssetName assetName) + where T : notnull { return this.Cache.ContainsKey(assetName.Name); } @@ -131,6 +131,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <inheritdoc /> public T LoadLocalized<T>(IAssetName assetName, LanguageCode language, bool useCache) + where T : notnull { // ignore locale in English (or if disabled) if (!this.TryLocalizeKeys || language == LocalizedContentManager.LanguageCode.en) @@ -172,11 +173,12 @@ namespace StardewModdingAPI.Framework.ContentManagers } /// <inheritdoc /> - public abstract T LoadExact<T>(IAssetName assetName, bool useCache); + public abstract T LoadExact<T>(IAssetName assetName, bool useCache) + where T : notnull; /// <inheritdoc /> [SuppressMessage("ReSharper", "ParameterOnlyUsedForPreconditionCheck.Local", Justification = "Parameter is only used for assertion checks by design.")] - public string AssertAndNormalizeAssetName(string assetName) + public string AssertAndNormalizeAssetName(string? assetName) { // NOTE: the game checks for ContentLoadException to handle invalid keys, so avoid // throwing other types like ArgumentException here. @@ -253,7 +255,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // dispose uncached assets foreach (WeakReference<IDisposable> reference in this.Disposables) { - if (reference.TryGetTarget(out IDisposable disposable)) + if (reference.TryGetTarget(out IDisposable? disposable)) { try { @@ -285,7 +287,8 @@ namespace StardewModdingAPI.Framework.ContentManagers *********/ /// <summary>Apply initial normalization to a raw asset name before it's parsed.</summary> /// <param name="assetName">The asset name to normalize.</param> - private string PrenormalizeRawAssetName(string assetName) + [return: NotNullIfNotNull("assetName")] + private string? PrenormalizeRawAssetName(string? assetName) { // trim assetName = assetName?.Trim(); @@ -301,7 +304,8 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <summary>Normalize path separators in a file path. For asset keys, see <see cref="AssertAndNormalizeAssetName"/> instead.</summary> /// <param name="path">The file path to normalize.</param> [Pure] - protected string NormalizePathSeparators(string path) + [return: NotNullIfNotNull("path")] + protected string? NormalizePathSeparators(string? path) { return this.Cache.NormalizePathSeparators(path); } @@ -323,6 +327,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <param name="value">The asset value.</param> /// <param name="useCache">Whether to save the asset to the asset cache.</param> protected virtual void TrackAsset<T>(IAssetName assetName, T value, bool useCache) + where T : notnull { // track asset key if (value is Texture2D texture) diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 8051c296..f0f4bce9 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Globalization; using System.IO; @@ -92,7 +90,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // resolve managed asset key { - if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string contentManagerID, out IAssetName relativePath)) + if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string? contentManagerID, out IAssetName? relativePath)) { if (contentManagerID != this.Name) throw this.GetLoadError(assetName, "can't load a different mod's managed asset key through this mod content manager."); @@ -173,7 +171,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <param name="file">The file to load.</param> private T LoadDataFile<T>(IAssetName assetName, FileInfo file) { - if (!this.JsonHelper.ReadJsonFileIfExists(file.FullName, out T asset)) + if (!this.JsonHelper.ReadJsonFileIfExists(file.FullName, out T? asset)) throw this.GetLoadError(assetName, "the JSON file is invalid."); // should never happen since we check for file existence before calling this method return asset; @@ -249,7 +247,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <param name="assetName">The asset name that failed to load.</param> /// <param name="reasonPhrase">The reason the file couldn't be loaded.</param> /// <param name="exception">The underlying exception, if applicable.</param> - private SContentLoadException GetLoadError(IAssetName assetName, string reasonPhrase, Exception exception = null) + private SContentLoadException GetLoadError(IAssetName assetName, string reasonPhrase, Exception? exception = null) { return new($"Failed loading asset '{assetName}' from {this.Name}: {reasonPhrase}", exception); } @@ -338,13 +336,16 @@ namespace StardewModdingAPI.Framework.ContentManagers // load best match try { - if (!this.TryGetTilesheetAssetName(relativeMapFolder, imageSource, out IAssetName assetName, out string error)) + if (!this.TryGetTilesheetAsset |
