diff options
Diffstat (limited to 'src')
487 files changed, 2171 insertions, 860 deletions
diff --git a/src/SMAPI.Installer/Framework/InstallerContext.cs b/src/SMAPI.Installer/Framework/InstallerContext.cs index bb973230..23d5b17c 100644 --- a/src/SMAPI.Installer/Framework/InstallerContext.cs +++ b/src/SMAPI.Installer/Framework/InstallerContext.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.IO; using StardewModdingAPI.Toolkit; using StardewModdingAPI.Toolkit.Framework.GameScanning; @@ -12,7 +14,7 @@ namespace StardewModdingAPI.Installer.Framework ** Fields *********/ /// <summary>The underlying toolkit game scanner.</summary> - private readonly GameScanner GameScanner = new GameScanner(); + private readonly GameScanner GameScanner = new(); /********* diff --git a/src/SMAPI.Installer/Framework/InstallerPaths.cs b/src/SMAPI.Installer/Framework/InstallerPaths.cs index 0976eceb..fd9d1be6 100644 --- a/src/SMAPI.Installer/Framework/InstallerPaths.cs +++ b/src/SMAPI.Installer/Framework/InstallerPaths.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.IO; using StardewModdingAPI.Toolkit.Framework; diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index b3bba883..b07c0461 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics; @@ -126,7 +128,7 @@ namespace StardewModdingApi.Installer /**** ** Get basic info & set window title ****/ - ModToolkit toolkit = new ModToolkit(); + ModToolkit toolkit = new(); var context = new InstallerContext(); Console.Title = $"SMAPI {context.GetInstallerVersion()} installer on {context.Platform} {context.PlatformName}"; Console.WriteLine(); @@ -246,7 +248,7 @@ namespace StardewModdingApi.Installer } // get folders - DirectoryInfo bundleDir = new DirectoryInfo(this.BundlePath); + DirectoryInfo bundleDir = new(this.BundlePath); paths = new InstallerPaths(bundleDir, installDir); } @@ -354,8 +356,8 @@ namespace StardewModdingApi.Installer // move global save data folder (changed in 3.2) { string dataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley"); - DirectoryInfo oldDir = new DirectoryInfo(Path.Combine(dataPath, "Saves", ".smapi")); - DirectoryInfo newDir = new DirectoryInfo(Path.Combine(dataPath, ".smapi")); + DirectoryInfo oldDir = new(Path.Combine(dataPath, "Saves", ".smapi")); + DirectoryInfo newDir = new(Path.Combine(dataPath, ".smapi")); if (oldDir.Exists) { @@ -428,7 +430,7 @@ namespace StardewModdingApi.Installer } // add or replace bundled mods - DirectoryInfo bundledModsDir = new DirectoryInfo(Path.Combine(paths.BundlePath, "Mods")); + DirectoryInfo bundledModsDir = new(Path.Combine(paths.BundlePath, "Mods")); if (bundledModsDir.Exists && bundledModsDir.EnumerateDirectories().Any()) { this.PrintDebug("Adding bundled mods..."); @@ -450,7 +452,7 @@ namespace StardewModdingApi.Installer // find target folder ModFolder targetMod = targetMods.FirstOrDefault(p => p.Manifest?.UniqueID?.Equals(sourceMod.Manifest.UniqueID, StringComparison.OrdinalIgnoreCase) == true); - DirectoryInfo defaultTargetFolder = new DirectoryInfo(Path.Combine(paths.ModsPath, sourceMod.Directory.Name)); + DirectoryInfo defaultTargetFolder = new(Path.Combine(paths.ModsPath, sourceMod.Directory.Name)); DirectoryInfo targetFolder = targetMod?.Directory ?? defaultTargetFolder; this.PrintDebug(targetFolder.FullName == defaultTargetFolder.FullName ? $" adding {sourceMod.Manifest.Name}..." @@ -562,7 +564,7 @@ namespace StardewModdingApi.Installer { try { - FileUtilities.ForceDelete(Directory.Exists(path) ? new DirectoryInfo(path) : (FileSystemInfo)new FileInfo(path)); + FileUtilities.ForceDelete(Directory.Exists(path) ? new DirectoryInfo(path) : new FileInfo(path)); break; } catch (Exception ex) @@ -593,7 +595,7 @@ namespace StardewModdingApi.Installer break; case DirectoryInfo sourceDir: - DirectoryInfo targetSubfolder = new DirectoryInfo(Path.Combine(targetFolder.FullName, sourceDir.Name)); + DirectoryInfo targetSubfolder = new(Path.Combine(targetFolder.FullName, sourceDir.Name)); foreach (var entry in sourceDir.EnumerateFileSystemInfos()) this.RecursiveCopy(entry, targetSubfolder, filter); break; @@ -717,7 +719,7 @@ namespace StardewModdingApi.Installer // get directory if (File.Exists(path)) path = Path.GetDirectoryName(path); - DirectoryInfo directory = new DirectoryInfo(path); + DirectoryInfo directory = new(path); // validate path if (!directory.Exists) @@ -795,7 +797,7 @@ namespace StardewModdingApi.Installer // get path string appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "StardewValley"); - DirectoryInfo modDir = new DirectoryInfo(Path.Combine(appDataPath, "Mods")); + DirectoryInfo modDir = new(Path.Combine(appDataPath, "Mods")); // check if migration needed if (!modDir.Exists) @@ -808,7 +810,7 @@ namespace StardewModdingApi.Installer { // get type bool isDir = entry is DirectoryInfo; - if (!isDir && !(entry is FileInfo)) + if (!isDir && entry is not FileInfo) continue; // should never happen // delete packaged mods (newer version bundled into SMAPI) diff --git a/src/SMAPI.Installer/Program.cs b/src/SMAPI.Installer/Program.cs index 45cfea75..5139513f 100644 --- a/src/SMAPI.Installer/Program.cs +++ b/src/SMAPI.Installer/Program.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -31,7 +33,7 @@ namespace StardewModdingApi.Installer public static void Main(string[] args) { // find install bundle - FileInfo zipFile = new FileInfo(Path.Combine(Program.InstallerPath, "install.dat")); + FileInfo zipFile = new(Path.Combine(Program.InstallerPath, "install.dat")); if (!zipFile.Exists) { Console.WriteLine($"Oops! Some of the installer files are missing; try re-downloading the installer. (Missing file: {zipFile.FullName})"); @@ -40,7 +42,7 @@ namespace StardewModdingApi.Installer } // unzip bundle into temp folder - DirectoryInfo bundleDir = new DirectoryInfo(Program.ExtractedBundlePath); + DirectoryInfo bundleDir = new(Program.ExtractedBundlePath); Console.WriteLine("Extracting install files..."); ZipFile.ExtractToDirectory(zipFile.FullName, bundleDir.FullName); @@ -70,7 +72,7 @@ namespace StardewModdingApi.Installer { try { - AssemblyName name = new AssemblyName(e.Name); + AssemblyName name = new(e.Name); foreach (FileInfo dll in new DirectoryInfo(Program.InternalFilesPath).EnumerateFiles("*.dll")) { if (name.Name.Equals(AssemblyName.GetAssemblyName(dll.FullName).Name, StringComparison.OrdinalIgnoreCase)) diff --git a/src/SMAPI.Internal.Patching/BasePatcher.cs b/src/SMAPI.Internal.Patching/BasePatcher.cs index 87155d7f..6d019b52 100644 --- a/src/SMAPI.Internal.Patching/BasePatcher.cs +++ b/src/SMAPI.Internal.Patching/BasePatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Reflection; using HarmonyLib; diff --git a/src/SMAPI.Internal.Patching/HarmonyPatcher.cs b/src/SMAPI.Internal.Patching/HarmonyPatcher.cs index c07e3b41..fc239fd2 100644 --- a/src/SMAPI.Internal.Patching/HarmonyPatcher.cs +++ b/src/SMAPI.Internal.Patching/HarmonyPatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using HarmonyLib; @@ -15,7 +17,7 @@ namespace StardewModdingAPI.Internal.Patching /// <param name="patchers">The patchers to apply.</param> public static Harmony Apply(string id, IMonitor monitor, params IPatcher[] patchers) { - Harmony harmony = new Harmony(id); + Harmony harmony = new(id); foreach (IPatcher patcher in patchers) { diff --git a/src/SMAPI.Internal.Patching/IPatcher.cs b/src/SMAPI.Internal.Patching/IPatcher.cs index a732d64f..5b373117 100644 --- a/src/SMAPI.Internal.Patching/IPatcher.cs +++ b/src/SMAPI.Internal.Patching/IPatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using HarmonyLib; namespace StardewModdingAPI.Internal.Patching diff --git a/src/SMAPI.Internal.Patching/PatchHelper.cs b/src/SMAPI.Internal.Patching/PatchHelper.cs index fc79ddf2..52b15fd1 100644 --- a/src/SMAPI.Internal.Patching/PatchHelper.cs +++ b/src/SMAPI.Internal.Patching/PatchHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using System.Reflection; @@ -43,7 +45,7 @@ namespace StardewModdingAPI.Internal.Patching /// <param name="generics">The method generic types, or <c>null</c> if it's not generic.</param> public static string GetMethodString(Type type, string name, Type[] parameters = null, Type[] generics = null) { - StringBuilder str = new StringBuilder(); + StringBuilder str = new(); // type str.Append(type.FullName); diff --git a/src/SMAPI.Internal/ConsoleWriting/ColorSchemeConfig.cs b/src/SMAPI.Internal/ConsoleWriting/ColorSchemeConfig.cs index 001840bf..b22aa231 100644 --- a/src/SMAPI.Internal/ConsoleWriting/ColorSchemeConfig.cs +++ b/src/SMAPI.Internal/ConsoleWriting/ColorSchemeConfig.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; diff --git a/src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs b/src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs index bfe155e0..19a31c7b 100644 --- a/src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs +++ b/src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using StardewModdingAPI.Toolkit.Utilities; diff --git a/src/SMAPI.Internal/ConsoleWriting/IConsoleWriter.cs b/src/SMAPI.Internal/ConsoleWriting/IConsoleWriter.cs index fbcf161c..84e17207 100644 --- a/src/SMAPI.Internal/ConsoleWriting/IConsoleWriter.cs +++ b/src/SMAPI.Internal/ConsoleWriting/IConsoleWriter.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Internal.ConsoleWriting { /// <summary>Writes text to the console.</summary> diff --git a/src/SMAPI.Internal/ExceptionHelper.cs b/src/SMAPI.Internal/ExceptionHelper.cs index 03d48911..a856cf71 100644 --- a/src/SMAPI.Internal/ExceptionHelper.cs +++ b/src/SMAPI.Internal/ExceptionHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Reflection; using System.Text.RegularExpressions; @@ -25,7 +27,7 @@ namespace StardewModdingAPI.Internal case ReflectionTypeLoadException ex: string summary = ex.ToString(); - foreach (Exception childEx in ex.LoaderExceptions ?? Array.Empty<Exception>()) + foreach (Exception childEx in ex.LoaderExceptions) summary += $"\n\n{childEx?.GetLogSummary()}"; message = summary; break; @@ -43,15 +45,6 @@ namespace StardewModdingAPI.Internal } } - /// <summary>Get the lowest exception in an exception stack.</summary> - /// <param name="exception">The exception from which to search.</param> - public static Exception GetInnermostException(this Exception exception) - { - while (exception.InnerException != null) - exception = exception.InnerException; - return exception; - } - /// <summary>Simplify common patterns in exception log messages that don't convey useful info.</summary> /// <param name="message">The log message to simplify.</param> public static string SimplifyExtensionMessage(string message) diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticResult.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticResult.cs index 896c2cb8..8c24eda9 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticResult.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticResult.cs @@ -1,3 +1,5 @@ +#nullable disable + // <generated /> using Microsoft.CodeAnalysis; using System; diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.Helper.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.Helper.cs index 0247288e..68a892a9 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.Helper.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.Helper.cs @@ -1,3 +1,5 @@ +#nullable disable + // <generated /> using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -51,17 +53,17 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework protected static Diagnostic[] GetSortedDiagnosticsFromDocuments(DiagnosticAnalyzer analyzer, Document[] documents) { var projects = new HashSet<Project>(); - foreach (var document in documents) + foreach (Document document in documents) { projects.Add(document.Project); } var diagnostics = new List<Diagnostic>(); - foreach (var project in projects) + foreach (Project project in projects) { - var compilationWithAnalyzers = project.GetCompilationAsync().Result.WithAnalyzers(ImmutableArray.Create(analyzer)); + CompilationWithAnalyzers compilationWithAnalyzers = project.GetCompilationAsync().Result.WithAnalyzers(ImmutableArray.Create(analyzer)); var diags = compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().Result; - foreach (var diag in diags) + foreach (Diagnostic diag in diags) { if (diag.Location == Location.None || diag.Location.IsInMetadata) { @@ -71,8 +73,8 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework { for (int i = 0; i < documents.Length; i++) { - var document = documents[i]; - var tree = document.GetSyntaxTreeAsync().Result; + Document document = documents[i]; + SyntaxTree tree = document.GetSyntaxTreeAsync().Result; if (tree == diag.Location.SourceTree) { diagnostics.Add(diag); @@ -113,7 +115,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework throw new ArgumentException("Unsupported Language"); } - var project = CreateProject(sources, language); + Project project = CreateProject(sources, language); var documents = project.Documents.ToArray(); if (sources.Length != documents.Length) @@ -146,9 +148,9 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework string fileNamePrefix = DefaultFilePathPrefix; string fileExt = language == LanguageNames.CSharp ? CSharpDefaultFileExt : VisualBasicDefaultExt; - var projectId = ProjectId.CreateNewId(debugName: TestProjectName); + ProjectId projectId = ProjectId.CreateNewId(debugName: TestProjectName); - var solution = new AdhocWorkspace() + Solution solution = new AdhocWorkspace() .CurrentSolution .AddProject(projectId, TestProjectName, TestProjectName, language) .AddMetadataReference(projectId, DiagnosticVerifier.SelfReference) @@ -158,10 +160,10 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework .AddMetadataReference(projectId, CodeAnalysisReference); int count = 0; - foreach (var source in sources) + foreach (string source in sources) { - var newFileName = fileNamePrefix + count + "." + fileExt; - var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName); + string newFileName = fileNamePrefix + count + "." + fileExt; + DocumentId documentId = DocumentId.CreateNewId(projectId, debugName: newFileName); solution = solution.AddDocument(documentId, newFileName, SourceText.From(source)); count++; } diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.cs index edaaabd4..4170042d 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.cs @@ -1,3 +1,5 @@ +#nullable disable + // <generated /> using System.Collections.Generic; using System.Linq; @@ -41,7 +43,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework /// <param name="expected"> DiagnosticResults that should appear after the analyzer is run on the source</param> protected void VerifyCSharpDiagnostic(string source, params DiagnosticResult[] expected) { - VerifyDiagnostics(new[] { source }, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected); + this.VerifyDiagnostics(new[] { source }, LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzer(), expected); } /// <summary> @@ -52,7 +54,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework /// <param name="expected">DiagnosticResults that should appear after the analyzer is run on the sources</param> protected void VerifyCSharpDiagnostic(string[] sources, params DiagnosticResult[] expected) { - VerifyDiagnostics(sources, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected); + this.VerifyDiagnostics(sources, LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzer(), expected); } /// <summary> @@ -65,8 +67,8 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework /// <param name="expected">DiagnosticResults that should appear after the analyzer is run on the sources</param> private void VerifyDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expected) { - var diagnostics = GetSortedDiagnostics(sources, language, analyzer); - VerifyDiagnosticResults(diagnostics, analyzer, expected); + var diagnostics = DiagnosticVerifier.GetSortedDiagnostics(sources, language, analyzer); + DiagnosticVerifier.VerifyDiagnosticResults(diagnostics, analyzer, expected); } #endregion @@ -86,7 +88,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework if (expectedCount != actualCount) { - string diagnosticsOutput = actualResults.Any() ? FormatDiagnostics(analyzer, actualResults.ToArray()) : " NONE."; + string diagnosticsOutput = actualResults.Any() ? DiagnosticVerifier.FormatDiagnostics(analyzer, actualResults.ToArray()) : " NONE."; Assert.IsTrue(false, string.Format("Mismatch between number of diagnostics returned, expected \"{0}\" actual \"{1}\"\r\n\r\nDiagnostics:\r\n{2}\r\n", expectedCount, actualCount, diagnosticsOutput)); @@ -103,12 +105,12 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework { Assert.IsTrue(false, string.Format("Expected:\nA project diagnostic with No location\nActual:\n{0}", - FormatDiagnostics(analyzer, actual))); + DiagnosticVerifier.FormatDiagnostics(analyzer, actual))); } } else { - VerifyDiagnosticLocation(analyzer, actual, actual.Location, expected.Locations.First()); + DiagnosticVerifier.VerifyDiagnosticLocation(analyzer, actual, actual.Location, expected.Locations.First()); var additionalLocations = actual.AdditionalLocations.ToArray(); if (additionalLocations.Length != expected.Locations.Length - 1) @@ -116,12 +118,12 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework Assert.IsTrue(false, string.Format("Expected {0} additional locations but got {1} for Diagnostic:\r\n {2}\r\n", expected.Locations.Length - 1, additionalLocations.Length, - FormatDiagnostics(analyzer, actual))); + DiagnosticVerifier.FormatDiagnostics(analyzer, actual))); } for (int j = 0; j < additionalLocations.Length; ++j) { - VerifyDiagnosticLocation(analyzer, actual, additionalLocations[j], expected.Locations[j + 1]); + DiagnosticVerifier.VerifyDiagnosticLocation(analyzer, actual, additionalLocations[j], expected.Locations[j + 1]); } } @@ -129,21 +131,21 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework { Assert.IsTrue(false, string.Format("Expected diagnostic id to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Id, actual.Id, FormatDiagnostics(analyzer, actual))); + expected.Id, actual.Id, DiagnosticVerifier.FormatDiagnostics(analyzer, actual))); } if (actual.Severity != expected.Severity) { Assert.IsTrue(false, string.Format("Expected diagnostic severity to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Severity, actual.Severity, FormatDiagnostics(analyzer, actual))); + expected.Severity, actual.Severity, DiagnosticVerifier.FormatDiagnostics(analyzer, actual))); } if (actual.GetMessage() != expected.Message) { Assert.IsTrue(false, string.Format("Expected diagnostic message to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Message, actual.GetMessage(), FormatDiagnostics(analyzer, actual))); + expected.Message, actual.GetMessage(), DiagnosticVerifier.FormatDiagnostics(analyzer, actual))); } } } @@ -161,7 +163,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework Assert.IsTrue(actualSpan.Path == expected.Path || (actualSpan.Path != null && actualSpan.Path.Contains("Test0.") && expected.Path.Contains("Test.")), string.Format("Expected diagnostic to be in file \"{0}\" was actually in file \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Path, actualSpan.Path, FormatDiagnostics(analyzer, diagnostic))); + expected.Path, actualSpan.Path, DiagnosticVerifier.FormatDiagnostics(analyzer, diagnostic))); var actualLinePosition = actualSpan.StartLinePosition; @@ -172,7 +174,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework { Assert.IsTrue(false, string.Format("Expected diagnostic to be on line \"{0}\" was actually on line \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Line, actualLinePosition.Line + 1, FormatDiagnostics(analyzer, diagnostic))); + expected.Line, actualLinePosition.Line + 1, DiagnosticVerifier.FormatDiagnostics(analyzer, diagnostic))); } } @@ -183,7 +185,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework { Assert.IsTrue(false, string.Format("Expected diagnostic to start at column \"{0}\" was actually at column \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Column, actualLinePosition.Character + 1, FormatDiagnostics(analyzer, diagnostic))); + expected.Column, actualLinePosition.Character + 1, DiagnosticVerifier.FormatDiagnostics(analyzer, diagnostic))); } } } @@ -201,7 +203,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework var builder = new StringBuilder(); for (int i = 0; i < diagnostics.Length; ++i) { - builder.AppendLine("// " + diagnostics[i].ToString()); + builder.AppendLine("// " + diagnostics[i]); var analyzerType = analyzer.GetType(); var rules = analyzer.SupportedDiagnostics; diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetCollection.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetCollection.cs index d160610e..54aa1c6c 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetCollection.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetCollection.cs @@ -1,3 +1,5 @@ +#nullable disable + // ReSharper disable CheckNamespace -- matches Stardew Valley's code using System.Collections; using System.Collections.Generic; diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetFieldBase.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetFieldBase.cs index 140c6f59..1c349a0b 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetFieldBase.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetFieldBase.cs @@ -1,3 +1,5 @@ +#nullable disable + // ReSharper disable CheckNamespace -- matches Stardew Valley's code namespace Netcode { diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetInt.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetInt.cs index b3abc467..e8e1dc63 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetInt.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetInt.cs @@ -1,3 +1,5 @@ +#nullable disable + // ReSharper disable CheckNamespace -- matches Stardew Valley's code namespace Netcode { diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetList.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetList.cs index 1699f71c..f7fb9617 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetList.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetList.cs @@ -1,3 +1,5 @@ +#nullable disable + // ReSharper disable CheckNamespace -- matches Stardew Valley's code using System.Collections; using System.Collections.Generic; diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetObjectList.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetObjectList.cs index 7814e7d6..74c17843 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetObjectList.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetObjectList.cs @@ -1,3 +1,5 @@ +#nullable disable + // ReSharper disable CheckNamespace -- matches Stardew Valley's code namespace Netcode { diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Farmer.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Farmer.cs index 13fab069..bdbf9b45 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Farmer.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Farmer.cs @@ -1,3 +1,5 @@ +#nullable disable + // ReSharper disable CheckNamespace, InconsistentNaming -- matches Stardew Valley's code #pragma warning disable 649 // (never assigned) -- only used to test type conversions using System.Collections.Generic; diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Item.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Item.cs index 1b6317c1..d1f0afc4 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Item.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Item.cs @@ -1,3 +1,5 @@ +#nullable disable + // ReSharper disable CheckNamespace, InconsistentNaming -- matches Stardew Valley's code using Netcode; @@ -7,27 +9,27 @@ namespace StardewValley public class Item { /// <summary>A net int field with an equivalent non-net <c>Category</c> property.</summary> - public readonly NetInt category = new NetInt { Value = 42 }; + public readonly NetInt category = new() { Value = 42 }; /// <summary>A generic net int field with no equivalent non-net property.</summary> - public readonly NetInt netIntField = new NetInt { Value = 42 }; + public readonly NetInt netIntField = new() { Value = 42 }; /// <summary>A generic net ref field with no equivalent non-net property.</summary> - public readonly NetRef<object> netRefField = new NetRef<object>(); + public readonly NetRef<object> netRefField = new(); /// <summary>A generic net int property with no equivalent non-net property.</summary> - public NetInt netIntProperty = new NetInt { Value = 42 }; + public NetInt netIntProperty = new() { Value = 42 }; /// <summary>A generic net ref property with no equivalent non-net property.</summary> - public NetRef<object> netRefProperty { get; } = new NetRef<object>(); + public NetRef<object> netRefProperty { get; } = new(); /// <summary>A sample net list.</summary> - public readonly NetList<int> netList = new NetList<int>(); + public readonly NetList<int> netList = new(); /// <summary>A sample net object list.</summary> - public readonly NetObjectList<int> netObjectList = new NetObjectList<int>(); + public readonly NetObjectList<int> netObjectList = new(); /// <summary>A sample net collection.</summary> - public readonly NetCollection<int> netCollection = new NetCollection<int>(); + public readonly NetCollection<int> netCollection = new(); } } diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Object.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Object.cs index 3dd66a6d..f54b22fe 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Object.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Object.cs @@ -1,3 +1,5 @@ +#nullable disable + // ReSharper disable CheckNamespace, InconsistentNaming -- matches Stardew Valley's code using Netcode; @@ -7,6 +9,6 @@ namespace StardewValley public class Object : Item { /// <summary>A net int field with an equivalent non-net property.</summary> - public NetInt type = new NetInt { Value = 42 }; + public NetInt type = new() { Value = 42 }; } } diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/NetFieldAnalyzerTests.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/NetFieldAnalyzerTests.cs index 89bd1be5..29f3b956 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/NetFieldAnalyzerTests.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/NetFieldAnalyzerTests.cs @@ -1,3 +1,5 @@ +#nullable disable + using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using NUnit.Framework; @@ -93,7 +95,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests { // arrange string code = NetFieldAnalyzerTests.SampleProgram.Replace("{{test-code}}", codeText); - DiagnosticResult expected = new DiagnosticResult + DiagnosticResult expected = new() { Id = "AvoidImplicitNetFieldCast", Message = $"This implicitly converts '{expression}' from {fromType} to {toType}, but {fromType} has unintuitive implicit conversion rules. Consider comparing against the actual value instead to avoid bugs. See https://smapi.io/package/avoid-implicit-net-field-cast for details.", @@ -135,7 +137,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests { // arrange string code = NetFieldAnalyzerTests.SampleProgram.Replace("{{test-code}}", codeText); - DiagnosticResult expected = new DiagnosticResult + DiagnosticResult expected = new() { Id = "AvoidNetField", Message = $"'{expression}' is a {netType} field; consider using the {suggestedProperty} property instead. See https://smapi.io/package/avoid-net-field for details.", diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/ObsoleteFieldAnalyzerTests.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/ObsoleteFieldAnalyzerTests.cs index 12641e1a..1cf7369f 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/ObsoleteFieldAnalyzerTests.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/ObsoleteFieldAnalyzerTests.cs @@ -1,3 +1,5 @@ +#nullable disable + using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using NUnit.Framework; @@ -64,7 +66,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests { // arrange string code = ObsoleteFieldAnalyzerTests.SampleProgram.Replace("{{test-code}}", codeText); - DiagnosticResult expected = new DiagnosticResult + DiagnosticResult expected = new() { Id = "AvoidObsoleteField", Message = $"The '{oldName}' field is obsolete and should be replaced with '{newName}'. See https://smapi.io/package/avoid-obsolete-field for details.", diff --git a/src/SMAPI.ModBuildConfig.Analyzer/AnalyzerReleases.Shipped.md b/src/SMAPI.ModBuildConfig.Analyzer/AnalyzerReleases.Shipped.md new file mode 100644 index 00000000..9a46676d --- /dev/null +++ b/src/SMAPI.ModBuildConfig.Analyzer/AnalyzerReleases.Shipped.md @@ -0,0 +1,7 @@ +## Release 2.1.0 +### New Rules +Rule ID | Category | Severity | Notes +------------------------- | ------------------ | -------- | ------------------------------------------------------------ +AvoidImplicitNetFieldCast | SMAPI.CommonErrors | Warning | See [documentation](https://smapi.io/package/code-warnings). +AvoidNetField | SMAPI.CommonErrors | Warning | See [documentation](https://smapi.io/package/code-warnings). +AvoidObsoleteField | SMAPI.CommonErrors | Warning | See [documentation](https://smapi.io/package/code-warnings). diff --git a/src/SMAPI.ModBuildConfig.Analyzer/AnalyzerUtilities.cs b/src/SMAPI.ModBuildConfig.Analyzer/AnalyzerUtilities.cs index 68b5001e..1cc37b38 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer/AnalyzerUtilities.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer/AnalyzerUtilities.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -64,7 +66,7 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer } // conditional access - if (node is ConditionalAccessExpressionSyntax conditionalAccess && conditionalAccess.WhenNotNull is MemberBindingExpressionSyntax conditionalBinding) + if (node is ConditionalAccessExpressionSyntax { WhenNotNull: MemberBindingExpressionSyntax conditionalBinding } conditionalAccess) { declaringType = semanticModel.GetTypeInfo(conditionalAccess.Expression).Type; memberType = semanticModel.GetTypeInfo(node); diff --git a/src/SMAPI.ModBuildConfig.Analyzer/NetFieldAnalyzer.cs b/src/SMAPI.ModBuildConfig.Analyzer/NetFieldAnalyzer.cs index e03c72de..cb2856da 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer/NetFieldAnalyzer.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer/NetFieldAnalyzer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -132,7 +134,7 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer }; /// <summary>The diagnostic info for an implicit net field cast.</summary> - private readonly DiagnosticDescriptor AvoidImplicitNetFieldCastRule = new DiagnosticDescriptor( + private readonly DiagnosticDescriptor AvoidImplicitNetFieldCastRule = new( id: "AvoidImplicitNetFieldCast", title: "Netcode types shouldn't be implicitly converted", messageFormat: "This implicitly converts '{0}' from {1} to {2}, but {1} has unintuitive implicit conversion rules. Consider comparing against the actual value instead to avoid bugs. See https://smapi.io/package/avoid-implicit-net-field-cast for details.", @@ -143,7 +145,7 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer ); /// <summary>The diagnostic info for an avoidable net field access.</summary> - private readonly DiagnosticDescriptor AvoidNetFieldRule = new DiagnosticDescriptor( + private readonly DiagnosticDescriptor AvoidNetFieldRule = new( id: "AvoidNetField", title: "Avoid Netcode types when possible", messageFormat: "'{0}' is a {1} field; consider using the {2} property instead. See https://smapi.io/package/avoid-net-field for details.", @@ -227,10 +229,7 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer // warn: implicit conversion if (this.IsInvalidConversion(memberType.Type, memberType.ConvertedType)) - { context.ReportDiagnostic(Diagnostic.Create(this.AvoidImplicitNetFieldCastRule, context.Node.GetLocation(), context.Node, memberType.Type.Name, memberType.ConvertedType)); - return; - } }); } diff --git a/src/SMAPI.ModBuildConfig.Analyzer/ObsoleteFieldAnalyzer.cs b/src/SMAPI.ModBuildConfig.Analyzer/ObsoleteFieldAnalyzer.cs index 722d5227..158d7243 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer/ObsoleteFieldAnalyzer.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer/ObsoleteFieldAnalyzer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -24,7 +26,7 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer /// <summary>Describes the diagnostic rule covered by the analyzer.</summary> private readonly IDictionary<string, DiagnosticDescriptor> Rules = new Dictionary<string, DiagnosticDescriptor> { - ["AvoidObsoleteField"] = new DiagnosticDescriptor( + ["AvoidObsoleteField"] = new( id: "AvoidObsoleteField", title: "Reference to obsolete field", messageFormat: "The '{0}' field is obsolete and should be replaced with '{1}'. See https://smapi.io/package/avoid-obsolete-field for details.", @@ -77,7 +79,7 @@ namespace StardewModdingAPI.ModBuildConfig.Analyzer try { // get reference info - if (!AnalyzerUtilities.TryGetMemberInfo(context.Node, context.SemanticModel, out ITypeSymbol declaringType, out TypeInfo memberType, out string memberName)) + if (!AnalyzerUtilities.TryGetMemberInfo(context.Node, context.SemanticModel, out ITypeSymbol declaringType, out _, out string memberName)) return; // suggest replacement diff --git a/src/SMAPI.ModBuildConfig.Analyzer/SMAPI.ModBuildConfig.Analyzer.csproj b/src/SMAPI.ModBuildConfig.Analyzer/SMAPI.ModBuildConfig.Analyzer.csproj index 3fadc37a..7ac3277e 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer/SMAPI.ModBuildConfig.Analyzer.csproj +++ b/src/SMAPI.ModBuildConfig.Analyzer/SMAPI.ModBuildConfig.Analyzer.csproj @@ -13,6 +13,10 @@ </ItemGroup> <ItemGroup> + <AdditionalFiles Include="AnalyzerReleases.Shipped.md" /> + </ItemGroup> + + <ItemGroup> <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" /> </ItemGroup> </Project> diff --git a/src/SMAPI.ModBuildConfig/DeployModTask.cs b/src/SMAPI.ModBuildConfig/DeployModTask.cs index 140933bd..43fac9d5 100644 --- a/src/SMAPI.ModBuildConfig/DeployModTask.cs +++ b/src/SMAPI.ModBuildConfig/DeployModTask.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; @@ -88,7 +90,7 @@ namespace StardewModdingAPI.ModBuildConfig Regex[] ignoreFilePatterns = this.GetCustomIgnorePatterns().ToArray(); // get mod info - ModFileManager package = new ModFileManager(this.ProjectDir, this.TargetDir, ignoreFilePaths, ignoreFilePatterns, bundleAssemblyTypes, this.ModDllName, validateRequiredModFiles: this.EnableModDeploy || this.EnableModZip); + ModFileManager package = new(this.ProjectDir, this.TargetDir, ignoreFilePaths, ignoreFilePatterns, bundleAssemblyTypes, this.ModDllName, validateRequiredModFiles: this.EnableModDeploy || this.EnableModZip); // deploy mod files if (this.EnableModDeploy) @@ -246,7 +248,7 @@ namespace StardewModdingAPI.ModBuildConfig // create zip file Directory.CreateDirectory(Path.GetDirectoryName(zipPath)!); using Stream zipStream = new FileStream(zipPath, FileMode.Create, FileAccess.Write); - using ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create); + using ZipArchive archive = new(zipStream, ZipArchiveMode.Create); foreach (var fileEntry in files) { diff --git a/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs b/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs index ad4ffdf9..ad2c0de3 100644 --- a/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs +++ b/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; @@ -136,7 +138,7 @@ namespace StardewModdingAPI.ModBuildConfig.Framework // project manifest bool hasProjectManifest = false; { - FileInfo manifest = new FileInfo(Path.Combine(projectDir, this.ManifestFileName)); + FileInfo manifest = new(Path.Combine(projectDir, this.ManifestFileName)); if (manifest.Exists) { yield return Tuple.Create(this.ManifestFileName, manifest); @@ -146,7 +148,7 @@ namespace StardewModdingAPI.ModBuildConfig.Framework // project i18n files bool hasProjectTranslations = false; - DirectoryInfo translationsFolder = new DirectoryInfo(Path.Combine(projectDir, "i18n")); + DirectoryInfo translationsFolder = new(Path.Combine(projectDir, "i18n")); if (translationsFolder.Exists) { foreach (FileInfo file in translationsFolder.EnumerateFiles()) @@ -156,7 +158,7 @@ namespace StardewModdingAPI.ModBuildConfig.Framework // project assets folder bool hasAssetsFolder = false; - DirectoryInfo assetsFolder = new DirectoryInfo(Path.Combine(projectDir, "assets")); + DirectoryInfo assetsFolder = new(Path.Combine(projectDir, "assets")); if (assetsFolder.Exists) { foreach (FileInfo file in assetsFolder.EnumerateFiles("*", SearchOption.AllDirectories)) @@ -168,7 +170,7 @@ namespace StardewModdingAPI.ModBuildConfig.Framework } // build output - DirectoryInfo buildFolder = new DirectoryInfo(targetDir); + DirectoryInfo buildFolder = new(targetDir); foreach (FileInfo file in buildFolder.EnumerateFiles("*", SearchOption.AllDirectories)) { // get path info diff --git a/src/SMAPI.ModBuildConfig/Framework/UserErrorException.cs b/src/SMAPI.ModBuildConfig/Framework/UserErrorException.cs index 64e31c29..588118ef 100644 --- a/src/SMAPI.ModBuildConfig/Framework/UserErrorException.cs +++ b/src/SMAPI.ModBuildConfig/Framework/UserErrorException.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.ModBuildConfig.Framework diff --git a/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj b/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj index 0bc8c45e..82eac7f6 100644 --- a/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj +++ b/src/SMAPI.ModBuildConfig/SMAPI.ModBuildConfig.csproj @@ -5,6 +5,7 @@ <TargetFramework>netstandard2.0</TargetFramework> <LangVersion>latest</LangVersion> <GeneratePackageOnBuild>true</GeneratePackageOnBuild> + <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking> <!--NuGet package--> <PackageId>Pathoschild.Stardew.ModBuildConfig</PackageId> diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ArgumentParser.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ArgumentParser.cs index 7e157c38..8fcbf711 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ArgumentParser.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ArgumentParser.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections; using System.Collections.Generic; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ConsoleCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ConsoleCommand.cs index 01cab92e..a8dd41f5 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ConsoleCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ConsoleCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -100,7 +102,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands List<string[]> lines = new List<string[]>(rows.Length + 2) { header, - header.Select((value, i) => "".PadRight(widths[i], '-')).ToArray() + header.Select((_, i) => "".PadRight(widths[i], '-')).ToArray() }; lines.AddRange(rows); diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/IConsoleCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/IConsoleCommand.cs index 9c82bbd3..4c6df538 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/IConsoleCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/IConsoleCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands { /// <summary>A console command to register.</summary> diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ApplySaveFixCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ApplySaveFixCommand.cs index 957b0e75..f31457ed 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ApplySaveFixCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ApplySaveFixCommand.cs @@ -1,10 +1,14 @@ +#nullable disable + using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other { /// <summary>A command which runs one of the game's save migrations.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class ApplySaveFixCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/DebugCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/DebugCommand.cs index 1955c14e..f289c669 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/DebugCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/DebugCommand.cs @@ -1,8 +1,12 @@ -using StardewValley; +#nullable disable + +using System.Diagnostics.CodeAnalysis; +using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other { /// <summary>A command which sends a debug command to the game.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class DebugCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/RegenerateBundles.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/RegenerateBundles.cs index 9beedb96..81a8c570 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/RegenerateBundles.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/RegenerateBundles.cs @@ -1,5 +1,8 @@ +#nullable disable + using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using Netcode; @@ -9,6 +12,7 @@ using StardewValley.Network; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other { /// <summary>A command which regenerates the game's bundles.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class RegenerateBundlesCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowDataFilesCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowDataFilesCommand.cs index 27f6ce53..d762d8bf 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowDataFilesCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowDataFilesCommand.cs @@ -1,8 +1,12 @@ -using System.Diagnostics; +#nullable disable + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other { /// <summary>A command which shows the data files.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class ShowDataFilesCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowGameFilesCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowGameFilesCommand.cs index b97cb3e6..b5733eb9 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowGameFilesCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowGameFilesCommand.cs @@ -1,8 +1,12 @@ +#nullable disable + using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other { /// <summary>A command which shows the game files.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class ShowGameFilesCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/TestInputCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/TestInputCommand.cs index 46583dc3..5484fc7c 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/TestInputCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/TestInputCommand.cs @@ -1,8 +1,12 @@ +#nullable disable + using System; +using System.Diagnostics.CodeAnalysis; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other { /// <summary>A command which logs the keys being pressed for 30 seconds once enabled.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class TestInputCommand : ConsoleCommand { /********* @@ -37,9 +41,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other public override void OnUpdated(IMonitor monitor) { // handle expiry - if (this.ExpiryTicks == null) - return; - if (this.ExpiryTicks <= DateTime.UtcNow.Ticks) + if (this.ExpiryTicks != null && this.ExpiryTicks <= DateTime.UtcNow.Ticks) { monitor.Log("No longer logging input.", LogLevel.Info); this.ExpiryTicks = null; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/AddCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/AddCommand.cs index 0e8f7517..0d8db870 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/AddCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/AddCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData; @@ -13,7 +15,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player ** Fields *********/ /// <summary>Provides methods for searching and constructing items.</summary> - private readonly ItemRepository Items = new ItemRepository(); + private readonly ItemRepository Items = new(); /// <summary>The type names recognized by this command.</summary> private readonly string[] ValidTypes = Enum.GetNames(typeof(ItemType)).Concat(new[] { "Name" }).ToArray(); diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemTypesCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemTypesCommand.cs index 1f12e5f9..e57d4065 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemTypesCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemTypesCommand.cs @@ -1,16 +1,20 @@ -using System.Linq; +#nullable disable + +using System.Diagnostics.CodeAnalysis; +using System.Linq; using StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// <summary>A command which list item types.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class ListItemTypesCommand : ConsoleCommand { /********* ** Fields *********/ /// <summary>Provides methods for searching and constructing items.</summary> - private readonly ItemRepository Items = new ItemRepository(); + private readonly ItemRepository Items = new(); /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemsCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemsCommand.cs index 67569298..5a21b459 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemsCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemsCommand.cs @@ -1,18 +1,22 @@ +#nullable disable + using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// <summary>A command which list items available to spawn.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class ListItemsCommand : ConsoleCommand { /********* ** Fields *********/ /// <summary>Provides methods for searching and constructing items.</summary> - private readonly ItemRepository Items = new ItemRepository(); + private readonly ItemRepository Items = new(); /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetColorCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetColorCommand.cs index 7b7cbf83..e8605163 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetColorCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetColorCommand.cs @@ -1,9 +1,13 @@ +#nullable disable + +using System.Diagnostics.CodeAnalysis; using Microsoft.Xna.Framework; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// <summary>A command which edits the color of a player feature.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class SetColorCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetFarmTypeCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetFarmTypeCommand.cs index 6fb399ae..02670911 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetFarmTypeCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetFarmTypeCommand.cs @@ -1,6 +1,9 @@ +#nullable disable + using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Text; @@ -10,6 +13,7 @@ using StardewValley.GameData; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// <summary>A command which changes the player's farm type.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class SetFarmTypeCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetHealthCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetHealthCommand.cs index f27b336f..1a1a9eab 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetHealthCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetHealthCommand.cs @@ -1,9 +1,13 @@ +#nullable disable + +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// <summary>A command which edits the player's current health.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class SetHealthCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetImmunityCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetImmunityCommand.cs index df90adf2..d1dede1f 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetImmunityCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetImmunityCommand.cs @@ -1,9 +1,13 @@ -using System.Linq; +#nullable disable + +using System.Diagnostics.CodeAnalysis; +using System.Linq; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// <summary>A command which edits the player's current immunity.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class SetImmunityCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxHealthCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxHealthCommand.cs index a5f7f444..2b3b140c 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxHealthCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxHealthCommand.cs @@ -1,9 +1,13 @@ -using System.Linq; +#nullable disable + +using System.Diagnostics.CodeAnalysis; +using System.Linq; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// <summary>A command which edits the player's maximum health.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class SetMaxHealthCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxStaminaCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxStaminaCommand.cs index e3c2f011..f9ed6c58 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxStaminaCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxStaminaCommand.cs @@ -1,9 +1,13 @@ -using System.Linq; +#nullable disable + +using System.Diagnostics.CodeAnalysis; +using System.Linq; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// <summary>A command which edits the player's maximum stamina.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class SetMaxStaminaCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMoneyCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMoneyCommand.cs index 787ce920..56447a65 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMoneyCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMoneyCommand.cs @@ -1,9 +1,13 @@ +#nullable disable + +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// <summary>A command which edits the player's current money.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class SetMoneyCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetNameCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetNameCommand.cs index 4911ad1c..4ce7e1f8 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetNameCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetNameCommand.cs @@ -1,8 +1,12 @@ +#nullable disable + +using System.Diagnostics.CodeAnalysis; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// <summary>A command which edits the player's name.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class SetNameCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStaminaCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStaminaCommand.cs index c78378ef..ea8d74c2 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStaminaCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStaminaCommand.cs @@ -1,9 +1,13 @@ +#nullable disable + +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// <summary>A command which edits the player's current stamina.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class SetStaminaCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStyleCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStyleCommand.cs index 98f6c330..84625a34 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStyleCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStyleCommand.cs @@ -1,8 +1,12 @@ +#nullable disable + +using System.Diagnostics.CodeAnalysis; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// <summary>A command which edits a player style.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class SetStyleCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs index ceaeb278..92c73e08 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs @@ -1,4 +1,7 @@ +#nullable disable + using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewValley; using StardewValley.Locations; @@ -9,6 +12,7 @@ using SObject = StardewValley.Object; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World { /// <summary>A command which clears in-game objects.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class ClearCommand : ConsoleCommand { /********* @@ -92,11 +96,10 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World removed += this.RemoveObjects(location, obj => - !(obj is Chest) + obj is not Chest && ( - obj.Name == "Weeds" - || obj.Name == "Stone" - || (obj.ParentSheetIndex == 294 || obj.ParentSheetIndex == 295) + obj.Name is "Weeds" or "Stone" + || obj.ParentSheetIndex is 294 or 295 ) ) + this.RemoveResourceClumps(location, clump => this.DebrisClumps.Contains(clump.parentSheetIndex.Value)); @@ -114,7 +117,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World case "furniture": { - int removed = this.RemoveFurniture(location, furniture => true); + int removed = this.RemoveFurniture(location, _ => true); monitor.Log($"Done! Removed {removed} entities from {location.Name}.", LogLevel.Info); break; } @@ -138,11 +141,11 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World { bool everything = type == "everything"; int removed = - this.RemoveFurniture(location, p => true) - + this.RemoveObjects(location, p => true) - + this.RemoveTerrainFeatures(location, p => true) - + this.RemoveLargeTerrainFeatures(location, p => everything || !(p is Bush bush) || bush.isDestroyable(location, p.currentTileLocation)) - + this.RemoveResourceClumps(location, p => true); + this.RemoveFurniture(location, _ => true) + + this.RemoveObjects(location, _ => true) + + this.RemoveTerrainFeatures(location, _ => true) + + this.RemoveLargeTerrainFeatures(location, p => everything || p is not Bush bush || bush.isDestroyable(location, p.currentTileLocation)) + + this.RemoveResourceClumps(location, _ => true); monitor.Log($"Done! Removed {removed} entities from {location.Name}.", LogLevel.Info); break; } diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/DownMineLevelCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/DownMineLevelCommand.cs index 0aa9c9c3..0f18c760 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/DownMineLevelCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/DownMineLevelCommand.cs @@ -1,9 +1,13 @@ +#nullable disable + +using System.Diagnostics.CodeAnalysis; using StardewValley; using StardewValley.Locations; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World { /// <summary>A command which moves the player to the next mine level.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class DownMineLevelCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/FreezeTimeCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/FreezeTimeCommand.cs index 16faa2fe..8808fe35 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/FreezeTimeCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/FreezeTimeCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Linq; using StardewValley; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/HurryAllCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/HurryAllCommand.cs index 2deac5f8..f9810dc3 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/HurryAllCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/HurryAllCommand.cs @@ -1,9 +1,13 @@ +#nullable disable + using System; +using System.Diagnostics.CodeAnalysis; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World { /// <summary>A command which immediately warps all NPCs to their scheduled positions. To hurry a single NPC, see <c>debug hurry npc-name</c> instead.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class HurryAllCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetDayCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetDayCommand.cs index 4028b3dc..8aa27d93 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetDayCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetDayCommand.cs @@ -1,10 +1,14 @@ -using System.Linq; +#nullable disable + +using System.Diagnostics.CodeAnalysis; +using System.Linq; using StardewModdingAPI.Utilities; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World { /// <summary>A command which sets the current day.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class SetDayCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetMineLevelCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetMineLevelCommand.cs index 40f4b19f..ad6ac777 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetMineLevelCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetMineLevelCommand.cs @@ -1,9 +1,13 @@ +#nullable disable + using System; +using System.Diagnostics.CodeAnalysis; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World { /// <summary>A command which moves the player to the given mine level.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class SetMineLevelCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetSeasonCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetSeasonCommand.cs index a4cb35bb..ebe58913 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetSeasonCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetSeasonCommand.cs @@ -1,3 +1,6 @@ +#nullable disable + +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewModdingAPI.Utilities; using StardewValley; @@ -5,6 +8,7 @@ using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World { /// <summary>A command which sets the current season.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class SetSeasonCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs index 2d4b4565..1e6bab96 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs @@ -1,3 +1,6 @@ +#nullable disable + +using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.Xna.Framework; using StardewValley; @@ -5,6 +8,7 @@ using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World { /// <summary>A command which sets the current time.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class SetTimeCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetYearCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetYearCommand.cs index 95401962..995f222e 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetYearCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetYearCommand.cs @@ -1,10 +1,14 @@ -using System.Linq; +#nullable disable + +using System.Diagnostics.CodeAnalysis; +using System.Linq; using StardewModdingAPI.Utilities; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World { /// <summary>A command which sets the current year.</summary> + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class SetYearCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs index 72d01eb7..ab0b2e05 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewValley; @@ -43,16 +45,6 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData this.Item = createItem(this); } - /// <summary>Construct an instance.</summary> - /// <param name="item">The item metadata to copy.</param> - public SearchableItem(SearchableItem item) - { - this.Type = item.Type; - this.ID = item.ID; - this.CreateItem = item.CreateItem; - this.Item = item.Item; - } - /// <summary>Get whether the item name contains a case-insensitive substring.</summary> /// <param name="substring">The substring to find.</param> public bool NameContains(string substring) diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs index 8704a403..7d2a1662 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -106,8 +108,8 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework { foreach (int id in this.TryLoad<int, string>("Data\\weapons").Keys) { - yield return this.TryCreate(ItemType.Weapon, id, p => (p.ID >= 32 && p.ID <= 34) - ? (Item)new Slingshot(p.ID) + yield return this.TryCreate(ItemType.Weapon, id, p => p.ID is >= 32 and <= 34 + ? new Slingshot(p.ID) : new MeleeWeapon(p.ID) ); } @@ -285,7 +287,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework case SObject.flowersCategory: yield return this.TryCreate(ItemType.Object, this.CustomIDOffset * 5 + id, _ => { - SObject honey = new SObject(Vector2.Zero, 340, $"{item.Name} Honey", false, true, false, false) + SObject honey = new(Vector2.Zero, 340, $"{item.Name} Honey", false, true, false, false) { Name = $"{item.Name} Honey", preservedParentSheetIndex = { id } diff --git a/src/SMAPI.Mods.ConsoleCommands/ModEntry.cs b/src/SMAPI.Mods.ConsoleCommands/ModEntry.cs index 91437fd3..e3ca1a39 100644 --- a/src/SMAPI.Mods.ConsoleCommands/ModEntry.cs +++ b/src/SMAPI.Mods.ConsoleCommands/ModEntry.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -71,7 +73,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands /// <param name="args">The command arguments.</param> private void HandleCommand(IConsoleCommand command, string commandName, string[] args) { - ArgumentParser argParser = new ArgumentParser(commandName, args, this.Monitor); + ArgumentParser argParser = new(commandName, args, this.Monitor); command.Handle(this.Monitor, commandName, argParser); } diff --git a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs index 2d6242cf..fa171012 100644 --- a/src/SMAPI.Mods.ErrorHandler/ModEntry.cs +++ b/src/SMAPI.Mods.ErrorHandler/ModEntry.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Reflection; using StardewModdingAPI.Events; diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/DialoguePatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/DialoguePatcher.cs index 7a3af39c..b05c8cca 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/DialoguePatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/DialoguePatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics.CodeAnalysis; using HarmonyLib; diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/EventPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/EventPatcher.cs index 1b706147..63674d09 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/EventPatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/EventPatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics.CodeAnalysis; using HarmonyLib; diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatcher.cs index 7df6b0a2..98aa4a38 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics.CodeAnalysis; using HarmonyLib; diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs index b65a695a..85ce8ac4 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Diagnostics.CodeAnalysis; using HarmonyLib; using StardewModdingAPI.Internal.Patching; diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/NpcPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/NpcPatcher.cs index 275bb5bf..5354f724 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/NpcPatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/NpcPatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/ObjectPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/ObjectPatcher.cs index fd4ea35c..499718b0 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/ObjectPatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/ObjectPatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs index 0a7ed212..1941d2a8 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -77,7 +79,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches private static Exception Finalize_LoadFarmType(Exception __exception) { // missing custom farm type - if (__exception?.Message?.Contains("not a valid farm type") == true && !int.TryParse(SaveGame.loaded.whichFarm, out _)) + if (__exception?.Message.Contains("not a valid farm type") == true && !int.TryParse(SaveGame.loaded.whichFarm, out _)) { SaveGamePatcher.Monitor.Log(__exception.GetLogSummary(), LogLevel.Error); SaveGamePatcher.Monitor.Log($"Removed invalid custom farm type '{SaveGame.loaded.whichFarm}' to avoid a crash when loading save '{Constants.SaveFolderName}'. (Did you remove a custom farm type mod?)", LogLevel.Warn); @@ -121,7 +123,7 @@ namespace StardewModdingAPI.Mods.ErrorHandler.Patches { try { - BluePrint _ = new BluePrint(building.buildingType.Value); + BluePrint _ = new(building.buildingType.Value); } catch (ContentLoadException) { diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs index f243c6d1..b4c03bb9 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics.CodeAnalysis; using HarmonyLib; diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/UtilityPatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/UtilityPatcher.cs index ce85d0c2..108ed585 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/UtilityPatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/UtilityPatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics.CodeAnalysis; using HarmonyLib; diff --git a/src/SMAPI.Mods.SaveBackup/ModEntry.cs b/src/SMAPI.Mods.SaveBackup/ModEntry.cs index b89bb9c3..b2b41ca6 100644 --- a/src/SMAPI.Mods.SaveBackup/ModEntry.cs +++ b/src/SMAPI.Mods.SaveBackup/ModEntry.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics; using System.IO; @@ -38,13 +40,13 @@ namespace StardewModdingAPI.Mods.SaveBackup try { // init backup folder - DirectoryInfo backupFolder = new DirectoryInfo(this.BackupFolder); + DirectoryInfo backupFolder = new(this.BackupFolder); backupFolder.Create(); // back up & prune saves Task .Run(() => this.CreateBackup(backupFolder)) - .ContinueWith(backupTask => this.PruneBackups(backupFolder, this.BackupsToKeep)); + .ContinueWith(_ => this.PruneBackups(backupFolder, this.BackupsToKeep)); } catch (Exception ex) { @@ -63,8 +65,8 @@ namespace StardewModdingAPI.Mods.SaveBackup try { // get target path - FileInfo targetFile = new FileInfo(Path.Combine(backupFolder.FullName, this.FileName)); - DirectoryInfo fallbackDir = new DirectoryInfo(Path.Combine(backupFolder.FullName, this.BackupLabel)); + FileInfo targetFile = new(Path.Combine(backupFolder.FullName, this.FileName)); + DirectoryInfo fallbackDir = new(Path.Combine(backupFolder.FullName, this.BackupLabel)); if (targetFile.Exists || fallbackDir.Exists) { this.Monitor.Log("Already backed up today."); @@ -72,7 +74,7 @@ namespace StardewModdingAPI.Mods.SaveBackup } // copy saves to fallback directory (ignore non-save files/folders) - DirectoryInfo savesDir = new DirectoryInfo(Constants.SavesPath); + DirectoryInfo savesDir = new(Constants.SavesPath); if (!this.RecursiveCopy(savesDir, fallbackDir, entry => this.MatchSaveFolders(savesDir, entry), copyRoot: false)) { this.Monitor.Log("No saves found."); @@ -170,8 +172,8 @@ namespace StardewModdingAPI.Mods.SaveBackup try { // create compressed backup - Assembly coreAssembly = Assembly.Load("System.IO.Compression, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089") ?? throw new InvalidOperationException("Can't load System.IO.Compression assembly."); - Assembly fsAssembly = Assembly.Load("System.IO.Compression.FileSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089") ?? throw new InvalidOperationException("Can't load System.IO.Compression assembly."); + Assembly coreAssembly = Assembly.Load("System.IO.Compression, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); + Assembly fsAssembly = Assembly.Load("System.IO.Compression.FileSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); Type compressionLevelType = coreAssembly.GetType("System.IO.Compression.CompressionLevel") ?? throw new InvalidOperationException("Can't load CompressionLevel type."); Type zipFileType = fsAssembly.GetType("System.IO.Compression.ZipFile") ?? throw new InvalidOperationException("Can't load ZipFile type."); createFromDirectory = zipFileType.GetMethod("CreateFromDirectory", new[] { typeof(string), typeof(string), compressionLevelType, typeof(bool) }) ?? throw new InvalidOperationException("Can't load ZipFile.CreateFromDirectory method."); @@ -190,8 +192,8 @@ namespace StardewModdingAPI.Mods.SaveBackup /// <param name="destination">The destination file to create.</param> private void CompressUsingMacProcess(string sourcePath, FileInfo destination) { - DirectoryInfo saveFolder = new DirectoryInfo(sourcePath); - ProcessStartInfo startInfo = new ProcessStartInfo + DirectoryInfo saveFolder = new(sourcePath); + ProcessStartInfo startInfo = new() { FileName = "zip", Arguments = $"-rq \"{destination.FullName}\" \"{saveFolder.Name}\" -x \"*.DS_Store\" -x \"__MACOSX\"", diff --git a/src/SMAPI.Tests.ModApiConsumer/ApiConsumer.cs b/src/SMAPI.Tests.ModApiConsumer/ApiConsumer.cs index 2c7f9952..285dd259 100644 --- a/src/SMAPI.Tests.ModApiConsumer/ApiConsumer.cs +++ b/src/SMAPI.Tests.ModApiConsumer/ApiConsumer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using SMAPI.Tests.ModApiConsumer.Interfaces; @@ -17,7 +19,7 @@ namespace SMAPI.Tests.ModApiConsumer // act int calls = 0; int lastValue = -1; - api.OnEventRaised += (sender, value) => + api.OnEventRaised += (_, value) => { calls++; lastValue = value; @@ -34,7 +36,7 @@ namespace SMAPI.Tests.ModApiConsumer // act int calls = 0; int lastValue = -1; - api.OnEventRaisedProperty += (sender, value) => + api.OnEventRaisedProperty += (_, value) => { calls++; lastValue = value; diff --git a/src/SMAPI.Tests.ModApiConsumer/Interfaces/ISimpleApi.cs b/src/SMAPI.Tests.ModApiConsumer/Interfaces/ISimpleApi.cs index 7f94e137..23491fd1 100644 --- a/src/SMAPI.Tests.ModApiConsumer/Interfaces/ISimpleApi.cs +++ b/src/SMAPI.Tests.ModApiConsumer/Interfaces/ISimpleApi.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Reflection; diff --git a/src/SMAPI.Tests.ModApiProvider/Framework/BaseApi.cs b/src/SMAPI.Tests.ModApiProvider/Framework/BaseApi.cs index 8092e3e7..b5870baa 100644 --- a/src/SMAPI.Tests.ModApiProvider/Framework/BaseApi.cs +++ b/src/SMAPI.Tests.ModApiProvider/Framework/BaseApi.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace SMAPI.Tests.ModApiProvider.Framework { /// <summary>The base class for <see cref="SimpleApi"/>.</summary> diff --git a/src/SMAPI.Tests.ModApiProvider/Framework/SimpleApi.cs b/src/SMAPI.Tests.ModApiProvider/Framework/SimpleApi.cs index 1100af36..82e902f5 100644 --- a/src/SMAPI.Tests.ModApiProvider/Framework/SimpleApi.cs +++ b/src/SMAPI.Tests.ModApiProvider/Framework/SimpleApi.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Reflection; diff --git a/src/SMAPI.Tests.ModApiProvider/ProviderMod.cs b/src/SMAPI.Tests.ModApiProvider/ProviderMod.cs index c36e1c6d..3fc8d749 100644 --- a/src/SMAPI.Tests.ModApiProvider/ProviderMod.cs +++ b/src/SMAPI.Tests.ModApiProvider/ProviderMod.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Reflection; using SMAPI.Tests.ModApiProvider.Framework; diff --git a/src/SMAPI.Tests/Core/AssetNameTests.cs b/src/SMAPI.Tests/Core/AssetNameTests.cs index 8785aab8..ef8a08ef 100644 --- a/src/SMAPI.Tests/Core/AssetNameTests.cs +++ b/src/SMAPI.Tests/Core/AssetNameTests.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using FluentAssertions; @@ -32,8 +34,7 @@ namespace SMAPI.Tests.Core name = PathUtilities.NormalizeAssetName(name); // act - string calledWithLocale = null; - IAssetName assetName = AssetName.Parse(name, parseLocale: locale => expectedLanguageCode); + IAssetName assetName = AssetName.Parse(name, parseLocale: _ => expectedLanguageCode); // assert assetName.Name.Should() @@ -161,7 +162,7 @@ namespace SMAPI.Tests.Core AssetName name = AssetName.Parse(mainAssetName, _ => null); // assert value is the same for any combination of options - bool result = name.StartsWith(prefix, true, true); + bool result = name.StartsWith(prefix); foreach (bool allowPartialWord in new[] { true, false }) { foreach (bool allowSubfolder in new[] { true, true }) diff --git a/src/SMAPI.Tests/Core/InterfaceProxyTests.cs b/src/SMAPI.Tests/Core/InterfaceProxyTests.cs index 99c1298f..1bf2ed68 100644 --- a/src/SMAPI.Tests/Core/InterfaceProxyTests.cs +++ b/src/SMAPI.Tests/Core/InterfaceProxyTests.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; diff --git a/src/SMAPI.Tests/Core/ModResolverTests.cs b/src/SMAPI.Tests/Core/ModResolverTests.cs index 1755f644..e1b56559 100644 --- a/src/SMAPI.Tests/Core/ModResolverTests.cs +++ b/src/SMAPI.Tests/Core/ModResolverTests.cs @@ -50,11 +50,11 @@ namespace SMAPI.Tests.Core // act IModMetadata[] mods = new ModResolver().ReadManifests(new ModToolkit(), rootFolder, new ModDatabase()).ToArray(); - IModMetadata mod = mods.FirstOrDefault(); + IModMetadata? mod = mods.FirstOrDefault(); // assert Assert.AreEqual(1, mods.Length, 0, $"Expected to find one manifest, found {mods.Length} instead."); - Assert.AreEqual(ModMetadataStatus.Failed, mod.Status, "The mod metadata was not marked failed."); + Assert.AreEqual(ModMetadataStatus.Failed, mod!.Status, "The mod metadata was not marked failed."); Assert.IsNotNull(mod.Error, "The mod metadata did not have an error message set."); } @@ -89,12 +89,12 @@ namespace SMAPI.Tests.Core // act IModMetadata[] mods = new ModResolver().ReadManifests(new ModToolkit(), rootFolder, new ModDatabase()).ToArray(); - IModMetadata mod = mods.FirstOrDefault(); + IModMetadata? mod = mods.FirstOrDefault(); // assert Assert.AreEqual(1, mods.Length, 0, "Expected to find one manifest."); Assert.IsNotNull(mod, "The loaded manifest shouldn't be null."); - Assert.AreEqual(null, mod.DataRecord, "The data record should be null since we didn't provide one."); + Assert.AreEqual(null, mod!.DataRecord, "The data record should be null since we didn't provide one."); Assert.AreEqual(modFolder, mod.DirectoryPath, "The directory path doesn't match."); Assert.AreEqual(null, mod.Error, "The error should be null since parsing should have succeeded."); Assert.AreEqual(ModMetadataStatus.Found, mod.Status, "The status doesn't match."); @@ -123,7 +123,7 @@ namespace SMAPI.Tests.Core [Test(Description = "Assert that validation doesn't fail if there are no mods installed.")] public void ValidateManifests_NoMods_DoesNothing() { - new ModResolver().ValidateManifests(Array.Empty<ModMetadata>(), apiVersion: new SemanticVersion("1.0"), getUpdateUrl: key => null); + new ModResolver().ValidateManifests(Array.Empty<ModMetadata>(), apiVersion: new SemanticVersion("1.0"), getUpdateUrl: _ => null); } [Test(Description = "Assert that validation skips manifests that have already failed without calling any other properties.")] @@ -134,7 +134,7 @@ namespace SMAPI.Tests.Core mock.Setup(p => p.Status).Returns(ModMetadataStatus.Failed); // act - new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: key => null); + new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: _ => null); // assert mock.VerifyGet(p => p.Status, Times.Once, "The validation did not check the manifest status."); @@ -151,7 +151,7 @@ namespace SMAPI.Tests.Core }); // act - new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: key => null); + new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: _ => null); // assert mock.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<ModFailReason>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once, "The validation did not fail the metadata."); @@ -166,7 +166,7 @@ namespace SMAPI.Tests.Core this.SetupMetadataForValidation(mock); // act - new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: key => null); + new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: _ => null); // assert mock.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<ModFailReason>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once, "The validation did not fail the metadata."); @@ -180,7 +180,7 @@ namespace SMAPI.Tests.Core this.SetupMetadataForValidation(mock); // act - new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: key => null); + new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: _ => null); // assert mock.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<ModFailReason>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once, "The validation did not fail the metadata."); @@ -197,7 +197,7 @@ namespace SMAPI.Tests.Core this.SetupMetadataForValidation(mod); // act - new ModResolver().ValidateManifests(new[] { modA.Object, modB.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: key => null); + new ModResolver().ValidateManifests(new[] { modA.Object, modB.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: _ => null); // assert modA.Verify(p => p.SetStatus(ModMetadataStatus.Failed, It.IsAny<ModFailReason>(), It.IsAny<string>(), It.IsAny<string>()), Times.Once, "The validation did not fail the first mod with a unique ID."); @@ -213,7 +213,7 @@ namespace SMAPI.Tests.Core // create DLL string modFolder = Path.Combine(this.GetTempFolderPath(), Guid.NewGuid().ToString("N")); Directory.CreateDirectory(modFolder); - File.WriteAllText(Path.Combine(modFolder, manifest.EntryDll), ""); + File.WriteAllText(Path.Combine(modFolder, manifest.EntryDll!), ""); // arrange Mock<IModMetadata> mock = new Mock<IModMetadata>(MockBehavior.Strict); @@ -223,7 +223,7 @@ namespace SMAPI.Tests.Core mock.Setup(p => p.DirectoryPath).Returns(modFolder); // act - new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: key => null); + new ModResolver().ValidateManifests(new[] { mock.Object }, apiVersion: new SemanticVersion("1.0"), getUpdateUrl: _ => null); // assert // if Moq doesn't throw a method-not-setup exception, the validation didn't override the status. @@ -478,21 +478,20 @@ namespace SMAPI.Tests.Core /// <param name="contentPackForID">The <see cref="IManifest.ContentPackFor"/> value.</param> /// <param name="minimumApiVersion">The <see cref="IManifest.MinimumApiVersion"/> value.</param> /// <param name="dependencies">The <see cref="IManifest.Dependencies"/> value.</param> - private Manifest GetManifest(string id = null, string name = null, string version = null, string entryDll = null, string contentPackForID = null, string minimumApiVersion = null, IManifestDependency[] dependencies = null) + private Manifest GetManifest(string? id = null, string? name = null, string? version = null, string? entryDll = null, string? contentPackForID = null, string? minimumApiVersion = null, IManifestDependency[]? dependencies = null) { - return new Manifest - { - UniqueID = id ?? $"{Sample.String()}.{Sample.String()}", - Name = name ?? id ?? Sample.String(), - Author = Sample.String(), - Description = Sample.String(), - Version = version != null ? new SemanticVersion(version) : new SemanticVersion(Sample.Int(), Sample.Int(), Sample.Int(), Sample.String()), - EntryDll = entryDll ?? $"{Sample.String()}.dll", - ContentPackFor = contentPackForID != null ? new ManifestContentPackFor { UniqueID = contentPackForID } : null, - MinimumApiVersion = minimumApiVersion != null ? new SemanticVersion(minimumApiVersion) : null, - Dependencies = dependencies ?? Array.Empty<IManifestDependency>(), - UpdateKeys = Array.Empty<string>() - }; + return new Manifest( + uniqueId: id ?? $"{Sample.String()}.{Sample.String()}", + name: name ?? id ?? Sample.String(), + author: Sample.String(), + description: Sample.String(), + version: version != null ? new SemanticVersion(version) : new SemanticVersion(Sample.Int(), Sample.Int(), Sample.Int(), Sample.String()), + entryDll: entryDll ?? $"{Sample.String()}.dll", + contentPackFor: contentPackForID != null ? new ManifestContentPackFor(contentPackForID, null) : null, + minimumApiVersion: minimumApiVersion != null ? new SemanticVersion(minimumApiVersion) : null, + dependencies: dependencies ?? Array.Empty<IManifestDependency>(), + updateKeys: Array.Empty<string>() + ); } /// <summary>Get a randomized basic manifest.</summary> @@ -508,7 +507,7 @@ namespace SMAPI.Tests.Core /// <param name="allowStatusChange">Whether the code being tested is allowed to change the mod status.</param> private Mock<IModMetadata> GetMetadata(string uniqueID, string[] dependencies, bool allowStatusChange = false) { - IManifest manifest = this.GetManifest(id: uniqueID, version: "1.0", dependencies: dependencies?.Select(dependencyID => (IManifestDependency)new ManifestDependency(dependencyID, null)).ToArray()); + IManifest manifest = this.GetManifest(id: uniqueID, version: "1.0", dependencies: dependencies?.Select(dependencyID => (IManifestDependency)new ManifestDependency(dependencyID, null as ISemanticVersion)).ToArray()); return this.GetMetadata(manifest, allowStatusChange); } @@ -536,7 +535,7 @@ namespace SMAPI.Tests.Core /// <summary>Set up a mock mod metadata for <see cref="ModResolver.ValidateManifests"/>.</summary> /// <param name="mod">The mock mod metadata.</param> /// <param name="modRecord">The extra metadata about the mod from SMAPI's internal data (if any).</param> - private void SetupMetadataForValidation(Mock<IModMetadata> mod, ModDataRecordVersionedFields modRecord = null) + private void SetupMetadataForValidation(Mock<IModMetadata> mod, ModDataRecordVersionedFields? modRecord = null) { mod.Setup(p => p.Status).Returns(ModMetadataStatus.Found); mod.Setup(p => p.DataRecord).Returns(() => null); diff --git a/src/SMAPI.Tests/Core/TranslationTests.cs b/src/SMAPI.Tests/Core/TranslationTests.cs index 457f9fad..f8f0e315 100644 --- a/src/SMAPI.Tests/Core/TranslationTests.cs +++ b/src/SMAPI.Tests/Core/TranslationTests.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -116,7 +118,7 @@ namespace SMAPI.Tests.Core public void Translation_ToString([ValueSource(nameof(TranslationTests.Samples))] string text) { // act - Translation translation = new Translation("pt-BR", "key", text); + Translation translation = new("pt-BR", "key", text); // assert if (translation.HasValue()) @@ -129,7 +131,7 @@ namespace SMAPI.Tests.Core public void Translation_ImplicitStringConversion([ValueSource(nameof(TranslationTests.Samples))] string text) { // act - Translation translation = new Translation("pt-BR", "key", text); + Translation translation = new("pt-BR", "key", text); // assert if (translation.HasValue()) @@ -182,7 +184,7 @@ namespace SMAPI.Tests.Core string expected = $"{start} tokens are properly replaced (including {middle} {middle}) {end}"; // act - Translation translation = new Translation("pt-BR", "key", input); + Translation translation = new("pt-BR", "key", input); switch (structure) { case "anonymous object": diff --git a/src/SMAPI.Tests/Sample.cs b/src/SMAPI.Tests/Sample.cs index f4f0d88e..6d4339ca 100644 --- a/src/SMAPI.Tests/Sample.cs +++ b/src/SMAPI.Tests/Sample.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace SMAPI.Tests @@ -9,7 +11,7 @@ namespace SMAPI.Tests ** Fields *********/ /// <summary>A random number generator.</summary> - private static readonly Random Random = new Random(); + private static readonly Random Random = new(); /********* diff --git a/src/SMAPI.Tests/Utilities/KeybindListTests.cs b/src/SMAPI.Tests/Utilities/KeybindListTests.cs index 0bd6ec17..f5c156c4 100644 --- a/src/SMAPI.Tests/Utilities/KeybindListTests.cs +++ b/src/SMAPI.Tests/Utilities/KeybindListTests.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using NUnit.Framework; diff --git a/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs b/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs index ab4c2618..ae2cc6ce 100644 --- a/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs +++ b/src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.IO; using NUnit.Framework; using StardewModdingAPI.Toolkit.Utilities; @@ -14,7 +16,7 @@ namespace SMAPI.Tests.Utilities /// <summary>Sample paths used in unit tests.</summary> public static readonly SamplePath[] SamplePaths = { // Windows absolute path - new SamplePath + new() { OriginalPath = @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley", @@ -26,7 +28,7 @@ namespace SMAPI.Tests.Utilities }, // Windows absolute path (with trailing slash) - new SamplePath + new() { OriginalPath = @"C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley\", @@ -38,7 +40,7 @@ namespace SMAPI.Tests.Utilities }, // Windows relative path - new SamplePath + new() { OriginalPath = @"Content\Characters\Dialogue\Abigail", @@ -50,7 +52,7 @@ namespace SMAPI.Tests.Utilities }, // Windows relative path (with directory climbing) - new SamplePath + new() { OriginalPath = @"..\..\Content", @@ -62,7 +64,7 @@ namespace SMAPI.Tests.Utilities }, // Windows UNC path - new SamplePath + new() { OriginalPath = @"\\unc\path", @@ -74,7 +76,7 @@ namespace SMAPI.Tests.Utilities }, // Linux absolute path - new SamplePath + new() { OriginalPath = @"/home/.steam/steam/steamapps/common/Stardew Valley", @@ -86,7 +88,7 @@ namespace SMAPI.Tests.Utilities }, // Linux absolute path (with trailing slash) - new SamplePath + new() { OriginalPath = @"/home/.steam/steam/steamapps/common/Stardew Valley/", @@ -98,7 +100,7 @@ namespace SMAPI.Tests.Utilities }, // Linux absolute path (with ~) - new SamplePath + new() { OriginalPath = @"~/.steam/steam/steamapps/common/Stardew Valley", @@ -110,7 +112,7 @@ namespace SMAPI.Tests.Utilities }, // Linux relative path - new SamplePath + new() { OriginalPath = @"Content/Characters/Dialogue/Abigail", @@ -122,7 +124,7 @@ namespace SMAPI.Tests.Utilities }, // Linux relative path (with directory climbing) - new SamplePath + new() { OriginalPath = @"../../Content", @@ -134,7 +136,7 @@ namespace SMAPI.Tests.Utilities }, // Mixed directory separators - new SamplePath + new() { OriginalPath = @"C:\some/mixed\path/separators", diff --git a/src/SMAPI.Tests/Utilities/SDateTests.cs b/src/SMAPI.Tests/Utilities/SDateTests.cs index 374f4921..a4a36828 100644 --- a/src/SMAPI.Tests/Utilities/SDateTests.cs +++ b/src/SMAPI.Tests/Utilities/SDateTests.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -61,7 +63,7 @@ namespace SMAPI.Tests.Utilities public void Constructor_SetsExpectedValues([ValueSource(nameof(SDateTests.SampleSeasonValues))] string season, [ValueSource(nameof(SDateTests.ValidDays))] int day, [Values(1, 2, 100)] int year) { // act - SDate date = new SDate(day, season, year); + SDate date = new(day, season, year); // assert Assert.AreEqual(day, date.Day); @@ -254,7 +256,7 @@ namespace SMAPI.Tests.Utilities { foreach (int day in SDateTests.ValidDays) { - SDate date = new SDate(day, season, year); + SDate date = new(day, season, year); int hash = date.GetHashCode(); if (hashes.TryGetValue(hash, out SDate otherDate)) Assert.Fail($"Received identical hash code {hash} for dates {otherDate} and {date}."); diff --git a/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs b/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs index ac4ef39b..fedadba6 100644 --- a/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs +++ b/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs @@ -61,10 +61,10 @@ namespace SMAPI.Tests.Utilities [TestCase("apple")] [TestCase("-apple")] [TestCase("-5")] - public void Constructor_FromString_WithInvalidValues(string input) + public void Constructor_FromString_WithInvalidValues(string? input) { if (input == null) - this.AssertAndLogException<ArgumentNullException>(() => new SemanticVersion(input)); + this.AssertAndLogException<ArgumentNullException>(() => new SemanticVersion(input!)); else this.AssertAndLogException<FormatException>(() => new SemanticVersion(input)); } @@ -91,7 +91,7 @@ namespace SMAPI.Tests.Utilities [TestCase("1.2.3.4-some-tag.4 ")] public void Constructor_FromString_Standard_DisallowsNonStandardVersion(string input) { - Assert.Throws<FormatException>(() => new SemanticVersion(input)); + Assert.Throws<FormatException>(() => _ = new SemanticVersion(input)); } /// <summary>Assert the parsed version when constructed from standard parts.</summary> @@ -110,7 +110,7 @@ namespace SMAPI.Tests.Utilities [TestCase(1, 2, 3, "some-tag.4 ", null, ExpectedResult = "1.2.3-some-tag.4")] [TestCase(1, 2, 3, "some-tag.4 ", "build.004", ExpectedResult = "1.2.3-some-tag.4+build.004")] [TestCase(1, 2, 0, null, "3.4.5-build.004", ExpectedResult = "1.2.0+3.4.5-build.004")] - public string Constructor_FromParts(int major, int minor, int patch, string prerelease, string build) + public string Constructor_FromParts(int major, int minor, int patch, string? prerelease, string? build) { // act ISemanticVersion version = new SemanticVersion(major, minor, patch, prerelease, build); @@ -217,11 +217,16 @@ namespace SMAPI.Tests.Utilities [TestCase("1.0-beta.2", "1.0-beta.1", ExpectedResult = 1)] [TestCase("1.0-beta.10", "1.0-beta.2", ExpectedResult = 1)] [TestCase("1.0-beta-10", "1.0-beta-2", ExpectedResult = 1)] - public int CompareTo(string versionStrA, string versionStrB) + + // null + [TestCase("1.0.0", null, ExpectedResult = 1)] // null is always less than any value per CompareTo remarks + public int CompareTo(string versionStrA, string? versionStrB) { // arrange ISemanticVersion versionA = new SemanticVersion(versionStrA); - ISemanticVersion versionB = new SemanticVersion(versionStrB); + ISemanticVersion? versionB = versionStrB != null + ? new SemanticVersion(versionStrB) + : null; // assert return versionA.CompareTo(versionB); @@ -260,14 +265,19 @@ namespace SMAPI.Tests.Utilities [TestCase("1.0-beta.2", "1.0-beta.1", ExpectedResult = false)] [TestCase("1.0-beta.10", "1.0-beta.2", ExpectedResult = false)] [TestCase("1.0-beta-10", "1.0-beta-2", ExpectedResult = false)] - public bool IsOlderThan(string versionStrA, string versionStrB) + + // null + [TestCase("1.0.0", null, ExpectedResult = false)] // null is always less than any value per CompareTo remarks + public bool IsOlderThan(string versionStrA, string? versionStrB) { // arrange ISemanticVersion versionA = new SemanticVersion(versionStrA); - ISemanticVersion versionB = new SemanticVersion(versionStrB); + ISemanticVersion? versionB = versionStrB != null + ? new SemanticVersion(versionStrB) + : null; // assert - Assert.AreEqual(versionA.IsOlderThan(versionB), versionA.IsOlderThan(versionB.ToString()), "The two signatures returned different results."); + Assert.AreEqual(versionA.IsOlderThan(versionB), versionA.IsOlderThan(versionB?.ToString()), "The two signatures returned different results."); return versionA.IsOlderThan(versionB); } @@ -304,14 +314,19 @@ namespace SMAPI.Tests.Utilities [TestCase("1.0-beta.2", "1.0-beta.1", ExpectedResult = true)] [TestCase("1.0-beta.10", "1.0-beta.2", ExpectedResult = true)] [TestCase("1.0-beta-10", "1.0-beta-2", ExpectedResult = true)] - public bool IsNewerThan(string versionStrA, string versionStrB) + + // null + [TestCase("1.0.0", null, ExpectedResult = true)] // null is always less than any value per CompareTo remarks + public bool IsNewerThan(string versionStrA, string? versionStrB) { // arrange ISemanticVersion versionA = new SemanticVersion(versionStrA); - ISemanticVersion versionB = new SemanticVersion(versionStrB); + ISemanticVersion? versionB = versionStrB != null + ? new SemanticVersion(versionStrB) + : null; // assert - Assert.AreEqual(versionA.IsNewerThan(versionB), versionA.IsNewerThan(versionB.ToString()), "The two signatures returned different results."); + Assert.AreEqual(versionA.IsNewerThan(versionB), versionA.IsNewerThan(versionB?.ToString()), "The two signatures returned different results."); return versionA.IsNewerThan(versionB); } @@ -322,7 +337,7 @@ namespace SMAPI.Tests.Utilities /// <param name="versionStr">The main version.</param> /// <param name="lowerStr">The lower version number.</param> /// <param name="upperStr">The upper version number.</param> - [Test(Description = "Assert that version.IsNewerThan returns the expected value.")] + [Test(Description = "Assert that version.IsBetween returns the expected value.")] // is between [TestCase("0.5.7-beta.3", "0.5.7-beta.3", "0.5.7-beta.3", ExpectedResult = true)] [TestCase("1.0", "1.0", "1.1", ExpectedResult = true)] @@ -330,6 +345,7 @@ namespace SMAPI.Tests.Utilities [TestCase("1.0", "0.5", "1.1", ExpectedResult = true)] [TestCase("1.0-beta.2", "1.0-beta.1", "1.0-beta.3", ExpectedResult = true)] [TestCase("1.0-beta-2", "1.0-beta-1", "1.0-beta-3", ExpectedResult = true)] + [TestCase("1.0.0", null, "1.0.0", ExpectedResult = true)] // null is always less than any value per CompareTo remarks // is not between [TestCase("1.0-beta", "1.0", "1.1", ExpectedResult = false)] @@ -337,15 +353,20 @@ namespace SMAPI.Tests.Utilities [TestCase("1.0-beta.2", "1.1", "1.0", ExpectedResult = false)] [TestCase("1.0-beta.2", "1.0-beta.10", "1.0-beta.3", ExpectedResult = false)] [TestCase("1.0-beta-2", "1.0-beta-10", "1.0-beta-3", ExpectedResult = false)] - public bool IsBetween(string versionStr, string lowerStr, string upperStr) + [TestCase("1.0.0", "1.0.0", null, ExpectedResult = false)] // null is always less than any value per CompareTo remarks + public bool IsBetween(string versionStr, string? lowerStr, string? upperStr) { // arrange - ISemanticVersion lower = new SemanticVersion(lowerStr); - ISemanticVersion upper = new SemanticVersion(upperStr); + ISemanticVersion? lower = lowerStr != null + ? new SemanticVersion(lowerStr) + : null; + ISemanticVersion? upper = upperStr != null + ? new SemanticVersion(upperStr) + : null; ISemanticVersion version = new SemanticVersion(versionStr); // assert - Assert.AreEqual(version.IsBetween(lower, upper), version.IsBetween(lower.ToString(), upper.ToString()), "The two signatures returned different results."); + Assert.AreEqual(version.IsBetween(lower, upper), version.IsBetween(lower?.ToString(), upper?.ToString()), "The two signatures returned different results."); return version.IsBetween(lower, upper); } @@ -395,7 +416,7 @@ namespace SMAPI.Tests.Utilities public void GameVersion(string versionStr) { // act - GameVersion version = new GameVersion(versionStr); + GameVersion version = new(versionStr); // assert Assert.AreEqual(versionStr, version.ToString(), "The game version did not round-trip to the same value."); @@ -413,7 +434,7 @@ namespace SMAPI.Tests.Utilities /// <param name="prerelease">The prerelease tag.</param> /// <param name="build">The build metadata.</param> /// <param name="nonStandard">Whether the version should be marked as non-standard.</param> - private void AssertParts(ISemanticVersion version, int major, int minor, int patch, string prerelease, string build, bool nonStandard) + private void AssertParts(ISemanticVersion version, int major, int minor, int patch, string? prerelease, string? build, bool nonStandard) { Assert.AreEqual(major, version.MajorVersion, "The major version doesn't match."); Assert.AreEqual(minor, version.MinorVersion, "The minor version doesn't match."); @@ -426,9 +447,8 @@ namespace SMAPI.Tests.Utilities /// <summary>Assert that the expected exception type is thrown, and log the action output and thrown exception.</summary> /// <typeparam name="T">The expected exception type.</typeparam> /// <param name="action">The action which may throw the exception.</param> - /// <param name="message">The message to log if the expected exception isn't thrown.</param> [SuppressMessage("ReSharper", "UnusedParameter.Local", Justification = "The message argument is deliberately only used in precondition checks since this is an assertion method.")] - private void AssertAndLogException<T>(Func<object> action, string message = null) + private void AssertAndLogException<T>(Func<object> action) where T : Exception { this.AssertAndLogException<T>(() => @@ -443,7 +463,7 @@ namespace SMAPI.Tests.Utilities /// <param name="action">The action which may throw the exception.</param> /// <param name="message">The message to log if the expected exception isn't thrown.</param> [SuppressMessage("ReSharper", "UnusedParameter.Local", Justification = "The message argument is deliberately only used in precondition checks since this is an assertion method.")] - private void AssertAndLogException<T>(Action action, string message = null) + private void AssertAndLogException<T>(Action action, string? message = null) where T : Exception { try @@ -455,7 +475,7 @@ namespace SMAPI.Tests.Utilities TestContext.WriteLine($"Exception thrown:\n{ex}"); return; } - catch (Exception ex) when (!(ex is AssertionException)) + catch (Exception ex) when (ex is not AssertionException) { TestContext.WriteLine($"Exception thrown:\n{ex}"); Assert.Fail(message ?? $"Didn't throw the expected exception; expected {typeof(T).FullName}, got {ex.GetType().FullName}."); diff --git a/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs b/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs index b896b09c..7695fbf8 100644 --- a/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs +++ b/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs @@ -1,5 +1,6 @@ +#nullable disable + using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using StardewModdingAPI; using StardewModdingAPI.Toolkit.Framework.Clients.Wiki; diff --git a/src/SMAPI.Toolkit.CoreInterfaces/IManifest.cs b/src/SMAPI.Toolkit.CoreInterfaces/IManifest.cs index 7375f005..ee6cc0b6 100644 --- a/src/SMAPI.Toolkit.CoreInterfaces/IManifest.cs +++ b/src/SMAPI.Toolkit.CoreInterfaces/IManifest.cs @@ -21,16 +21,16 @@ namespace StardewModdingAPI ISemanticVersion Version { get; } /// <summary>The minimum SMAPI version required by this mod, if any.</summary> - ISemanticVersion MinimumApiVersion { get; } + ISemanticVersion? MinimumApiVersion { get; } /// <summary>The unique mod ID.</summary> string UniqueID { get; } /// <summary>The name of the DLL in the directory that has the <c>Entry</c> method. Mutually exclusive with <see cref="ContentPackFor"/>.</summary> - string EntryDll { get; } + string? EntryDll { get; } /// <summary>The mod which will read this as a content pack. Mutually exclusive with <see cref="EntryDll"/>.</summary> - IManifestContentPackFor ContentPackFor { get; } + IManifestContentPackFor? ContentPackFor { get; } /// <summary>The other mods that must be loaded before this mod.</summary> IManifestDependency[] Dependencies { get; } diff --git a/src/SMAPI.Toolkit.CoreInterfaces/IManifestContentPackFor.cs b/src/SMAPI.Toolkit.CoreInterfaces/IManifestContentPackFor.cs index f05a3873..52ac8f1c 100644 --- a/src/SMAPI.Toolkit.CoreInterfaces/IManifestContentPackFor.cs +++ b/src/SMAPI.Toolkit.CoreInterfaces/IManifestContentPackFor.cs @@ -7,6 +7,6 @@ namespace StardewModdingAPI string UniqueID { get; } /// <summary>The minimum required version (if any).</summary> - ISemanticVersion MinimumVersion { get; } + ISemanticVersion? MinimumVersion { get; } } } diff --git a/src/SMAPI.Toolkit.CoreInterfaces/IManifestDependency.cs b/src/SMAPI.Toolkit.CoreInterfaces/IManifestDependency.cs index e86cd1f4..58425eb2 100644 --- a/src/SMAPI.Toolkit.CoreInterfaces/IManifestDependency.cs +++ b/src/SMAPI.Toolkit.CoreInterfaces/IManifestDependency.cs @@ -10,7 +10,7 @@ namespace StardewModdingAPI string UniqueID { get; } /// <summary>The minimum required version (if any).</summary> - ISemanticVersion MinimumVersion { get; } + ISemanticVersion? MinimumVersion { get; } /// <summary>Whether the dependency must be installed to use the mod.</summary> bool IsRequired { get; } diff --git a/src/SMAPI.Toolkit.CoreInterfaces/ISemanticVersion.cs b/src/SMAPI.Toolkit.CoreInterfaces/ISemanticVersion.cs index b228b2d1..7998272f 100644 --- a/src/SMAPI.Toolkit.CoreInterfaces/ISemanticVersion.cs +++ b/src/SMAPI.Toolkit.CoreInterfaces/ISemanticVersion.cs @@ -18,10 +18,10 @@ namespace StardewModdingAPI int PatchVersion { get; } /// <summary>An optional prerelease tag.</summary> - string PrereleaseTag { get; } + string? PrereleaseTag { get; } /// <summary>Optional build metadata. This is ignored when determining version precedence.</summary> - string BuildMetadata { get; } + string? BuildMetadata { get; } /********* @@ -32,32 +32,38 @@ namespace StardewModdingAPI /// <summary>Get whether this version is older than the specified version.</summary> /// <param name="other">The version to compare with this instance.</param> - bool IsOlderThan(ISemanticVersion other); + /// <remarks>Although the <paramref name="other"/> parameter is nullable, it isn't optional. A <c>null</c> version is considered earlier than every possible valid version, so passing <c>null</c> to <paramref name="other"/> will always return false.</remarks> + bool IsOlderThan(ISemanticVersion? other); /// <summary>Get whether this version is older than the specified version.</summary> - /// <param name="other">The version to compare with this instance.</param> + /// <param name="other">The version to compare with this instance. A null value is never older.</param> /// <exception cref="FormatException">The specified version is not a valid semantic version.</exception> - bool IsOlderThan(string other); + /// <remarks>Although the <paramref name="other"/> parameter is nullable, it isn't optional. A <c>null</c> version is considered earlier than every possible valid version, so passing <c>null</c> to <paramref name="other"/> will always return false.</remarks> + bool IsOlderThan(string? other); /// <summary>Get whether this version is newer than the specified version.</summary> - /// <param name="other">The version to compare with this instance.</param> - bool IsNewerThan(ISemanticVersion other); + /// <param name="other">The version to compare with this instance. A null value is always older.</param> + /// <remarks>Although the <paramref name="other"/> parameter is nullable, it isn't optional. A <c>null</c> version is considered earlier than every possible valid version, so passing <c>null</c> to <paramref name="other"/> will always return true.</remarks> + bool IsNewerThan(ISemanticVersion? other); /// <summary>Get whether this version is newer than the specified version.</summary> - /// <param name="other">The version to compare with this instance.</param> + /// <param name="other">The version to compare with this instance. A null value is always older.</param> /// <exception cref="FormatException">The specified version is not a valid semantic version.</exception> - bool IsNewerThan(string other); + /// <remarks>Although the <paramref name="other"/> parameter is nullable, it isn't optional. A <c>null</c> version is considered earlier than every possible valid version, so passing <c>null</c> to <paramref name="other"/> will always return true.</remarks> + bool IsNewerThan(string? other); /// <summary>Get whether this version is between two specified versions (inclusively).</summary> - /// <param name="min">The minimum version.</param> - /// <param name="max">The maximum version.</param> - bool IsBetween(ISemanticVersion min, ISemanticVersion max); + /// <param name="min">The minimum version. A null value is always older.</param> + /// <param name="max">The maximum version. A null value is never newer.</param> + /// <remarks>Although the <paramref name="min"/> and <paramref name="max"/> parameters are nullable, they are not optional. A <c>null</c> version is considered earlier than every possible valid version. For example, passing <c>null</c> to <paramref name="max"/> will always return false, since no valid version can be earlier than <c>null</c>.</remarks> + bool IsBetween(ISemanticVersion? min, ISemanticVersion? max); /// <summary>Get whether this version is between two specified versions (inclusively).</summary> - /// <param name="min">The minimum version.</param> - /// <param name="max">The maximum version.</param> + /// <param name="min">The minimum version. A null value is always older.</param> + /// <param name="max">The maximum version. A null value is never newer.</param> /// <exception cref="FormatException">One of the specified versions is not a valid semantic version.</exception> - bool IsBetween(string min, string max); + /// <remarks>Although the <paramref name="min"/> and <paramref name="max"/> parameters are nullable, they are not optional. A <c>null</c> version is considered earlier than every possible valid version. For example, passing <c>null</c> to <paramref name="max"/> will always return false, since no valid version can be earlier than <c>null</c>.</remarks> + bool IsBetween(string? min, string? max); /// <summary>Get a string representation of the version.</summary> string ToString(); diff --git a/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs index 0115fbf3..d5ca2034 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi diff --git a/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryVersionModel.cs b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryVersionModel.cs index 188db31d..9aac7fd3 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryVersionModel.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryVersionModel.cs @@ -1,3 +1,5 @@ +#nullable disable + using Newtonsoft.Json; using StardewModdingAPI.Toolkit.Serialization.Converters; diff --git a/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs index 0fa4a74d..eb54ec78 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModExtendedMetadataModel.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModSearchEntryModel.cs b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModSearchEntryModel.cs index 404d4618..8fe8fa2a 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModSearchEntryModel.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModSearchEntryModel.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi diff --git a/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModSearchModel.cs b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModSearchModel.cs index 73698173..393391f7 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModSearchModel.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/WebApi/ModSearchModel.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Linq; using StardewModdingAPI.Toolkit.Utilities; diff --git a/src/SMAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs b/src/SMAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs index c2d906a0..56acb768 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -62,9 +64,9 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.WebApi private TResult Post<TBody, TResult>(string url, TBody content) { // note: avoid HttpClient for macOS compatibility - using WebClient client = new WebClient(); + using WebClient client = new(); - Uri fullUrl = new Uri(this.BaseUrl, url); + Uri fullUrl = new(this.BaseUrl, url); string data = JsonConvert.SerializeObject(content); client.Headers["Content-Type"] = "application/json"; diff --git a/src/SMAPI.Toolkit/Framework/Clients/Wiki/ChangeDescriptor.cs b/src/SMAPI.Toolkit/Framework/Clients/Wiki/ChangeDescriptor.cs index 2ed255c8..910bf793 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/Wiki/ChangeDescriptor.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/Wiki/ChangeDescriptor.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Collections.ObjectModel; diff --git a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs index 0f5a0ec3..86c3bd75 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -65,7 +67,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki foreach (var entry in this.ParseOverrideEntries(modNodes)) { - if (entry.Ids?.Any() != true || !entry.HasChanges) + if (entry.Ids.Any() != true || !entry.HasChanges) continue; foreach (string id in entry.Ids) @@ -127,7 +129,7 @@ namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki string pullRequestUrl = this.GetAttribute(node, "data-pr"); // parse stable compatibility - WikiCompatibilityInfo compatibility = new WikiCompatibilityInfo + WikiCompatibilityInfo compatibility = new() { Status = this.GetAttributeAsEnum<WikiCompatibilityStatus>(node, "data-status") ?? WikiCompatibilityStatus.Ok, BrokeIn = this.GetAttribute(node, "data-broke-in"), diff --git a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityInfo.cs b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityInfo.cs index 204acd2b..30e76d04 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityInfo.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki { /// <summary>Compatibility info for a mod.</summary> diff --git a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityStatus.cs b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityStatus.cs index 5cdf489f..2c222b71 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityStatus.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityStatus.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki { /// <summary>The compatibility status for a mod.</summary> diff --git a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiDataOverrideEntry.cs b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiDataOverrideEntry.cs index 03c0d214..a6f5a88f 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiDataOverrideEntry.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiDataOverrideEntry.cs @@ -1,7 +1,5 @@ using System; -#nullable enable - namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki { /// <summary>The data overrides to apply to matching mods.</summary> diff --git a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs index 4e0104da..91943ff9 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki { /// <summary>A mod entry in the wiki list.</summary> diff --git a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModList.cs b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModList.cs index 0d614f28..1787197a 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModList.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModList.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Toolkit.Framework.Clients.Wiki { /// <summary>Metadata from the wiki's mod compatibility list.</summary> diff --git a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs index 8d4198de..4f872f1c 100644 --- a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs +++ b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs @@ -39,13 +39,13 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning IEnumerable<string> paths = this .GetCustomInstallPaths() .Concat(this.GetDefaultInstallPaths()) - .Select(PathUtilities.NormalizePath) + .Select(path => PathUtilities.NormalizePath(path)) .Distinct(StringComparer.OrdinalIgnoreCase); // yield valid folders foreach (string path in paths) { - DirectoryInfo folder = new DirectoryInfo(path); + DirectoryInfo folder = new(path); if (this.LooksLikeGameFolder(folder)) yield return folder; } @@ -78,10 +78,12 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning return GameFolderType.NoGameFound; // get assembly version - Version version; + Version? version; try { version = AssemblyName.GetAssemblyName(executable.FullName).Version; + if (version == null) + return GameFolderType.InvalidUnknown; } catch { @@ -121,7 +123,7 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning case Platform.Linux: case Platform.Mac: { - string home = Environment.GetEnvironmentVariable("HOME"); + string home = Environment.GetEnvironmentVariable("HOME")!; // Linux yield return $"{home}/GOG Games/Stardew Valley/game"; @@ -146,13 +148,13 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning }; foreach (var pair in registryKeys) { - string path = this.GetLocalMachineRegistryValue(pair.Key, pair.Value); + string? path = this.GetLocalMachineRegistryValue(pair.Key, pair.Value); if (!string.IsNullOrWhiteSpace(path)) yield return path; } // via Steam library path - string steamPath = this.GetCurrentUserRegistryValue(@"Software\Valve\Steam", "SteamPath"); + string? steamPath = this.GetCurrentUserRegistryValue(@"Software\Valve\Steam", "SteamPath"); if (steamPath != null) yield return Path.Combine(steamPath.Replace('/', '\\'), @"steamapps\common\Stardew Valley"); #endif @@ -186,12 +188,12 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning private IEnumerable<string> GetCustomInstallPaths() { // get home path - string homePath = Environment.GetEnvironmentVariable(this.Platform == Platform.Windows ? "USERPROFILE" : "HOME"); + string homePath = Environment.GetEnvironmentVariable(this.Platform == Platform.Windows ? "USERPROFILE" : "HOME")!; if (string.IsNullOrWhiteSpace(homePath)) yield break; // get targets file - FileInfo file = new FileInfo(Path.Combine(homePath, "stardewvalley.targets")); + FileInfo file = new(Path.Combine(homePath, "stardewvalley.targets")); if (!file.Exists) yield break; @@ -208,7 +210,7 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning } // get install path - XElement element = root.XPathSelectElement("//*[local-name() = 'GamePath']"); // can't use '//GamePath' due to the default namespace + XElement? element = root.XPathSelectElement("//*[local-name() = 'GamePath']"); // can't use '//GamePath' due to the default namespace if (!string.IsNullOrWhiteSpace(element?.Value)) yield return element.Value.Trim(); } @@ -217,27 +219,27 @@ namespace StardewModdingAPI.Toolkit.Framework.GameScanning /// <summary>Get the value of a key in the Windows HKLM registry.</summary> /// <param name="key">The full path of the registry key relative to HKLM.</param> /// <param name="name">The name of the value.</param> - private string GetLocalMachineRegistryValue(string key, string name) + private string? GetLocalMachineRegistryValue(string key, string name) { RegistryKey localMachine = Environment.Is64BitOperatingSystem ? RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64) : Registry.LocalMachine; - RegistryKey openKey = localMachine.OpenSubKey(key); + RegistryKey? openKey = localMachine.OpenSubKey(key); if (openKey == null) return null; using (openKey) - return (string)openKey.GetValue(name); + return (string?)openKey.GetValue(name); } /// <summary>Get the value of a key in the Windows HKCU registry.</summary> /// <param name="key">The full path of the registry key relative to HKCU.</param> /// <param name="name">The name of the value.</param> - private string GetCurrentUserRegistryValue(string key, string name) + private string? GetCurrentUserRegistryValue(string key, string name) { - RegistryKey currentuser = Environment.Is64BitOperatingSystem ? RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64) : Registry.CurrentUser; - RegistryKey openKey = currentuser.OpenSubKey(key); + RegistryKey currentUser = Environment.Is64BitOperatingSystem ? RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64) : Registry.CurrentUser; + RegistryKey? openKey = currentUser.OpenSubKey(key); if (openKey == null) return null; using (openKey) - return (string)openKey.GetValue(name); + return (string?)openKey.GetValue(name); } #endif } diff --git a/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs b/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs index 8b6eb5fb..6978567e 100644 --- a/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs +++ b/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs @@ -57,11 +57,13 @@ namespace StardewModdingAPI.Toolkit.Framework #if SMAPI_FOR_WINDOWS try { - return new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem") + string? result = new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem") .Get() .Cast<ManagementObject>() .Select(entry => entry.GetPropertyValue("Caption").ToString()) .FirstOrDefault(); + + return result ?? "Windows"; } catch { } #endif @@ -98,7 +100,7 @@ namespace StardewModdingAPI.Toolkit.Framework /// </remarks> private static bool IsRunningAndroid() { - using Process process = new Process + using Process process = new() { StartInfo = { @@ -135,7 +137,7 @@ namespace StardewModdingAPI.Toolkit.Framework buffer = Marshal.AllocHGlobal(8192); if (LowLevelEnvironmentUtility.uname(buffer) == 0) { - string os = Marshal.PtrToStringAnsi(buffer); + string? os = Marshal.PtrToStringAnsi(buffer); return os == "Darwin"; } return false; diff --git a/src/SMAPI.Toolkit/Framework/ModData/MetadataModel.cs b/src/SMAPI.Toolkit/Framework/ModData/MetadataModel.cs index ef6d4dd9..3fa70615 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/MetadataModel.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/MetadataModel.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; namespace StardewModdingAPI.Toolkit.Framework.ModData diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs index b02be3e4..46cb81e1 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Linq; namespace StardewModdingAPI.Toolkit.Framework.ModData diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDataModel.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDataModel.cs index 2167d3e5..4d96a555 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModDataModel.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModDataModel.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs index 5dd32acf..4c09e1ba 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -82,7 +84,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData /// <param name="manifest">The manifest to match.</param> public ModDataRecordVersionedFields GetVersionedFields(IManifest manifest) { - ModDataRecordVersionedFields parsed = new ModDataRecordVersionedFields { DisplayName = this.DisplayName, DataRecord = this }; + ModDataRecordVersionedFields parsed = new() { DisplayName = this.DisplayName, DataRecord = this }; foreach (ModDataField field in this.Fields.Where(field => field.IsMatch(manifest))) { switch (field.Key) diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDataRecordVersionedFields.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDataRecordVersionedFields.cs index 5aaabd51..b599b343 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModDataRecordVersionedFields.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModDataRecordVersionedFields.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Toolkit.Framework.ModData { /// <summary>The versioned fields from a <see cref="ModDataRecord"/> for a specific manifest.</summary> diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDatabase.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDatabase.cs index 5b7e2a02..a5237334 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModDatabase.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModDatabase.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -22,7 +24,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData *********/ /// <summary>Construct an empty instance.</summary> public ModDatabase() - : this(Array.Empty<ModDataRecord>(), key => null) { } + : this(Array.Empty<ModDataRecord>(), _ => null) { } /// <summary>Construct an instance.</summary> /// <param name="records">The underlying mod data records indexed by default display name.</param> diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs index 825b98e5..da2a3c85 100644 --- a/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs +++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs @@ -22,13 +22,13 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning public ModType Type { get; } /// <summary>The mod manifest.</summary> - public Manifest Manifest { get; } + public Manifest? Manifest { get; } /// <summary>The error which occurred parsing the manifest, if any.</summary> public ModParseError ManifestParseError { get; set; } /// <summary>A human-readable message for the <see cref="ManifestParseError"/>, if any.</summary> - public string ManifestParseErrorText { get; set; } + public string? ManifestParseErrorText { get; set; } /********* @@ -49,7 +49,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning /// <param name="manifest">The mod manifest.</param> /// <param name="manifestParseError">The error which occurred parsing the manifest, if any.</param> /// <param name="manifestParseErrorText">A human-readable message for the <paramref name="manifestParseError"/>, if any.</param> - public ModFolder(DirectoryInfo root, DirectoryInfo directory, ModType type, Manifest manifest, ModParseError manifestParseError, string manifestParseErrorText) + public ModFolder(DirectoryInfo root, DirectoryInfo directory, ModType type, Manifest? manifest, ModParseError manifestParseError, string? manifestParseErrorText) { // save info this.Directory = directory; @@ -59,9 +59,9 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning this.ManifestParseErrorText = manifestParseErrorText; // set display name - this.DisplayName = manifest?.Name; - if (string.IsNullOrWhiteSpace(this.DisplayName)) - this.DisplayName = PathUtilities.GetRelativePath(root.FullName, directory.FullName); + this.DisplayName = !string.IsNullOrWhiteSpace(manifest?.Name) + ? manifest.Name + : PathUtilities.GetRelativePath(root.FullName, directory.FullName); } /// <summary>Get the update keys for a mod.</summary> diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs index e6105f9c..2af30092 100644 --- a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs +++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs @@ -18,7 +18,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning private readonly JsonHelper JsonHelper; /// <summary>A list of filesystem entry names to ignore when checking whether a folder should be treated as a mod.</summary> - private readonly HashSet<Regex> IgnoreFilesystemNames = new HashSet<Regex> + private readonly HashSet<Regex> IgnoreFilesystemNames = new() { new Regex(@"^__folder_managed_by_vortex$", RegexOptions.Compiled | RegexOptions.IgnoreCase), // Vortex mod manager new Regex(@"(?:^\._|^\.DS_Store$|^__MACOSX$|^mcs$)", RegexOptions.Compiled | RegexOptions.IgnoreCase), // macOS @@ -26,7 +26,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning }; /// <summary>A list of file extensions to ignore when searching for mod files.</summary> - private readonly HashSet<string> IgnoreFileExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + private readonly HashSet<string> IgnoreFileExtensions = new(StringComparer.OrdinalIgnoreCase) { // text ".doc", @@ -60,7 +60,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning }; /// <summary>The extensions for packed content files.</summary> - private readonly HashSet<string> StrictXnbModExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + private readonly HashSet<string> StrictXnbModExtensions = new(StringComparer.OrdinalIgnoreCase) { ".xgs", ".xnb", @@ -69,7 +69,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning }; /// <summary>The extensions for files which an XNB mod may contain, in addition to <see cref="StrictXnbModExtensions"/>.</summary> - private readonly HashSet<string> PotentialXnbModExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + private readonly HashSet<string> PotentialXnbModExtensions = new(StringComparer.OrdinalIgnoreCase) { ".json", ".yaml" @@ -96,7 +96,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning /// <param name="rootPath">The root folder containing mods.</param> public IEnumerable<ModFolder> GetModFolders(string rootPath) { - DirectoryInfo root = new DirectoryInfo(rootPath); + DirectoryInfo root = new(rootPath); return this.GetModFolders(root, root); } @@ -115,7 +115,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning public ModFolder ReadFolder(DirectoryInfo root, DirectoryInfo searchFolder) { // find manifest.json - FileInfo manifestFile = this.FindManifest(searchFolder); + FileInfo? manifestFile = this.FindManifest(searchFolder); // set appropriate invalid-mod error if (manifestFile == null) @@ -137,7 +137,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning return new ModFolder(root, searchFolder, ModType.Xnb, null, ModParseError.XnbMod, "it's not a SMAPI mod (see https://smapi.io/xnb for info)."); // SMAPI installer - if (relevantFiles.Any(p => p.Name == "install on Linux.sh" || p.Name == "install on macOS.command" || p.Name == "install on Windows.bat")) + if (relevantFiles.Any(p => p.Name is "install on Linux.sh" or "install on macOS.command" or "install on Windows.bat")) return new ModFolder(root, searchFolder, ModType.Invalid, null, ModParseError.ManifestMissing, "the SMAPI installer isn't a mod (you can delete this folder after running the installer file)."); // not a mod? @@ -145,13 +145,13 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning } // read mod info - Manifest manifest = null; + Manifest? manifest = null; ModParseError error = ModParseError.None; - string errorText = null; + string? errorText = null; { try { - if (!this.JsonHelper.ReadJsonFileIfExists<Manifest>(manifestFile.FullName, out manifest) || manifest == null) + if (!this.JsonHelper.ReadJsonFileIfExists<Manifest>(manifestFile.FullName, out manifest)) { error = ModParseError.ManifestInvalid; errorText = "its manifest is invalid."; @@ -169,14 +169,6 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning } } - // normalize display fields - if (manifest != null) - { - manifest.Name = this.StripNewlines(manifest.Name); - manifest.Description = this.StripNewlines(manifest.Description); - manifest.Author = this.StripNewlines(manifest.Author); - } - // get mod type ModType type; { @@ -192,7 +184,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning } // build result - return new ModFolder(root, manifestFile.Directory, type, manifest, error, errorText); + return new ModFolder(root, manifestFile.Directory!, type, manifest, error, errorText); } @@ -255,12 +247,12 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning /// <summary>Find the manifest for a mod folder.</summary> /// <param name="folder">The folder to search.</param> - private FileInfo FindManifest(DirectoryInfo folder) + private FileInfo? FindManifest(DirectoryInfo folder) { while (true) { // check for manifest in current folder - FileInfo file = new FileInfo(Path.Combine(folder.FullName, "manifest.json")); + FileInfo file = new(Path.Combine(folder.FullName, "manifest.json")); if (file.Exists) return file; @@ -363,12 +355,5 @@ namespace StardewModdingAPI.Toolkit.Framework.ModScanning return hasVortexMarker; } - - /// <summary>Strip newlines from a string.</summary> - /// <param name="input">The input to strip.</param> - private string StripNewlines(string input) - { - return input?.Replace("\r", "").Replace("\n", ""); - } } } diff --git a/src/SMAPI.Toolkit/Framework/SemanticVersionReader.cs b/src/SMAPI.Toolkit/Framework/SemanticVersionReader.cs index 489e1c4d..939be771 100644 --- a/src/SMAPI.Toolkit/Framework/SemanticVersionReader.cs +++ b/src/SMAPI.Toolkit/Framework/SemanticVersionReader.cs @@ -1,3 +1,5 @@ +using System.Diagnostics.CodeAnalysis; + namespace StardewModdingAPI.Toolkit.Framework { /// <summary>Reads strings into a semantic version.</summary> @@ -16,7 +18,7 @@ namespace StardewModdingAPI.Toolkit.Framework /// <param name="prereleaseTag">An optional prerelease tag.</param> /// <param name="buildMetadata">Optional build metadata. This is ignored when determining version precedence.</param> /// <returns>Returns whether the version was successfully parsed.</returns> - public static bool TryParse(string versionStr, bool allowNonStandard, out int major, out int minor, out int patch, out int platformRelease, out string prereleaseTag, out string buildMetadata) + public static bool TryParse(string? versionStr, bool allowNonStandard, out int major, out int minor, out int patch, out int platformRelease, out string? prereleaseTag, out string? buildMetadata) { // init major = 0; @@ -103,7 +105,12 @@ namespace StardewModdingAPI.Toolkit.Framework /// <param name="raw">The raw characters to parse.</param> /// <param name="index">The index of the next character to read.</param> /// <param name="tag">The parsed tag.</param> - private static bool TryParseTag(char[] raw, ref int index, out string tag) + private static bool TryParseTag(char[] raw, ref int index, +#if NET5_0_OR_GREATER + [NotNullWhen(true)] +#endif + out string? tag + ) { // read tag length int length = 0; diff --git a/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs b/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs index 077c0361..4c9ca2ff 100644 --- a/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs +++ b/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; namespace StardewModdingAPI.Toolkit.Framework.UpdateData { @@ -15,10 +16,13 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData public ModSiteKey Site { get; } /// <summary>The mod ID within the repository.</summary> - public string ID { get; } +#if NET5_0_OR_GREATER + [MemberNotNullWhen(true, nameof(LooksValid))] +#endif + public string? ID { get; } /// <summary>If specified, a substring in download names/descriptions to match.</summary> - public string Subkey { get; } + public string? Subkey { get; } /// <summary>Whether the update key seems to be valid.</summary> public bool LooksValid { get; } @@ -32,9 +36,9 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData /// <param name="site">The mod site containing the mod.</param> /// <param name="id">The mod ID within the site.</param> /// <param name="subkey">If specified, a substring in download names/descriptions to match.</param> - public UpdateKey(string rawText, ModSiteKey site, string id, string subkey) + public UpdateKey(string? rawText, ModSiteKey site, string? id, string? subkey) { - this.RawText = rawText?.Trim(); + this.RawText = rawText?.Trim() ?? string.Empty; this.Site = site; this.ID = id?.Trim(); this.Subkey = subkey?.Trim(); @@ -47,19 +51,19 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData /// <param name="site">The mod site containing the mod.</param> /// <param name="id">The mod ID within the site.</param> /// <param name="subkey">If specified, a substring in download names/descriptions to match.</param> - public UpdateKey(ModSiteKey site, string id, string subkey) + public UpdateKey(ModSiteKey site, string? id, string? subkey) : this(UpdateKey.GetString(site, id, subkey), site, id, subkey) { } /// <summary>Parse a raw update key.</summary> /// <param name="raw">The raw update key to parse.</param> - public static UpdateKey Parse(string raw) + public static UpdateKey Parse(string? raw) { // extract site + ID - string rawSite; - string id; + string? rawSite; + string? id; { - string[] parts = raw?.Trim().Split(':'); - if (parts == null || parts.Length != 2) + string[]? parts = raw?.Trim().Split(':'); + if (parts?.Length != 2) return new UpdateKey(raw, ModSiteKey.Unknown, null, null); rawSite = parts[0].Trim(); @@ -69,7 +73,7 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData id = null; // extract subkey - string subkey = null; + string? subkey = null; if (id != null) { string[] parts = id.Split('@'); @@ -109,7 +113,7 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData /// <summary>Indicates whether the current object is equal to another object of the same type.</summary> /// <param name="other">An object to compare with this object.</param> - public bool Equals(UpdateKey other) + public bool Equals(UpdateKey? other) { if (!this.LooksValid) { @@ -127,7 +131,7 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData /// <summary>Determines whether the specified object is equal to the current object.</summary> /// <param name="obj">The object to compare with the current object.</param> - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is UpdateKey other && this.Equals(other); } @@ -143,7 +147,7 @@ namespace StardewModdingAPI.Toolkit.Framework.UpdateData /// <param name="site">The mod site containing the mod.</param> /// <param name="id">The mod ID within the repository.</param> /// <param name="subkey">If specified, a substring in download names/descriptions to match.</param> - public static string GetString(ModSiteKey site, string id, string subkey = null) + public static string GetString(ModSiteKey site, string? id, string? subkey = null) { return $"{site}:{id}{subkey}".Trim(); } diff --git a/src/SMAPI.Toolkit/ModToolkit.cs b/src/SMAPI.Toolkit/ModToolkit.cs index 38a67ae5..51f6fa24 100644 --- a/src/SMAPI.Toolkit/ModToolkit.cs +++ b/src/SMAPI.Toolkit/ModToolkit.cs @@ -22,7 +22,7 @@ namespace StardewModdingAPI.Toolkit private readonly string UserAgent; /// <summary>Maps vendor keys (like <c>Nexus</c>) to their mod URL template (where <c>{0}</c> is the mod ID). This doesn't affect update checks, which defer to the remote web API.</summary> - private readonly IDictionary<ModSiteKey, string> VendorModUrls = new Dictionary<ModSiteKey, string>() + private readonly Dictionary<ModSiteKey, string> VendorModUrls = new() { [ModSiteKey.Chucklefish] = "https://community.playstarbound.com/resources/{0}", [ModSiteKey.GitHub] = "https://github.com/{0}/releases", @@ -34,7 +34,7 @@ namespace StardewModdingAPI.Toolkit ** Accessors *********/ /// <summary>Encapsulates SMAPI's JSON parsing.</summary> - public JsonHelper JsonHelper { get; } = new JsonHelper(); + public JsonHelper JsonHelper { get; } = new(); /********* @@ -43,7 +43,7 @@ namespace StardewModdingAPI.Toolkit /// <summary>Construct an instance.</summary> public ModToolkit() { - ISemanticVersion version = new SemanticVersion(this.GetType().Assembly.GetName().Version); + ISemanticVersion version = new SemanticVersion(this.GetType().Assembly.GetName().Version!); this.UserAgent = $"SMAPI Mod Handler Toolkit/{version}"; } @@ -57,7 +57,7 @@ namespace StardewModdingAPI.Toolkit /// <summary>Extract mod metadata from the wiki compatibility list.</summary> public async Task<WikiModList> GetWikiCompatibilityListAsync() { - var client = new WikiClient(this.UserAgent); + WikiClient client = new(this.UserAgent); return await client.FetchModsAsync(); } @@ -87,13 +87,13 @@ namespace StardewModdingAPI.Toolkit /// <summary>Get an update URL for an update key (if valid).</summary> /// <param name="updateKey">The update key.</param> - public string GetUpdateUrl(string updateKey) + public string? GetUpdateUrl(string updateKey) { UpdateKey parsed = UpdateKey.Parse(updateKey); if (!parsed.LooksValid) return null; - if (this.VendorModUrls.TryGetValue(parsed.Site, out string urlTemplate)) + if (this.VendorModUrls.TryGetValue(parsed.Site, out string? urlTemplate)) return string.Format(urlTemplate, parsed.ID); return null; diff --git a/src/SMAPI.Toolkit/SemanticVersion.cs b/src/SMAPI.Toolkit/SemanticVersion.cs index 2f3e282b..2cb27e11 100644 --- a/src/SMAPI.Toolkit/SemanticVersion.cs +++ b/src/SMAPI.Toolkit/SemanticVersion.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using StardewModdingAPI.Toolkit.Framework; @@ -38,10 +39,10 @@ namespace StardewModdingAPI.Toolkit public int PlatformRelease { get; } /// <inheritdoc /> - public string PrereleaseTag { get; } + public string? PrereleaseTag { get; } /// <inheritdoc /> - public string BuildMetadata { get; } + public string? BuildMetadata { get; } /********* @@ -54,7 +55,7 @@ namespace StardewModdingAPI.Toolkit /// <param name="platformRelease">The platform-specific version (if applicable).</param> /// <param name="prereleaseTag">An optional prerelease tag.</param> /// <param name="buildMetadata">Optional build metadata. This is ignored when determining version precedence.</param> - public SemanticVersion(int major, int minor, int patch, int platformRelease = 0, string prereleaseTag = null, string buildMetadata = null) + public SemanticVersion(int major, int minor, int patch, int platformRelease = 0, string? prereleaseTag = null, string? buildMetadata = null) { this.MajorVersion = major; this.MinorVersion = minor; @@ -90,7 +91,7 @@ namespace StardewModdingAPI.Toolkit { if (version == null) throw new ArgumentNullException(nameof(version), "The input version string can't be null."); - if (!SemanticVersionReader.TryParse(version, allowNonStandard, out int major, out int minor, out int patch, out int platformRelease, out string prereleaseTag, out string buildMetadata) || (!allowNonStandard && platformRelease != 0)) + if (!SemanticVersionReader.TryParse(version, allowNonStandard, out int major, out int minor, out int patch, out int platformRelease, out string? prereleaseTag, out string? buildMetadata) || (!allowNonStandard && platformRelease != 0)) throw new FormatException($"The input '{version}' isn't a valid semantic version."); this.MajorVersion = major; @@ -104,15 +105,15 @@ namespace StardewModdingAPI.Toolkit } /// <inheritdoc /> - public int CompareTo(ISemanticVersion other) + public int CompareTo(ISemanticVersion? other) { - if (other == null) - throw new ArgumentNullException(nameof(other)); - return this.CompareTo(other.MajorVersion, other.MinorVersion, other.PatchVersion, (other as SemanticVersion)?.PlatformRelease ?? 0, other.PrereleaseTag); + return other == null + ? 1 + : this.CompareTo(other.MajorVersion, other.MinorVersion, other.PatchVersion, (other as SemanticVersion)?.PlatformRelease ?? 0, other.PrereleaseTag); } /// <inheritdoc /> - public bool Equals(ISemanticVersion other) + public bool Equals(ISemanticVersion? other) { return other != null && this.CompareTo(other) == 0; } @@ -124,39 +125,54 @@ namespace StardewModdingAPI.Toolkit } /// <inheritdoc /> - public bool IsOlderThan(ISemanticVersion other) + public bool IsOlderThan(ISemanticVersion? other) { return this.CompareTo(other) < 0; } /// <inheritdoc /> - public bool IsOlderThan(string other) + public bool IsOlderThan(string? other) { - return this.IsOlderThan(new SemanticVersion(other, allowNonStandard: true)); + ISemanticVersion? otherVersion = other != null + ? new SemanticVersion(other, allowNonStandard: true) + : null; + + return this.IsOlderThan(otherVersion); } /// <inheritdoc /> - public bool IsNewerThan(ISemanticVersion other) + public bool IsNewerThan(ISemanticVersion? other) { return this.CompareTo(other) > 0; } /// <inheritdoc /> - public bool IsNewerThan(string other) + public bool IsNewerThan(string? other) { - return this.IsNewerThan(new SemanticVersion(other, allowNonStandard: true)); + ISemanticVersion? otherVersion = other != null + ? new SemanticVersion(other, allowNonStandard: true) + : null; + + return this.IsNewerThan(otherVersion); } /// <inheritdoc /> - public bool IsBetween(ISemanticVersion min, ISemanticVersion max) + public bool IsBetween(ISemanticVersion? min, ISemanticVersion? max) { return this.CompareTo(min) >= 0 && this.CompareTo(max) <= 0; } /// <inheritdoc /> - public bool IsBetween(string min, string max) + public bool IsBetween(string? min, string? max) { - return this.IsBetween(new SemanticVersion(min, allowNonStandard: true), new SemanticVersion(max, allowNonStandard: true)); + ISemanticVersion? minVersion = min != null + ? new SemanticVersion(min, allowNonStandard: true) + : null; + ISemanticVersion? maxVersion = max != null + ? new SemanticVersion(max, allowNonStandard: true) + : null; + + return this.IsBetween(minVersion, maxVersion); } /// <inheritdoc cref="ISemanticVersion.ToString" /> @@ -182,7 +198,12 @@ namespace StardewModdingAPI.Toolkit /// <param name="version">The version string.</param> /// <param name="parsed">The parsed representation.</param> /// <returns>Returns whether parsing the version succeeded.</returns> - public static bool TryParse(string version, out ISemanticVersion parsed) + public static bool TryParse(string? version, +#if NET5_0_OR_GREATER + [NotNullWhen(true)] +#endif + out ISemanticVersion? parsed + ) { return SemanticVersion.TryParse(version, allowNonStandard: false, out parsed); } @@ -192,8 +213,19 @@ namespace StardewModdingAPI.Toolkit /// <param name="allowNonStandard">Whether to allow non-standard extensions to semantic versioning.</param> /// <param name="parsed">The parsed representation.</param> /// <returns>Returns whether parsing the version succeeded.</returns> - public static bool TryParse(string version, bool allowNonStandard, out ISemanticVersion parsed) + public static bool TryParse(string? version, bool allowNonStandard, +#if NET5_0_OR_GREATER + [NotNullWhen(true)] +#endif + out ISemanticVersion? parsed + ) { + if (version == null) + { + parsed = null; + return false; + } + try { parsed = new SemanticVersion(version, allowNonStandard); @@ -212,7 +244,7 @@ namespace StardewModdingAPI.Toolkit *********/ /// <summary>Get a normalized prerelease or build tag.</summary> /// <param name="tag">The tag to normalize.</param> - private string GetNormalizedTag(string tag) + private string? GetNormalizedTag(string? tag) { tag = tag?.Trim(); return !string.IsNullOrWhiteSpace(tag) ? tag : null; @@ -224,7 +256,7 @@ namespace StardewModdingAPI.Toolkit /// <param name="otherPatch">The patch version to compare with this instance.</param> /// <param name="otherPlatformRelease">The non-standard platform release to compare with this instance.</param> /// <param name="otherTag">The prerelease tag to compare with this instance.</param> - private int CompareTo(int otherMajor, int otherMinor, int otherPatch, int otherPlatformRelease, string otherTag) + private int CompareTo(int otherMajor, int otherMinor, int otherPatch, int otherPlatformRelease, string? otherTag) { const int same = 0; const int curNewer = 1; @@ -253,8 +285,8 @@ namespace StardewModdingAPI.Toolkit return curOlder; // compare two prerelease tag values - string[] curParts = this.PrereleaseTag.Split('.', '-'); - string[] otherParts = otherTag.Split('.', '-'); + string[] curParts = this.PrereleaseTag?.Split('.', '-') ?? Array.Empty<string>(); + string[] otherParts = otherTag?.Split('.', '-') ?? Array.Empty<string>(); int length = Math.Max(curParts.Length, otherParts.Length); for (int i = 0; i < length; i++) { diff --git a/src/SMAPI.Toolkit/SemanticVersionComparer.cs b/src/SMAPI.Toolkit/SemanticVersionComparer.cs index 9f6b57a2..85c974bd 100644 --- a/src/SMAPI.Toolkit/SemanticVersionComparer.cs +++ b/src/SMAPI.Toolkit/SemanticVersionComparer.cs @@ -9,14 +9,14 @@ namespace StardewModdingAPI.Toolkit ** Accessors *********/ /// <summary>A singleton instance of the comparer.</summary> - public static SemanticVersionComparer Instance { get; } = new SemanticVersionComparer(); + public static SemanticVersionComparer Instance { get; } = new(); /********* ** Public methods *********/ /// <inheritdoc /> - public int Compare(ISemanticVersion x, ISemanticVersion y) + public int Compare(ISemanticVersion? x, ISemanticVersion? y) { if (object.ReferenceEquals(x, y)) return 0; diff --git a/src/SMAPI.Toolkit/Serialization/Converters/ManifestContentPackForConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/ManifestContentPackForConverter.cs index 5cabe9d8..faaeedea 100644 --- a/src/SMAPI.Toolkit/Serialization/Converters/ManifestContentPackForConverter.cs +++ b/src/SMAPI.Toolkit/Serialization/Converters/ManifestContentPackForConverter.cs @@ -33,7 +33,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters /// <param name="objectType">The object type.</param> /// <param name="existingValue">The object being read.</param> /// <param name="serializer">The calling serializer.</param> - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { return serializer.Deserialize<ManifestContentPackFor>(reader); } @@ -42,7 +42,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters /// <param name="writer">The JSON writer.</param> /// <param name="value">The value.</param> /// <param name="serializer">The calling serializer.</param> - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { throw new InvalidOperationException("This converter does not write JSON."); } diff --git a/src/SMAPI.Toolkit/Serialization/Converters/ManifestDependencyArrayConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/ManifestDependencyArrayConverter.cs index 7b88d6b7..c499a2c6 100644 --- a/src/SMAPI.Toolkit/Serialization/Converters/ManifestDependencyArrayConverter.cs +++ b/src/SMAPI.Toolkit/Serialization/Converters/ManifestDependencyArrayConverter.cs @@ -35,13 +35,13 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters /// <param name="objectType">The object type.</param> /// <param name="existingValue">The object being read.</param> /// <param name="serializer">The calling serializer.</param> - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { List<ManifestDependency> result = new List<ManifestDependency>(); foreach (JObject obj in JArray.Load(reader).Children<JObject>()) { - string uniqueID = obj.ValueIgnoreCase<string>(nameof(ManifestDependency.UniqueID)); - string minVersion = obj.ValueIgnoreCase<string>(nameof(ManifestDependency.MinimumVersion)); + string uniqueID = obj.ValueIgnoreCase<string>(nameof(ManifestDependency.UniqueID))!; // will be validated separately if null + string? minVersion = obj.ValueIgnoreCase<string>(nameof(ManifestDependency.MinimumVersion)); bool required = obj.ValueIgnoreCase<bool?>(nameof(ManifestDependency.IsRequired)) ?? true; result.Add(new ManifestDependency(uniqueID, minVersion, required)); } @@ -52,7 +52,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters /// <param name="writer">The JSON writer.</param> /// <param name="value">The value.</param> /// <param name="serializer">The calling serializer.</param> - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { throw new InvalidOperationException("This converter does not write JSON."); } diff --git a/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs index cf69104d..c32c3185 100644 --- a/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs +++ b/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs @@ -39,15 +39,17 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters /// <param name="objectType">The object type.</param> /// <param name="existingValue">The object being read.</param> /// <param name="serializer">The calling serializer.</param> - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { string path = reader.Path; switch (reader.TokenType) { case JsonToken.StartObject: return this.ReadObject(JObject.Load(reader)); + case JsonToken.String: return this.ReadString(JToken.Load(reader).Value<string>(), path); + default: throw new SParseException($"Can't parse {nameof(ISemanticVersion)} from {reader.TokenType} node (path: {reader.Path})."); } @@ -57,7 +59,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters /// <param name="writer">The JSON writer.</param> /// <param name="value">The value.</param> /// <param name="serializer">The calling serializer.</param> - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { writer.WriteValue(value?.ToString()); } @@ -73,7 +75,7 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters int major = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.MajorVersion)); int minor = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.MinorVersion)); int patch = obj.ValueIgnoreCase<int>(nameof(ISemanticVersion.PatchVersion)); - string prereleaseTag = obj.ValueIgnoreCase<string>(nameof(ISemanticVersion.PrereleaseTag)); + string? prereleaseTag = obj.ValueIgnoreCase<string>(nameof(ISemanticVersion.PrereleaseTag)); return new SemanticVersion(major, minor, patch, prereleaseTag: prereleaseTag); } @@ -81,11 +83,11 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters /// <summary>Read a JSON string.</summary> /// <param name="str">The JSON string value.</param> /// <param name="path">The path to the current JSON node.</param> - private ISemanticVersion ReadString(string str, string path) + private ISemanticVersion? ReadString(string str, string path) { if (string.IsNullOrWhiteSpace(str)) return null; - if (!SemanticVersion.TryParse(str, allowNonStandard: this.AllowNonStandard, out ISemanticVersion version)) + if (!SemanticVersion.TryParse(str, allowNonStandard: this.AllowNonStandard, out ISemanticVersion? version)) throw new SParseException($"Can't parse semantic version from invalid value '{str}', should be formatted like 1.2, 1.2.30, or 1.2.30-beta (path: {path})."); return version; } diff --git a/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs index ccc5158b..1c59f5e7 100644 --- a/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs +++ b/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs @@ -25,21 +25,12 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters return objectType == typeof(T) || Nullable.GetUnderlyingType(objectType) == typeof(T); } - /// <summary>Writes the JSON representation of the object.</summary> - /// <param name="writer">The JSON writer.</param> - /// <param name="value">The value.</param> - /// <param name="serializer">The calling serializer.</param> - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new InvalidOperationException("This converter does not write JSON."); - } - /// <summary>Reads the JSON representation of the object.</summary> /// <param name="reader">The JSON reader.</param> /// <param name="objectType">The object type.</param> /// <param name="existingValue">The object being read.</param> /// <param name="serializer">The calling serializer.</param> - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { string path = reader.Path; switch (reader.TokenType) @@ -58,6 +49,15 @@ namespace StardewModdingAPI.Toolkit.Serialization.Converters } } + /// <summary>Writes the JSON representation of the object.</summary> + /// <param name="writer">The JSON writer.</param> + /// <param name="value">The value.</param> + /// <param name="serializer">The calling serializer.</param> + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) + { + throw new InvalidOperationException("This converter does not write JSON."); + } + /********* ** Protected methods diff --git a/src/SMAPI.Toolkit/Serialization/InternalExtensions.cs b/src/SMAPI.Toolkit/Serialization/InternalExtensions.cs index 10f88dde..78297035 100644 --- a/src/SMAPI.Toolkit/Serialization/InternalExtensions.cs +++ b/src/SMAPI.Toolkit/Serialization/InternalExtensions.cs @@ -10,12 +10,12 @@ namespace StardewModdingAPI.Toolkit.Serialization /// <typeparam name="T">The value type.</typeparam> /// <param name="obj">The JSON object to search.</param> /// <param name="fieldName">The field name.</param> - public static T ValueIgnoreCase<T>(this JObject obj, string fieldName) + public static T? ValueIgnoreCase<T>(this JObject obj, string fieldName) { - JToken token = obj.GetValue(fieldName, StringComparison.OrdinalIgnoreCase); + JToken? token = obj.GetValue(fieldName, StringComparison.OrdinalIgnoreCase); return token != null ? token.Value<T>() - : default(T); + : default; } } } diff --git a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs index 00db9903..3c9308f2 100644 --- a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs +++ b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -14,7 +15,7 @@ namespace StardewModdingAPI.Toolkit.Serialization ** Accessors *********/ /// <summary>The JSON settings to use when serializing and deserializing files.</summary> - public JsonSerializerSettings JsonSettings { get; } = new JsonSerializerSettings + public JsonSerializerSettings JsonSettings { get; } = new() { Formatting = Formatting.Indented, ObjectCreationHandling = ObjectCreationHandling.Replace, // avoid issue where default ICollection<T> values are duplicated each time the config is loaded @@ -36,7 +37,12 @@ namespace StardewModdingAPI.Toolkit.Serialization /// <returns>Returns false if the file doesn't exist, else true.</returns> /// <exception cref="ArgumentException">The given <paramref name="fullPath"/> is empty or invalid.</exception> /// <exception cref="JsonReaderException">The file contains invalid JSON.</exception> - public bool ReadJsonFileIfExists<TModel>(string fullPath, out TModel result) + public bool ReadJsonFileIfExists<TModel>(string fullPath, +#if NET5_0_OR_GREATER + [NotNullWhen(true)] +#endif + out TModel? result + ) { // validate if (string.IsNullOrWhiteSpace(fullPath)) @@ -48,9 +54,9 @@ namespace StardewModdingAPI.Toolkit.Serialization { json = File.ReadAllText(fullPath); } - catch (Exception ex) when (ex is DirectoryNotFoundException || ex is FileNotFoundException) + catch (Exception ex) when (ex is DirectoryNotFoundException or FileNotFoundException) { - result = default(TModel); + result = default; return false; } @@ -58,7 +64,7 @@ namespace StardewModdingAPI.Toolkit.Serialization try { result = this.Deserialize<TModel>(json); - return true; + return result != null; } catch (Exception ex) { @@ -88,7 +94,7 @@ namespace StardewModdingAPI.Toolkit.Serialization throw new ArgumentException("The file path is empty or invalid.", nameof(fullPath)); // create directory if needed - string dir = Path.GetDirectoryName(fullPath); + string dir = Path.GetDirectoryName(fullPath)!; if (dir == null) throw new ArgumentException("The file path is invalid.", nameof(fullPath)); if (!Directory.Exists(dir)) @@ -106,7 +112,8 @@ namespace StardewModdingAPI.Toolkit.Serialization { try { - return JsonConvert.DeserializeObject<TModel>(json, this.JsonSettings); + return JsonConvert.DeserializeObject<TModel>(json, this.JsonSettings) + ?? throw new InvalidOperationException($"Couldn't deserialize model type '{typeof(TModel)}' from empty or null JSON."); } catch (JsonReaderException) { @@ -115,7 +122,8 @@ namespace StardewModdingAPI.Toolkit.Serialization { try { - return JsonConvert.DeserializeObject<TModel>(json.Replace('“', '"').Replace('”', '"'), this.JsonSettings); + return JsonConvert.DeserializeObject<TModel>(json.Replace('“', '"').Replace('”', '"'), this.JsonSettings) + ?? throw new InvalidOperationException($"Couldn't deserialize model type '{typeof(TModel)}' from empty or null JSON."); } catch { /* rethrow original error */ } } diff --git a/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs b/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs index 4ad97b6d..01010602 100644 --- a/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs +++ b/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Diagnostics.CodeAnalysis; using Newtonsoft.Json; using StardewModdingAPI.Toolkit.Serialization.Converters; @@ -13,48 +13,45 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models ** Accessors *********/ /// <summary>The mod name.</summary> - public string Name { get; set; } + public string Name { get; } /// <summary>A brief description of the mod.</summary> - public string Description { get; set; } + public string Description { get; } /// <summary>The mod author's name.</summary> - public string Author { get; set; } + public string Author { get; } /// <summary>The mod version.</summary> - public ISemanticVersion Version { get; set; } + public ISemanticVersion Version { get; } /// <summary>The minimum SMAPI version required by this mod, if any.</summary> - public ISemanticVersion MinimumApiVersion { get; set; } + public ISemanticVersion? MinimumApiVersion { get; } /// <summary>The name of the DLL in the directory that has the <c>Entry</c> method. Mutually exclusive with <see cref="ContentPackFor"/>.</summary> - public string EntryDll { get; set; } + public string? EntryDll { get; } /// <summary>The mod which will read this as a content pack. Mutually exclusive with <see cref="Manifest.EntryDll"/>.</summary> [JsonConverter(typeof(ManifestContentPackForConverter))] - public IManifestContentPackFor ContentPackFor { get; set; } + public IManifestContentPackFor? ContentPackFor { get; } /// <summary>The other mods that must be loaded before this mod.</summary> [JsonConverter(typeof(ManifestDependencyArrayConverter))] - public IManifestDependency[] Dependencies { get; set; } + public IManifestDependency[] Dependencies { get; } /// <summary>The namespaced mod IDs to query for updates (like <c>Nexus:541</c>).</summary> - public string[] UpdateKeys { get; set; } + public string[] UpdateKeys { get; private set; } /// <summary>The unique mod ID.</summary> - public string UniqueID { get; set; } + public string UniqueID { get; } /// <summary>Any manifest fields which didn't match a valid field.</summary> [JsonExtensionData] - public IDictionary<string, object> ExtraFields { get; set; } + public IDictionary<string, object> ExtraFields { get; set; } = new Dictionary<string, object>(); /********* ** Public methods *********/ - /// <summary>Construct an instance.</summary> - public Manifest() { } - /// <summary>Construct an instance for a transitional content pack.</summary> /// <param name="uniqueID">The unique mod ID.</param> /// <param name="name">The mod name.</param> @@ -62,24 +59,71 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models /// <param name="description">A brief description of the mod.</param> /// <param name="version">The mod version.</param> /// <param name="contentPackFor">The modID which will read this as a content pack.</param> - public Manifest(string uniqueID, string name, string author, string description, ISemanticVersion version, string contentPackFor = null) + public Manifest(string uniqueID, string name, string author, string description, ISemanticVersion version, string? contentPackFor = null) + : this( + uniqueId: uniqueID, + name: name, + author: author, + description: description, + version: version, + minimumApiVersion: null, + entryDll: null, + contentPackFor: contentPackFor != null + ? new ManifestContentPackFor(contentPackFor, null) + : null, + dependencies: null, + updateKeys: null + ) + { } + + /// <summary>Construct an instance for a transitional content pack.</summary> + /// <param name="uniqueId">The unique mod ID.</param> + /// <param name="name">The mod name.</param> + /// <param name="author">The mod author's name.</param> + /// <param name="description">A brief description of the mod.</param> + /// <param name="version">The mod version.</param> + /// <param name="minimumApiVersion">The minimum SMAPI version required by this mod, if any.</param> + /// <param name="entryDll">The name of the DLL in the directory that has the <c>Entry</c> method. Mutually exclusive with <see cref="ContentPackFor"/>.</param> + /// <param name="contentPackFor">The modID which will read this as a content pack.</param> + /// <param name="dependencies">The other mods that must be loaded before this mod.</param> + /// <param name="updateKeys">The namespaced mod IDs to query for updates (like <c>Nexus:541</c>).</param> + [JsonConstructor] + public Manifest(string uniqueId, string name, string author, string description, ISemanticVersion version, ISemanticVersion? minimumApiVersion, string? entryDll, IManifestContentPackFor? contentPackFor, IManifestDependency[]? dependencies, string[]? updateKeys) { - this.Name = name; - this.Author = author; - this.Description = description; + this.UniqueID = this.NormalizeWhitespace(uniqueId); + this.Name = this.NormalizeWhitespace(name); + this.Author = this.NormalizeWhitespace(author); + this.Description = this.NormalizeWhitespace(description); this.Version = version; - this.UniqueID = uniqueID; - this.UpdateKeys = Array.Empty<string>(); - this.ContentPackFor = new ManifestContentPackFor { UniqueID = contentPackFor }; + this.MinimumApiVersion = minimumApiVersion; + this.EntryDll = this.NormalizeWhitespace(entryDll); + this.ContentPackFor = contentPackFor; + this.Dependencies = dependencies ?? Array.Empty<IManifestDependency>(); + this.UpdateKeys = updateKeys ?? Array.Empty<string>(); + } + + /// <summary>Override the update keys loaded from the mod info.</summary> + /// <param name="updateKeys">The new update keys to set.</param> + internal void OverrideUpdateKeys(params string[] updateKeys) + { + this.UpdateKeys = updateKeys; } - /// <summary>Normalize the model after it's deserialized.</summary> - /// <param name="context">The deserialization context.</param> - [OnDeserialized] - public void OnDeserialized(StreamingContext context) + + /********* + ** Private methods + *********/ + /// <summary>Normalize whitespace in a raw string.</summary> + /// <param name="input">The input to strip.</param> +#if NET5_0_OR_GREATER + [return: NotNullIfNotNull("input")] +#endif + private string? NormalizeWhitespace(string? input) { - this.Dependencies ??= Array.Empty<IManifestDependency>(); - this.UpdateKeys ??= Array.Empty<string>(); + return input + ?.Trim() + .Replace("\r", "") + .Replace("\n", ""); } } } diff --git a/src/SMAPI.Toolkit/Serialization/Models/ManifestContentPackFor.cs b/src/SMAPI.Toolkit/Serialization/Models/ManifestContentPackFor.cs index 1eb80889..f7dc8aa8 100644 --- a/src/SMAPI.Toolkit/Serialization/Models/ManifestContentPackFor.cs +++ b/src/SMAPI.Toolkit/Serialization/Models/ManifestContentPackFor.cs @@ -1,3 +1,5 @@ +using System.Diagnostics.CodeAnalysis; + namespace StardewModdingAPI.Toolkit.Serialization.Models { /// <summary>Indicates which mod can read the content pack represented by the containing manifest.</summary> @@ -7,9 +9,36 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models ** Accessors *********/ /// <summary>The unique ID of the mod which can read this content pack.</summary> - public string UniqueID { get; set; } + public string UniqueID { get; } /// <summary>The minimum required version (if any).</summary> - public ISemanticVersion MinimumVersion { get; set; } + public ISemanticVersion? MinimumVersion { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="uniqueId">The unique ID of the mod which can read this content pack.</param> + /// <param name="minimumVersion">The minimum required version (if any).</param> + public ManifestContentPackFor(string uniqueId, ISemanticVersion? minimumVersion) + { + this.UniqueID = this.NormalizeWhitespace(uniqueId); + this.MinimumVersion = minimumVersion; + } + + + /********* + ** Private methods + *********/ + /// <summary>Normalize whitespace in a raw string.</summary> + /// <param name="input">The input to strip.</param> +#if NET5_0_OR_GREATER + [return: NotNullIfNotNull("input")] +#endif + private string? NormalizeWhitespace(string? input) + { + return input?.Trim(); + } } } diff --git a/src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs b/src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs index 00f168f4..fa254ea7 100644 --- a/src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs +++ b/src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs @@ -1,3 +1,6 @@ +using System.Diagnostics.CodeAnalysis; +using Newtonsoft.Json; + namespace StardewModdingAPI.Toolkit.Serialization.Models { /// <summary>A mod dependency listed in a mod manifest.</summary> @@ -7,13 +10,13 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models ** Accessors *********/ /// <summary>The unique mod ID to require.</summary> - public string UniqueID { get; set; } + public string UniqueID { get; } /// <summary>The minimum required version (if any).</summary> - public ISemanticVersion MinimumVersion { get; set; } + public ISemanticVersion? MinimumVersion { get; } /// <summary>Whether the dependency must be installed to use the mod.</summary> - public bool IsRequired { get; set; } + public bool IsRequired { get; } /********* @@ -23,13 +26,40 @@ namespace StardewModdingAPI.Toolkit.Serialization.Models /// <param name="uniqueID">The unique mod ID to require.</param> /// <param name="minimumVersion">The minimum required version (if any).</param> /// <param name="required">Whether the dependency must be installed to use the mod.</param> - public ManifestDependency(string uniqueID, string minimumVersion, bool required = true) + public ManifestDependency(string uniqueID, string? minimumVersion, bool required = true) + : this( + uniqueID: uniqueID, + minimumVersion: !string.IsNullOrWhiteSpace(minimumVersion) + ? new SemanticVersion(minimumVersion) + : null, + required: required + ) + { } + + /// <summary>Construct an instance.</summary> + /// <param name="uniqueID">The unique mod ID to require.</param> + /// <param name="minimumVersion">The minimum required version (if any).</param> + /// <param name="required">Whether the dependency must be installed to use the mod.</param> + [JsonConstructor] + public ManifestDependency(string uniqueID, ISemanticVersion? minimumVersion, bool required = true) { - this.UniqueID = uniqueID; - this.MinimumVersion = !string.IsNullOrWhiteSpace(minimumVersion) - ? new SemanticVersion(minimumVersion) - : null; + this.UniqueID = this.NormalizeWhitespace(uniqueID); + this.MinimumVersion = minimumVersion; this.IsRequired = required; } + + + /********* + ** Private methods + *********/ + /// <summary>Normalize whitespace in a raw string.</summary> + /// <param name="input">The input to strip.</param> +#if NET5_0_OR_GREATER + [return: NotNullIfNotNull("input")] +#endif + private string? NormalizeWhitespace(string? input) + { + return input?.Trim(); + } } } diff --git a/src/SMAPI.Toolkit/Serialization/SParseException.cs b/src/SMAPI.Toolkit/Serialization/SParseException.cs index 5f58b5b8..c2b3f68e 100644 --- a/src/SMAPI.Toolkit/Serialization/SParseException.cs +++ b/src/SMAPI.Toolkit/Serialization/SParseException.cs @@ -11,7 +11,7 @@ namespace StardewModdingAPI.Toolkit.Serialization /// <summary>Construct an instance.</summary> /// <param name="message">The error message.</param> /// <param name="ex">The underlying exception, if any.</param> - public SParseException(string message, Exception ex = null) + public SParseException(string message, Exception? ex = null) : base(message, ex) { } } } diff --git a/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs b/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs index 7536337a..1791c5b3 100644 --- a/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs +++ b/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics.CodeAnalysis; using StardewModdingAPI.Toolkit.Framework; namespace StardewModdingAPI.Toolkit.Utilities @@ -34,7 +33,6 @@ namespace StardewModdingAPI.Toolkit.Utilities /// <summary>Get the human-readable OS name and version.</summary> /// <param name="platform">The current platform.</param> - [SuppressMessage("ReSharper", "EmptyGeneralCatchClause", Justification = "Error suppressed deliberately to fallback to default behaviour.")] public static string GetFriendlyPlatformName(Platform platform) { return LowLevelEnvironmentUtility.GetFriendlyPlatformName(platform.ToString()); diff --git a/src/SMAPI.Toolkit/Utilities/PathUtilities.cs b/src/SMAPI.Toolkit/Utilities/PathUtilities.cs index 2e9e5eac..136279f2 100644 --- a/src/SMAPI.Toolkit/Utilities/PathUtilities.cs +++ b/src/SMAPI.Toolkit/Utilities/PathUtilities.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.IO; using System.Linq; @@ -36,8 +37,11 @@ namespace StardewModdingAPI.Toolkit.Utilities /// <param name="path">The path to split.</param> /// <param name="limit">The number of segments to match. Any additional segments will be merged into the last returned part.</param> [Pure] - public static string[] GetSegments(string path, int? limit = null) + public static string[] GetSegments(string? path, int? limit = null) { + if (path == null) + return Array.Empty<string>(); + return limit.HasValue ? path.Split(PathUtilities.PossiblePathSeparators, limit.Value, StringSplitOptions.RemoveEmptyEntries) : path.Split(PathUtilities.PossiblePathSeparators, StringSplitOptions.RemoveEmptyEntries); @@ -45,8 +49,16 @@ namespace StardewModdingAPI.Toolkit.Utilities /// <summary>Normalize an asset name to match how MonoGame's content APIs would normalize and cache it.</summary> /// <param name="assetName">The asset name to normalize.</param> - public static string NormalizeAssetName(string assetName) + [Pure] +#if NET5_0_OR_GREATER + [return: NotNullIfNotNull("assetName")] +#endif + public static string? NormalizeAssetName(string? assetName) { + assetName = assetName?.Trim(); + if (string.IsNullOrEmpty(assetName)) + return assetName; + return string.Join(PathUtilities.PreferredAssetSeparator.ToString(), PathUtilities.GetSegments(assetName)); // based on MonoGame's ContentManager.Load<T> logic } @@ -54,7 +66,10 @@ namespace StardewModdingAPI.Toolkit.Utilities /// <param name="path">The file path to normalize.</param> /// <remarks>This should only be used for file paths. For asset names, use <see cref="NormalizeAssetName"/> instead.</remarks> [Pure] - public static string NormalizePath(string path) +#if NET5_0_OR_GREATER + [return: NotNullIfNotNull("path")] +#endif + public static string? NormalizePath(string? path) { path = path?.Trim(); if (string.IsNullOrEmpty(path)) @@ -100,8 +115,8 @@ namespace StardewModdingAPI.Toolkit.Utilities // though, this is only for compatibility with the mod build package. // convert to URIs - Uri from = new Uri(sourceDir.TrimEnd(PathUtilities.PossiblePathSeparators) + "/"); - Uri to = new Uri(targetPath.TrimEnd(PathUtilities.PossiblePathSeparators) + "/"); + Uri from = new(sourceDir.TrimEnd(PathUtilities.PossiblePathSeparators) + "/"); + Uri to = new(targetPath.TrimEnd(PathUtilities.PossiblePathSeparators) + "/"); if (from.Scheme != to.Scheme) throw new InvalidOperationException($"Can't get path for '{targetPath}' relative to '{sourceDir}'."); @@ -132,7 +147,7 @@ namespace StardewModdingAPI.Toolkit.Utilities /// <summary>Get whether a path is relative and doesn't try to climb out of its containing folder (e.g. doesn't contain <c>../</c>).</summary> /// <param name="path">The path to check.</param> [Pure] - public static bool IsSafeRelativePath(string path) + public static bool IsSafeRelativePath(string? path) { if (string.IsNullOrWhiteSpace(path)) return true; @@ -145,9 +160,11 @@ namespace StardewModdingAPI.Toolkit.Utilities /// <summary>Get whether a string is a valid 'slug', containing only basic characters that are safe in all contexts (e.g. filenames, URLs, etc).</summary> /// <param name="str">The string to check.</param> [Pure] - public static bool IsSlug(string str) + public static bool IsSlug(string? str) { - return !Regex.IsMatch(str, "[^a-z0-9_.-]", RegexOptions.IgnoreCase); + return + string.IsNullOrWhiteSpace(str) + || !Regex.IsMatch(str, "[^a-z0-9_.-]", RegexOptions.IgnoreCase); } } } diff --git a/src/SMAPI.Web/BackgroundService.cs b/src/SMAPI.Web/BackgroundService.cs index 64bd5ca5..7706b276 100644 --- a/src/SMAPI.Web/BackgroundService.cs +++ b/src/SMAPI.Web/BackgroundService.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics.CodeAnalysis; using System.Threading; diff --git a/src/SMAPI.Web/Controllers/IndexController.cs b/src/SMAPI.Web/Controllers/IndexController.cs index 5097997c..f7834b9c 100644 --- a/src/SMAPI.Web/Controllers/IndexController.cs +++ b/src/SMAPI.Web/Controllers/IndexController.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -94,7 +96,7 @@ namespace StardewModdingAPI.Web.Controllers // strip 'noinclude' blocks from release description if (release != null) { - HtmlDocument doc = new HtmlDocument(); + HtmlDocument doc = new(); doc.LoadHtml(release.Body); foreach (HtmlNode node in doc.DocumentNode.SelectNodes("//*[@class='noinclude']")?.ToArray() ?? Array.Empty<HtmlNode>()) node.Remove(); diff --git a/src/SMAPI.Web/Controllers/JsonValidatorController.cs b/src/SMAPI.Web/Controllers/JsonValidatorController.cs index e06c1236..5791d834 100644 --- a/src/SMAPI.Web/Controllers/JsonValidatorController.cs +++ b/src/SMAPI.Web/Controllers/JsonValidatorController.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; @@ -161,7 +163,7 @@ namespace StardewModdingAPI.Web.Controllers return this.View("Index", this.GetModel(result.ID, schemaName, isEditView: true).SetContent(input, null).SetUploadError(result.UploadError)); // redirect to view - return this.Redirect(this.Url.PlainAction("Index", "JsonValidator", new { schemaName = schemaName, id = result.ID })); + return this.Redirect(this.Url.PlainAction("Index", "JsonValidator", new { schemaName, id = result.ID })); } @@ -197,7 +199,7 @@ namespace StardewModdingAPI.Web.Controllers return null; // get matching file - DirectoryInfo schemaDir = new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "schemas")); + DirectoryInfo schemaDir = new(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "schemas")); foreach (FileInfo file in schemaDir.EnumerateFiles("*.json")) { if (file.Name.Equals($"{id}.json")) @@ -317,13 +319,10 @@ namespace StardewModdingAPI.Web.Controllers /// <param name="key">The case-insensitive field key.</param> private T GetExtensionField<T>(JSchema schema, string key) { - if (schema.ExtensionData != null) + foreach ((string curKey, JToken value) in schema.ExtensionData) { - foreach ((string curKey, JToken value) in schema.ExtensionData) - { - if (curKey.Equals(key, StringComparison.OrdinalIgnoreCase)) - return value.ToObject<T>(); - } + if (curKey.Equals(key, StringComparison.OrdinalIgnoreCase)) + return value.ToObject<T>(); } return default; diff --git a/src/SMAPI.Web/Controllers/LogParserController.cs b/src/SMAPI.Web/Controllers/LogParserController.cs index db53d942..524cfbcc 100644 --- a/src/SMAPI.Web/Controllers/LogParserController.cs +++ b/src/SMAPI.Web/Controllers/LogParserController.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using System.Text; diff --git a/src/SMAPI.Web/Controllers/ModsApiController.cs b/src/SMAPI.Web/Controllers/ModsApiController.cs index dfe2504b..3dc1e366 100644 --- a/src/SMAPI.Web/Controllers/ModsApiController.cs +++ b/src/SMAPI.Web/Controllers/ModsApiController.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; @@ -135,7 +137,7 @@ namespace StardewModdingAPI.Web.Controllers bool isSmapiBeta = apiVersion.IsPrerelease() && apiVersion.PrereleaseTag.StartsWith("beta"); // get latest versions - ModEntryModel result = new ModEntryModel { ID = search.ID }; + ModEntryModel result = new() { ID = search.ID }; IList<string> errors = new List<string>(); ModEntryVersionModel main = null; ModEntryVersionModel optional = null; diff --git a/src/SMAPI.Web/Controllers/ModsController.cs b/src/SMAPI.Web/Controllers/ModsController.cs index c62ed605..5292e1ce 100644 --- a/src/SMAPI.Web/Controllers/ModsController.cs +++ b/src/SMAPI.Web/Controllers/ModsController.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Linq; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Mvc; diff --git a/src/SMAPI.Web/Framework/AllowLargePostsAttribute.cs b/src/SMAPI.Web/Framework/AllowLargePostsAttribute.cs index 864aa215..108ceff7 100644 --- a/src/SMAPI.Web/Framework/AllowLargePostsAttribute.cs +++ b/src/SMAPI.Web/Framework/AllowLargePostsAttribute.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.Filters; diff --git a/src/SMAPI.Web/Framework/Caching/Cached.cs b/src/SMAPI.Web/Framework/Caching/Cached.cs index 52041a16..aabbf146 100644 --- a/src/SMAPI.Web/Framework/Caching/Cached.cs +++ b/src/SMAPI.Web/Framework/Caching/Cached.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Web.Framework.Caching diff --git a/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs b/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs index 0d912c7b..2020d747 100644 --- a/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs +++ b/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs @@ -1,6 +1,7 @@ +#nullable disable + using System; using StardewModdingAPI.Toolkit.Framework.UpdateData; -using StardewModdingAPI.Web.Framework.Clients; namespace StardewModdingAPI.Web.Framework.Caching.Mods { diff --git a/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs index 9769793c..338562d8 100644 --- a/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs +++ b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs @@ -1,8 +1,9 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; using StardewModdingAPI.Toolkit.Framework.UpdateData; -using StardewModdingAPI.Web.Framework.Clients; namespace StardewModdingAPI.Web.Framework.Caching.Mods { diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs b/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs index 2ab7ea5a..6edafddc 100644 --- a/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs +++ b/src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using StardewModdingAPI.Toolkit.Framework.Clients.Wiki; diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheMemoryRepository.cs b/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheMemoryRepository.cs index d037a123..d1ccb9c7 100644 --- a/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheMemoryRepository.cs +++ b/src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheMemoryRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI.Web/Framework/Caching/Wiki/WikiMetadata.cs b/src/SMAPI.Web/Framework/Caching/Wiki/WikiMetadata.cs index c04de4a5..6ae42488 100644 --- a/src/SMAPI.Web/Framework/Caching/Wiki/WikiMetadata.cs +++ b/src/SMAPI.Web/Framework/Caching/Wiki/WikiMetadata.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.Framework.Caching.Wiki { /// <summary>The model for cached wiki metadata.</summary> diff --git a/src/SMAPI.Web/Framework/Clients/Chucklefish/ChucklefishClient.cs b/src/SMAPI.Web/Framework/Clients/Chucklefish/ChucklefishClient.cs index b8b05878..4d041c1b 100644 --- a/src/SMAPI.Web/Framework/Clients/Chucklefish/ChucklefishClient.cs +++ b/src/SMAPI.Web/Framework/Clients/Chucklefish/ChucklefishClient.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Net; using System.Threading.Tasks; @@ -58,7 +60,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.Chucklefish .GetAsync(string.Format(this.ModPageUrlFormat, parsedId)) .AsString(); } - catch (ApiException ex) when (ex.Status == HttpStatusCode.NotFound || ex.Status == HttpStatusCode.Forbidden) + catch (ApiException ex) when (ex.Status is HttpStatusCode.NotFound or HttpStatusCode.Forbidden) { return page.SetError(RemoteModStatus.DoesNotExist, "Found no Chucklefish mod with this ID."); } @@ -90,7 +92,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.Chucklefish /// <param name="id">The mod ID.</param> private string GetModUrl(uint id) { - UriBuilder builder = new UriBuilder(this.Client.BaseClient.BaseAddress); + UriBuilder builder = new(this.Client.BaseClient.BaseAddress); builder.Path += string.Format(this.ModPageUrlFormat, id); return builder.Uri.ToString(); } diff --git a/src/SMAPI.Web/Framework/Clients/CurseForge/CurseForgeClient.cs b/src/SMAPI.Web/Framework/Clients/CurseForge/CurseForgeClient.cs index d8008721..5ef369d5 100644 --- a/src/SMAPI.Web/Framework/Clients/CurseForge/CurseForgeClient.cs +++ b/src/SMAPI.Web/Framework/Clients/CurseForge/CurseForgeClient.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -17,7 +19,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.CurseForge private readonly IClient Client; /// <summary>A regex pattern which matches a version number in a CurseForge mod file name.</summary> - private readonly Regex VersionInNamePattern = new Regex(@"^(?:.+? | *)v?(\d+\.\d+(?:\.\d+)?(?:-.+?)?) *(?:\.(?:zip|rar|7z))?$", RegexOptions.Compiled); + private readonly Regex VersionInNamePattern = new(@"^(?:.+? | *)v?(\d+\.\d+(?:\.\d+)?(?:-.+?)?) *(?:\.(?:zip|rar|7z))?$", RegexOptions.Compiled); /********* diff --git a/src/SMAPI.Web/Framework/Clients/CurseForge/ResponseModels/ModFileModel.cs b/src/SMAPI.Web/Framework/Clients/CurseForge/ResponseModels/ModFileModel.cs index 9de74847..eabef9f0 100644 --- a/src/SMAPI.Web/Framework/Clients/CurseForge/ResponseModels/ModFileModel.cs +++ b/src/SMAPI.Web/Framework/Clients/CurseForge/ResponseModels/ModFileModel.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.Framework.Clients.CurseForge.ResponseModels { /// <summary>Metadata from the CurseForge API about a mod file.</summary> diff --git a/src/SMAPI.Web/Framework/Clients/CurseForge/ResponseModels/ModModel.cs b/src/SMAPI.Web/Framework/Clients/CurseForge/ResponseModels/ModModel.cs index 48cd185b..a95df7f1 100644 --- a/src/SMAPI.Web/Framework/Clients/CurseForge/ResponseModels/ModModel.cs +++ b/src/SMAPI.Web/Framework/Clients/CurseForge/ResponseModels/ModModel.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.Framework.Clients.CurseForge.ResponseModels { /// <summary>An mod from the CurseForge API.</summary> diff --git a/src/SMAPI.Web/Framework/Clients/GenericModDownload.cs b/src/SMAPI.Web/Framework/Clients/GenericModDownload.cs index f08b471c..919072b0 100644 --- a/src/SMAPI.Web/Framework/Clients/GenericModDownload.cs +++ b/src/SMAPI.Web/Framework/Clients/GenericModDownload.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.Framework.Clients { /// <summary>Generic metadata about a file download on a mod page.</summary> diff --git a/src/SMAPI.Web/Framework/Clients/GenericModPage.cs b/src/SMAPI.Web/Framework/Clients/GenericModPage.cs index a5f7c9b9..4788aa2a 100644 --- a/src/SMAPI.Web/Framework/Clients/GenericModPage.cs +++ b/src/SMAPI.Web/Framework/Clients/GenericModPage.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI.Web/Framework/Clients/GitHub/GitAsset.cs b/src/SMAPI.Web/Framework/Clients/GitHub/GitAsset.cs index 73ce4025..39ebf94e 100644 --- a/src/SMAPI.Web/Framework/Clients/GitHub/GitAsset.cs +++ b/src/SMAPI.Web/Framework/Clients/GitHub/GitAsset.cs @@ -1,3 +1,5 @@ +#nullable disable + using Newtonsoft.Json; namespace StardewModdingAPI.Web.Framework.Clients.GitHub diff --git a/src/SMAPI.Web/Framework/Clients/GitHub/GitHubClient.cs b/src/SMAPI.Web/Framework/Clients/GitHub/GitHubClient.cs index 671f077c..0e68e2c2 100644 --- a/src/SMAPI.Web/Framework/Clients/GitHub/GitHubClient.cs +++ b/src/SMAPI.Web/Framework/Clients/GitHub/GitHubClient.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using System.Net; diff --git a/src/SMAPI.Web/Framework/Clients/GitHub/GitLicense.cs b/src/SMAPI.Web/Framework/Clients/GitHub/GitLicense.cs index 736efbe6..275c775a 100644 --- a/src/SMAPI.Web/Framework/Clients/GitHub/GitLicense.cs +++ b/src/SMAPI.Web/Framework/Clients/GitHub/GitLicense.cs @@ -1,3 +1,5 @@ +#nullable disable + using Newtonsoft.Json; namespace StardewModdingAPI.Web.Framework.Clients.GitHub diff --git a/src/SMAPI.Web/Framework/Clients/GitHub/GitRelease.cs b/src/SMAPI.Web/Framework/Clients/GitHub/GitRelease.cs index d0db5297..383775d2 100644 --- a/src/SMAPI.Web/Framework/Clients/GitHub/GitRelease.cs +++ b/src/SMAPI.Web/Framework/Clients/GitHub/GitRelease.cs @@ -1,3 +1,5 @@ +#nullable disable + using Newtonsoft.Json; namespace StardewModdingAPI.Web.Framework.Clients.GitHub diff --git a/src/SMAPI.Web/Framework/Clients/GitHub/GitRepo.cs b/src/SMAPI.Web/Framework/Clients/GitHub/GitRepo.cs index 7d80576e..5b5ce6a6 100644 --- a/src/SMAPI.Web/Framework/Clients/GitHub/GitRepo.cs +++ b/src/SMAPI.Web/Framework/Clients/GitHub/GitRepo.cs @@ -1,3 +1,5 @@ +#nullable disable + using Newtonsoft.Json; namespace StardewModdingAPI.Web.Framework.Clients.GitHub diff --git a/src/SMAPI.Web/Framework/Clients/GitHub/IGitHubClient.cs b/src/SMAPI.Web/Framework/Clients/GitHub/IGitHubClient.cs index 0d6f4643..e1961416 100644 --- a/src/SMAPI.Web/Framework/Clients/GitHub/IGitHubClient.cs +++ b/src/SMAPI.Web/Framework/Clients/GitHub/IGitHubClient.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading.Tasks; diff --git a/src/SMAPI.Web/Framework/Clients/IModSiteClient.cs b/src/SMAPI.Web/Framework/Clients/IModSiteClient.cs index 33277711..2cd1f635 100644 --- a/src/SMAPI.Web/Framework/Clients/IModSiteClient.cs +++ b/src/SMAPI.Web/Framework/Clients/IModSiteClient.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading.Tasks; using StardewModdingAPI.Toolkit.Framework.UpdateData; diff --git a/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropClient.cs b/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropClient.cs index 3a1c5b9d..1a11a606 100644 --- a/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropClient.cs +++ b/src/SMAPI.Web/Framework/Clients/ModDrop/ModDropClient.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Threading.Tasks; using Pathoschild.Http.Client; diff --git a/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/FileDataModel.cs b/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/FileDataModel.cs index b01196f4..dd6a95e0 100644 --- a/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/FileDataModel.cs +++ b/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/FileDataModel.cs @@ -1,3 +1,5 @@ +#nullable disable + using Newtonsoft.Json; namespace StardewModdingAPI.Web.Framework.Clients.ModDrop.ResponseModels diff --git a/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModDataModel.cs b/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModDataModel.cs index cfdd6a4e..6cae16d9 100644 --- a/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModDataModel.cs +++ b/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModDataModel.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.Framework.Clients.ModDrop.ResponseModels { /// <summary>Metadata about a mod from the ModDrop API.</summary> diff --git a/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModListModel.cs b/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModListModel.cs index 7f692ca1..445e25cb 100644 --- a/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModListModel.cs +++ b/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModListModel.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; namespace StardewModdingAPI.Web.Framework.Clients.ModDrop.ResponseModels diff --git a/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModModel.cs b/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModModel.cs index 9f4b2c6f..8869193e 100644 --- a/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModModel.cs +++ b/src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModModel.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.Framework.Clients.ModDrop.ResponseModels { /// <summary>An entry in a mod list from the ModDrop API.</summary> diff --git a/src/SMAPI.Web/Framework/Clients/Nexus/NexusClient.cs b/src/SMAPI.Web/Framework/Clients/Nexus/NexusClient.cs index 4ba94f81..dd0bb94f 100644 --- a/src/SMAPI.Web/Framework/Clients/Nexus/NexusClient.cs +++ b/src/SMAPI.Web/Framework/Clients/Nexus/NexusClient.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -75,7 +77,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.Nexus mod = await this.GetModFromApiAsync(parsedId); // page doesn't exist - if (mod == null || mod.Status == NexusModStatus.Hidden || mod.Status == NexusModStatus.NotPublished) + if (mod == null || mod.Status is NexusModStatus.Hidden or NexusModStatus.NotPublished) return page.SetError(RemoteModStatus.DoesNotExist, "Found no Nexus mod with this ID."); // return info @@ -195,7 +197,7 @@ namespace StardewModdingAPI.Web.Framework.Clients.Nexus /// <param name="id">The mod ID.</param> private string GetModUrl(uint id) { - UriBuilder builder = new UriBuilder(this.WebClient.BaseClient.BaseAddress); + UriBuilder builder = new(this.WebClient.BaseClient.BaseAddress); builder.Path += string.Format(this.WebModUrlFormat, id); return builder.Uri.ToString(); } diff --git a/src/SMAPI.Web/Framework/Clients/Nexus/ResponseModels/NexusMod.cs b/src/SMAPI.Web/Framework/Clients/Nexus/ResponseModels/NexusMod.cs index aef90ede..358c4633 100644 --- a/src/SMAPI.Web/Framework/Clients/Nexus/ResponseModels/NexusMod.cs +++ b/src/SMAPI.Web/Framework/Clients/Nexus/ResponseModels/NexusMod.cs @@ -1,3 +1,5 @@ +#nullable disable + using Newtonsoft.Json; namespace StardewModdingAPI.Web.Framework.Clients.Nexus.ResponseModels diff --git a/src/SMAPI.Web/Framework/Clients/Pastebin/IPastebinClient.cs b/src/SMAPI.Web/Framework/Clients/Pastebin/IPastebinClient.cs index 431fed7b..03c78e01 100644 --- a/src/SMAPI.Web/Framework/Clients/Pastebin/IPastebinClient.cs +++ b/src/SMAPI.Web/Framework/Clients/Pastebin/IPastebinClient.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading.Tasks; diff --git a/src/SMAPI.Web/Framework/Clients/Pastebin/PasteInfo.cs b/src/SMAPI.Web/Framework/Clients/Pastebin/PasteInfo.cs index 813ea115..2d48a7ae 100644 --- a/src/SMAPI.Web/Framework/Clients/Pastebin/PasteInfo.cs +++ b/src/SMAPI.Web/Framework/Clients/Pastebin/PasteInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.Framework.Clients.Pastebin { /// <summary>The response for a get-paste request.</summary> diff --git a/src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs b/src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs index 1be00be7..d0cdf374 100644 --- a/src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs +++ b/src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Net; using System.Threading.Tasks; diff --git a/src/SMAPI.Web/Framework/Compression/GzipHelper.cs b/src/SMAPI.Web/Framework/Compression/GzipHelper.cs index 676d660d..843b7735 100644 --- a/src/SMAPI.Web/Framework/Compression/GzipHelper.cs +++ b/src/SMAPI.Web/Framework/Compression/GzipHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.IO; using System.IO.Compression; @@ -29,9 +31,9 @@ namespace StardewModdingAPI.Web.Framework.Compression // compressed byte[] compressedData; - using (MemoryStream stream = new MemoryStream()) + using (MemoryStream stream = new()) { - using (GZipStream zipStream = new GZipStream(stream, CompressionLevel.Optimal, leaveOpen: true)) + using (GZipStream zipStream = new(stream, CompressionLevel.Optimal, leaveOpen: true)) zipStream.Write(buffer, 0, buffer.Length); stream.Position = 0; @@ -69,7 +71,7 @@ namespace StardewModdingAPI.Web.Framework.Compression return rawText; // decompress - using MemoryStream memoryStream = new MemoryStream(); + using MemoryStream memoryStream = new(); { // read length prefix int dataLength = BitConverter.ToInt32(zipBuffer, 0); @@ -78,7 +80,7 @@ namespace StardewModdingAPI.Web.Framework.Compression // read data byte[] buffer = new byte[dataLength]; memoryStream.Position = 0; - using (GZipStream gZipStream = new GZipStream(memoryStream, CompressionMode.Decompress)) + using (GZipStream gZipStream = new(memoryStream, CompressionMode.Decompress)) gZipStream.Read(buffer, 0, buffer.Length); // return original string diff --git a/src/SMAPI.Web/Framework/Compression/IGzipHelper.cs b/src/SMAPI.Web/Framework/Compression/IGzipHelper.cs index a000865e..e1ec9b67 100644 --- a/src/SMAPI.Web/Framework/Compression/IGzipHelper.cs +++ b/src/SMAPI.Web/Framework/Compression/IGzipHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.Framework.Compression { /// <summary>Handles GZip compression logic.</summary> diff --git a/src/SMAPI.Web/Framework/ConfigModels/ApiClientsConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/ApiClientsConfig.cs index 878130bf..3730a9db 100644 --- a/src/SMAPI.Web/Framework/ConfigModels/ApiClientsConfig.cs +++ b/src/SMAPI.Web/Framework/ConfigModels/ApiClientsConfig.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.Framework.ConfigModels { /// <summary>The config settings for the API clients.</summary> diff --git a/src/SMAPI.Web/Framework/ConfigModels/ModOverrideConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/ModOverrideConfig.cs index f382d7b5..682c97e6 100644 --- a/src/SMAPI.Web/Framework/ConfigModels/ModOverrideConfig.cs +++ b/src/SMAPI.Web/Framework/ConfigModels/ModOverrideConfig.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.Framework.ConfigModels { /// <summary>Override update-check metadata for a mod.</summary> diff --git a/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs index aea695b8..e525e09a 100644 --- a/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs +++ b/src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.Framework.ConfigModels { /// <summary>The config settings for mod update checks.</summary> diff --git a/src/SMAPI.Web/Framework/ConfigModels/SiteConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/SiteConfig.cs index 664dbef3..ef6c2659 100644 --- a/src/SMAPI.Web/Framework/ConfigModels/SiteConfig.cs +++ b/src/SMAPI.Web/Framework/ConfigModels/SiteConfig.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.Framework.ConfigModels { /// <summary>The site config settings.</summary> diff --git a/src/SMAPI.Web/Framework/ConfigModels/SmapiInfoConfig.cs b/src/SMAPI.Web/Framework/ConfigModels/SmapiInfoConfig.cs index d69fabb3..dbf58817 100644 --- a/src/SMAPI.Web/Framework/ConfigModels/SmapiInfoConfig.cs +++ b/src/SMAPI.Web/Framework/ConfigModels/SmapiInfoConfig.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.Framework.ConfigModels { /// <summary>The update-check config for SMAPI's own update checks.</summary> diff --git a/src/SMAPI.Web/Framework/Extensions.cs b/src/SMAPI.Web/Framework/Extensions.cs index 5305b142..a72c12c1 100644 --- a/src/SMAPI.Web/Framework/Extensions.cs +++ b/src/SMAPI.Web/Framework/Extensions.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using JetBrains.Annotations; using Microsoft.AspNetCore.Html; @@ -29,7 +31,7 @@ namespace StardewModdingAPI.Web.Framework public static string PlainAction(this IUrlHelper helper, [AspMvcAction] string action, [AspMvcController] string controller, object values = null, bool absoluteUrl = false) { // get route values - RouteValueDictionary valuesDict = new RouteValueDictionary(values); + RouteValueDictionary valuesDict = new(values); foreach (var value in helper.ActionContext.RouteData.Values) { if (!valuesDict.ContainsKey(value.Key)) @@ -45,7 +47,7 @@ namespace StardewModdingAPI.Web.Framework if (absoluteUrl) { HttpRequest request = helper.ActionContext.HttpContext.Request; - Uri baseUri = new Uri($"{request.Scheme}://{request.Host}"); + Uri baseUri = new($"{request.Scheme}://{request.Host}"); url = new Uri(baseUri, url).ToString(); } diff --git a/src/SMAPI.Web/Framework/IModDownload.cs b/src/SMAPI.Web/Framework/IModDownload.cs index dc058bcb..b8d1f62c 100644 --- a/src/SMAPI.Web/Framework/IModDownload.cs +++ b/src/SMAPI.Web/Framework/IModDownload.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.Framework { /// <summary>Generic metadata about a file download on a mod page.</summary> diff --git a/src/SMAPI.Web/Framework/IModPage.cs b/src/SMAPI.Web/Framework/IModPage.cs index e66d401f..68220b49 100644 --- a/src/SMAPI.Web/Framework/IModPage.cs +++ b/src/SMAPI.Web/Framework/IModPage.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using StardewModdingAPI.Toolkit.Framework.UpdateData; diff --git a/src/SMAPI.Web/Framework/InternalControllerFeatureProvider.cs b/src/SMAPI.Web/Framework/InternalControllerFeatureProvider.cs index 2c24c610..98738a82 100644 --- a/src/SMAPI.Web/Framework/InternalControllerFeatureProvider.cs +++ b/src/SMAPI.Web/Framework/InternalControllerFeatureProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Reflection; using Microsoft.AspNetCore.Mvc; diff --git a/src/SMAPI.Web/Framework/JobDashboardAuthorizationFilter.cs b/src/SMAPI.Web/Framework/JobDashboardAuthorizationFilter.cs index 385c0c91..8db43dca 100644 --- a/src/SMAPI.Web/Framework/JobDashboardAuthorizationFilter.cs +++ b/src/SMAPI.Web/Framework/JobDashboardAuthorizationFilter.cs @@ -1,3 +1,5 @@ +#nullable disable + using Hangfire.Dashboard; namespace StardewModdingAPI.Web.Framework @@ -9,7 +11,7 @@ namespace StardewModdingAPI.Web.Framework ** Fields *********/ /// <summary>An authorization filter that allows local requests.</summary> - private static readonly LocalRequestsOnlyAuthorizationFilter LocalRequestsOnlyFilter = new LocalRequestsOnlyAuthorizationFilter(); + private static readonly LocalRequestsOnlyAuthorizationFilter LocalRequestsOnlyFilter = new(); /********* diff --git a/src/SMAPI.Web/Framework/LogParsing/LogMessageBuilder.cs b/src/SMAPI.Web/Framework/LogParsing/LogMessageBuilder.cs index 992876ef..1b692e63 100644 --- a/src/SMAPI.Web/Framework/LogParsing/LogMessageBuilder.cs +++ b/src/SMAPI.Web/Framework/LogParsing/LogMessageBuilder.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Text; using StardewModdingAPI.Web.Framework.LogParsing.Models; @@ -23,7 +25,7 @@ namespace StardewModdingAPI.Web.Framework.LogParsing public string Mod { get; set; } /// <summary>The text for the next log message.</summary> - private readonly StringBuilder Text = new StringBuilder(); + private readonly StringBuilder Text = new(); /********* diff --git a/src/SMAPI.Web/Framework/LogParsing/LogParseException.cs b/src/SMAPI.Web/Framework/LogParsing/LogParseException.cs index 5d4c8c08..4ee58433 100644 --- a/src/SMAPI.Web/Framework/LogParsing/LogParseException.cs +++ b/src/SMAPI.Web/Framework/LogParsing/LogParseException.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Web.Framework.LogParsing @@ -10,6 +12,7 @@ namespace StardewModdingAPI.Web.Framework.LogParsing *********/ /// <summary>Construct an instance.</summary> /// <param name="message">The user-friendly error message.</param> - public LogParseException(string message) : base(message) { } + public LogParseException(string message) + : base(message) { } } } diff --git a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs index 887d0105..4e61ac95 100644 --- a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs +++ b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; @@ -14,38 +16,38 @@ namespace StardewModdingAPI.Web.Framework.LogParsing ** Fields *********/ /// <summary>A regex pattern matching the start of a SMAPI message.</summary> - private readonly Regex MessageHeaderPattern = new Regex(@"^\[(?<time>\d\d[:\.]\d\d[:\.]\d\d) (?<level>[a-z]+)(?: +screen_(?<screen>\d+))? +(?<modName>[^\]]+)\] ", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private readonly Regex MessageHeaderPattern = new(@"^\[(?<time>\d\d[:\.]\d\d[:\.]\d\d) (?<level>[a-z]+)(?: +screen_(?<screen>\d+))? +(?<modName>[^\]]+)\] ", RegexOptions.Compiled | RegexOptions.IgnoreCase); /// <summary>A regex pattern matching SMAPI's initial platform info message.</summary> - private readonly Regex InfoLinePattern = new Regex(@"^SMAPI (?<apiVersion>.+) with Stardew Valley (?<gameVersion>.+) on (?<os>.+)", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private readonly Regex InfoLinePattern = new(@"^SMAPI (?<apiVersion>.+) with Stardew Valley (?<gameVersion>.+) on (?<os>.+)", RegexOptions.Compiled | RegexOptions.IgnoreCase); /// <summary>A regex pattern matching SMAPI's mod folder path line.</summary> - private readonly Regex ModPathPattern = new Regex(@"^Mods go here: (?<path>.+)", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private readonly Regex ModPathPattern = new(@"^Mods go here: (?<path>.+)", RegexOptions.Compiled | RegexOptions.IgnoreCase); /// <summary>A regex pattern matching SMAPI's log timestamp line.</summary> - private readonly Regex LogStartedAtPattern = new Regex(@"^Log started at (?<timestamp>.+) UTC", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private readonly Regex LogStartedAtPattern = new(@"^Log started at (?<timestamp>.+) UTC", RegexOptions.Compiled | RegexOptions.IgnoreCase); /// <summary>A regex pattern matching the start of SMAPI's mod list.</summary> - private readonly Regex ModListStartPattern = new Regex(@"^Loaded \d+ mods:$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private readonly Regex ModListStartPattern = new(@"^Loaded \d+ mods:$", RegexOptions.Compiled | RegexOptions.IgnoreCase); /// <summary>A regex pattern matching an entry in SMAPI's mod list.</summary> /// <remarks>The author name and description are optional.</remarks> - private readonly Regex ModListEntryPattern = new Regex(@"^ (?<name>.+?) (?<version>[^\s]+)(?: by (?<author>[^\|]+))?(?: \| (?<description>.+))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private readonly Regex ModListEntryPattern = new(@"^ (?<name>.+?) (?<version>[^\s]+)(?: by (?<author>[^\|]+))?(?: \| (?<description>.+))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); /// <summary>A regex pattern matching the start of SMAPI's content pack list.</summary> - private readonly Regex ContentPackListStartPattern = new Regex(@"^Loaded \d+ content packs:$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private readonly Regex ContentPackListStartPattern = new(@"^Loaded \d+ content packs:$", RegexOptions.Compiled | RegexOptions.IgnoreCase); /// <summary>A regex pattern matching an entry in SMAPI's content pack list.</summary> - private readonly Regex ContentPackListEntryPattern = new Regex(@"^ (?<name>.+?) (?<version>[^\s]+)(?: by (?<author>[^\|]+))? \| for (?<for>[^\|]+)(?: \| (?<description>.+))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private readonly Regex ContentPackListEntryPattern = new(@"^ (?<name>.+?) (?<version>[^\s]+)(?: by (?<author>[^\|]+))? \| for (?<for>[^\|]+)(?: \| (?<description>.+))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase); /// <summary>A regex pattern matching the start of SMAPI's mod update list.</summary> - private readonly Regex ModUpdateListStartPattern = new Regex(@"^You can update \d+ mods?:$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private readonly Regex ModUpdateListStartPattern = new(@"^You can update \d+ mods?:$", RegexOptions.Compiled | RegexOptions.IgnoreCase); /// <summary>A regex pattern matching an entry in SMAPI's mod update list.</summary> - private readonly Regex ModUpdateListEntryPattern = new Regex(@"^ (?<name>.+) (?<version>[^\s]+): (?<link>.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private readonly Regex ModUpdateListEntryPattern = new(@"^ (?<name>.+) (?<version>[^\s]+): (?<link>.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); /// <summary>A regex pattern matching SMAPI's update line.</summary> - private readonly Regex SmapiUpdatePattern = new Regex(@"^You can update SMAPI to (?<version>[^\s]+): (?<link>.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private readonly Regex SmapiUpdatePattern = new(@"^You can update SMAPI to (?<version>[^\s]+): (?<link>.+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); /********* @@ -69,7 +71,7 @@ namespace StardewModdingAPI.Web.Framework.LogParsing } // init log - ParsedLog log = new ParsedLog + ParsedLog log = new() { IsValid = true, RawText = logText, @@ -77,8 +79,8 @@ namespace StardewModdingAPI.Web.Framework.LogParsing }; // parse log messages - LogModInfo smapiMod = new LogModInfo { Name = "SMAPI", Author = "Pathoschild", Description = "", Loaded = true }; - LogModInfo gameMod = new LogModInfo { Name = "game", Author = "", Description = "", Loaded = true }; + LogModInfo smapiMod = new() { Name = "SMAPI", Author = "Pathoschild", Description = "", Loaded = true }; + LogModInfo gameMod = new() { Name = "game", Author = "", Description = "", Loaded = true }; IDictionary<string, List<LogModInfo>> mods = new Dictionary<string, List<LogModInfo>>(); bool inModList = false; bool inContentPackList = false; @@ -211,7 +213,7 @@ namespace StardewModdingAPI.Web.Framework.LogParsing { Match match = this.ModPathPattern.Match(message.Text); log.ModPath = match.Groups["path"].Value; - int lastDelimiterPos = log.ModPath.LastIndexOfAny(new char[] { '/', '\\' }); + int lastDelimiterPos = log.ModPath.LastIndexOfAny(new[] { '/', '\\' }); log.GamePath = lastDelimiterPos >= 0 ? log.ModPath.Substring(0, lastDelimiterPos) : log.ModPath; @@ -288,8 +290,8 @@ namespace StardewModdingAPI.Web.Framework.LogParsing /// <exception cref="LogParseException">The log text can't be parsed successfully.</exception> private IEnumerable<LogMessage> GetMessages(string logText) { - LogMessageBuilder builder = new LogMessageBuilder(); - using StringReader reader = new StringReader(logText); + LogMessageBuilder builder = new(); + using StringReader reader = new(logText); while (true) { // read line diff --git a/src/SMAPI.Web/Framework/LogParsing/Models/LogMessage.cs b/src/SMAPI.Web/Framework/LogParsing/Models/LogMessage.cs index 1e08be78..57d28755 100644 --- a/src/SMAPI.Web/Framework/LogParsing/Models/LogMessage.cs +++ b/src/SMAPI.Web/Framework/LogParsing/Models/LogMessage.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.Framework.LogParsing.Models { /// <summary>A parsed log message.</summary> diff --git a/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs b/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs index 92bfe5c7..349312df 100644 --- a/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs +++ b/src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.Framework.LogParsing.Models { /// <summary>Metadata about a mod or content pack in the log.</summary> diff --git a/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs b/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs index 693a16ec..dae91d84 100644 --- a/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs +++ b/src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Web.Framework.LogParsing.Models diff --git a/src/SMAPI.Web/Framework/ModInfoModel.cs b/src/SMAPI.Web/Framework/ModInfoModel.cs index 7845b8c5..021d14fb 100644 --- a/src/SMAPI.Web/Framework/ModInfoModel.cs +++ b/src/SMAPI.Web/Framework/ModInfoModel.cs @@ -1,4 +1,4 @@ -using StardewModdingAPI.Web.Framework.Clients; +#nullable disable namespace StardewModdingAPI.Web.Framework { diff --git a/src/SMAPI.Web/Framework/ModSiteManager.cs b/src/SMAPI.Web/Framework/ModSiteManager.cs index a2b92aa4..2d6755d8 100644 --- a/src/SMAPI.Web/Framework/ModSiteManager.cs +++ b/src/SMAPI.Web/Framework/ModSiteManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI.Web/Framework/RedirectRules/RedirectHostsToUrlsRule.cs b/src/SMAPI.Web/Framework/RedirectRules/RedirectHostsToUrlsRule.cs index d75ee791..fe601524 100644 --- a/src/SMAPI.Web/Framework/RedirectRules/RedirectHostsToUrlsRule.cs +++ b/src/SMAPI.Web/Framework/RedirectRules/RedirectHostsToUrlsRule.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Net; using Microsoft.AspNetCore.Rewrite; @@ -37,8 +39,6 @@ namespace StardewModdingAPI.Web.Framework.RedirectRules { // get requested host string host = context.HttpContext.Request.Host.Host; - if (host == null) - return null; // get new host host = this.Map(host); diff --git a/src/SMAPI.Web/Framework/RedirectRules/RedirectMatchRule.cs b/src/SMAPI.Web/Framework/RedirectRules/RedirectMatchRule.cs index 6e81c4ca..81a265c9 100644 --- a/src/SMAPI.Web/Framework/RedirectRules/RedirectMatchRule.cs +++ b/src/SMAPI.Web/Framework/RedirectRules/RedirectMatchRule.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Net; using Microsoft.AspNetCore.Http; diff --git a/src/SMAPI.Web/Framework/RedirectRules/RedirectPathsToUrlsRule.cs b/src/SMAPI.Web/Framework/RedirectRules/RedirectPathsToUrlsRule.cs index d9d44641..cb3e53ef 100644 --- a/src/SMAPI.Web/Framework/RedirectRules/RedirectPathsToUrlsRule.cs +++ b/src/SMAPI.Web/Framework/RedirectRules/RedirectPathsToUrlsRule.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Linq; using System.Net; diff --git a/src/SMAPI.Web/Framework/RedirectRules/RedirectToHttpsRule.cs b/src/SMAPI.Web/Framework/RedirectRules/RedirectToHttpsRule.cs index 2a503ae3..dd7c836f 100644 --- a/src/SMAPI.Web/Framework/RedirectRules/RedirectToHttpsRule.cs +++ b/src/SMAPI.Web/Framework/RedirectRules/RedirectToHttpsRule.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Net; using Microsoft.AspNetCore.Http; @@ -22,7 +24,7 @@ namespace StardewModdingAPI.Web.Framework.RedirectRules /// <param name="except">Matches requests which should be ignored.</param> public RedirectToHttpsRule(Func<HttpRequest, bool> except = null) { - this.Except = except ?? (req => false); + this.Except = except ?? (_ => false); this.StatusCode = HttpStatusCode.RedirectKeepVerb; } diff --git a/src/SMAPI.Web/Framework/Storage/IStorageProvider.cs b/src/SMAPI.Web/Framework/Storage/IStorageProvider.cs index dfc1fb47..2eca4845 100644 --- a/src/SMAPI.Web/Framework/Storage/IStorageProvider.cs +++ b/src/SMAPI.Web/Framework/Storage/IStorageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Threading.Tasks; namespace StardewModdingAPI.Web.Framework.Storage diff --git a/src/SMAPI.Web/Framework/Storage/StorageProvider.cs b/src/SMAPI.Web/Framework/Storage/StorageProvider.cs index c6f8bac1..0177e602 100644 --- a/src/SMAPI.Web/Framework/Storage/StorageProvider.cs +++ b/src/SMAPI.Web/Framework/Storage/StorageProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; @@ -103,7 +105,7 @@ namespace StardewModdingAPI.Web.Framework.Storage // fetch file Response<BlobDownloadInfo> response = await blob.DownloadAsync(); using BlobDownloadInfo result = response.Value; - using StreamReader reader = new StreamReader(result.Content); + using StreamReader reader = new(result.Content); DateTimeOffset expiry = result.Details.LastModified + TimeSpan.FromDays(this.ExpiryDays); string content = this.GzipHelper.DecompressString(reader.ReadToEnd()); @@ -130,7 +132,7 @@ namespace StardewModdingAPI.Web.Framework.Storage else { // get file - FileInfo file = new FileInfo(this.GetDevFilePath(id)); + FileInfo file = new(this.GetDevFilePath(id)); if (file.Exists && file.LastWriteTimeUtc.AddDays(this.ExpiryDays) < DateTime.UtcNow) // expired file.Delete(); if (!file.Exists) diff --git a/src/SMAPI.Web/Framework/Storage/StoredFileInfo.cs b/src/SMAPI.Web/Framework/Storage/StoredFileInfo.cs index 30676c88..cd941c94 100644 --- a/src/SMAPI.Web/Framework/Storage/StoredFileInfo.cs +++ b/src/SMAPI.Web/Framework/Storage/StoredFileInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Web.Framework.Storage diff --git a/src/SMAPI.Web/Framework/Storage/UploadResult.cs b/src/SMAPI.Web/Framework/Storage/UploadResult.cs index 483c1769..b1eedd59 100644 --- a/src/SMAPI.Web/Framework/Storage/UploadResult.cs +++ b/src/SMAPI.Web/Framework/Storage/UploadResult.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.Framework.Storage { /// <summary>The result of an attempt to upload a file.</summary> diff --git a/src/SMAPI.Web/Framework/VersionConstraint.cs b/src/SMAPI.Web/Framework/VersionConstraint.cs index f0c57c41..f230a95b 100644 --- a/src/SMAPI.Web/Framework/VersionConstraint.cs +++ b/src/SMAPI.Web/Framework/VersionConstraint.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; diff --git a/src/SMAPI.Web/Program.cs b/src/SMAPI.Web/Program.cs index 1fdd3185..5134791a 100644 --- a/src/SMAPI.Web/Program.cs +++ b/src/SMAPI.Web/Program.cs @@ -1,3 +1,5 @@ +#nullable disable + using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; diff --git a/src/SMAPI.Web/Startup.cs b/src/SMAPI.Web/Startup.cs index d8561172..0199938d 100644 --- a/src/SMAPI.Web/Startup.cs +++ b/src/SMAPI.Web/Startup.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Net; using Hangfire; @@ -81,7 +83,7 @@ namespace StardewModdingAPI.Web // init Hangfire services - .AddHangfire((serv, config) => + .AddHangfire((_, config) => { config .SetDataCompatibilityLevel(CompatibilityLevel.Version_170) diff --git a/src/SMAPI.Web/ViewModels/IndexModel.cs b/src/SMAPI.Web/ViewModels/IndexModel.cs index d8d2d27f..2283acd9 100644 --- a/src/SMAPI.Web/ViewModels/IndexModel.cs +++ b/src/SMAPI.Web/ViewModels/IndexModel.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.ViewModels { /// <summary>The view model for the index page.</summary> diff --git a/src/SMAPI.Web/ViewModels/IndexVersionModel.cs b/src/SMAPI.Web/ViewModels/IndexVersionModel.cs index 4f63b979..1f5d4ec0 100644 --- a/src/SMAPI.Web/ViewModels/IndexVersionModel.cs +++ b/src/SMAPI.Web/ViewModels/IndexVersionModel.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.ViewModels { /// <summary>The fields for a SMAPI version.</summary> diff --git a/src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorErrorModel.cs b/src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorErrorModel.cs index 62b95501..3c63b730 100644 --- a/src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorErrorModel.cs +++ b/src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorErrorModel.cs @@ -1,3 +1,5 @@ +#nullable disable + using Newtonsoft.Json.Schema; namespace StardewModdingAPI.Web.ViewModels.JsonValidator diff --git a/src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorModel.cs b/src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorModel.cs index e659b389..2543807f 100644 --- a/src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorModel.cs +++ b/src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorModel.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorRequestModel.cs b/src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorRequestModel.cs index c8e851bf..43114d94 100644 --- a/src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorRequestModel.cs +++ b/src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorRequestModel.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.ViewModels.JsonValidator { /// <summary>The view model for a JSON validation request.</summary> diff --git a/src/SMAPI.Web/ViewModels/LogParserModel.cs b/src/SMAPI.Web/ViewModels/LogParserModel.cs index 0b6d7722..c768a08c 100644 --- a/src/SMAPI.Web/ViewModels/LogParserModel.cs +++ b/src/SMAPI.Web/ViewModels/LogParserModel.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -14,7 +16,7 @@ namespace StardewModdingAPI.Web.ViewModels ** Fields *********/ /// <summary>A regex pattern matching characters to remove from a mod name to create the slug ID.</summary> - private readonly Regex SlugInvalidCharPattern = new Regex("[^a-z0-9]", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private readonly Regex SlugInvalidCharPattern = new("[^a-z0-9]", RegexOptions.Compiled | RegexOptions.IgnoreCase); /********* diff --git a/src/SMAPI.Web/ViewModels/ModCompatibilityModel.cs b/src/SMAPI.Web/ViewModels/ModCompatibilityModel.cs index 85bf1e46..2af30cc3 100644 --- a/src/SMAPI.Web/ViewModels/ModCompatibilityModel.cs +++ b/src/SMAPI.Web/ViewModels/ModCompatibilityModel.cs @@ -1,3 +1,5 @@ +#nullable disable + using StardewModdingAPI.Toolkit.Framework.Clients.Wiki; namespace StardewModdingAPI.Web.ViewModels diff --git a/src/SMAPI.Web/ViewModels/ModLinkModel.cs b/src/SMAPI.Web/ViewModels/ModLinkModel.cs index 97dd215c..3039702e 100644 --- a/src/SMAPI.Web/ViewModels/ModLinkModel.cs +++ b/src/SMAPI.Web/ViewModels/ModLinkModel.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.ViewModels { /// <summary>Metadata about a link.</summary> diff --git a/src/SMAPI.Web/ViewModels/ModListModel.cs b/src/SMAPI.Web/ViewModels/ModListModel.cs index 6b8279c1..f0cf0c3a 100644 --- a/src/SMAPI.Web/ViewModels/ModListModel.cs +++ b/src/SMAPI.Web/ViewModels/ModListModel.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI.Web/ViewModels/ModModel.cs b/src/SMAPI.Web/ViewModels/ModModel.cs index 575d596a..d0d7373b 100644 --- a/src/SMAPI.Web/ViewModels/ModModel.cs +++ b/src/SMAPI.Web/ViewModels/ModModel.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Linq; using StardewModdingAPI.Toolkit.Framework.Clients.Wiki; diff --git a/src/SMAPI.Web/Views/Index/Index.cshtml b/src/SMAPI.Web/Views/Index/Index.cshtml index 669cfd99..9841ca42 100644 --- a/src/SMAPI.Web/Views/Index/Index.cshtml +++ b/src/SMAPI.Web/Views/Index/Index.cshtml @@ -1,3 +1,7 @@ +@{ + #nullable disable +} + @using Microsoft.Extensions.Options @using StardewModdingAPI.Web.Framework @using StardewModdingAPI.Web.Framework.ConfigModels diff --git a/src/SMAPI.Web/Views/Index/Privacy.cshtml b/src/SMAPI.Web/Views/Index/Privacy.cshtml index fd78f908..1dc327d7 100644 --- a/src/SMAPI.Web/Views/Index/Privacy.cshtml +++ b/src/SMAPI.Web/Views/Index/Privacy.cshtml @@ -1,3 +1,7 @@ +@{ + #nullable disable +} + @using Microsoft.Extensions.Options @using StardewModdingAPI.Web.Framework @using StardewModdingAPI.Web.Framework.ConfigModels diff --git a/src/SMAPI.Web/Views/JsonValidator/Index.cshtml b/src/SMAPI.Web/Views/JsonValidator/Index.cshtml index 1db79857..5e38e4dc 100644 --- a/src/SMAPI.Web/Views/JsonValidator/Index.cshtml +++ b/src/SMAPI.Web/Views/JsonValidator/Index.cshtml @@ -1,3 +1,7 @@ +@{ + #nullable disable +} + @using Humanizer @using StardewModdingAPI.Web.Framework @using StardewModdingAPI.Web.ViewModels.JsonValidator diff --git a/src/SMAPI.Web/Views/LogParser/Index.cshtml b/src/SMAPI.Web/Views/LogParser/Index.cshtml index b54867b1..c26ec230 100644 --- a/src/SMAPI.Web/Views/LogParser/Index.cshtml +++ b/src/SMAPI.Web/Views/LogParser/Index.cshtml @@ -1,3 +1,7 @@ +@{ + #nullable disable +} + @using Humanizer @using StardewModdingAPI.Toolkit.Utilities @using StardewModdingAPI.Web.Framework @@ -40,8 +44,8 @@ smapi.logParser({ logStarted: new Date(@this.ForJson(log?.Timestamp)), showPopup: @this.ForJson(log == null), - showMods: @this.ForJson(log?.Mods?.Select(p => Model.GetSlug(p.Name)).Distinct().ToDictionary(slug => slug, slug => true)), - showSections: @this.ForJson(Enum.GetNames(typeof(LogSection)).ToDictionary(section => section, section => false)), + showMods: @this.ForJson(log?.Mods?.Select(p => Model.GetSlug(p.Name)).Distinct().ToDictionary(slug => slug, _ => true)), + showSections: @this.ForJson(Enum.GetNames(typeof(LogSection)).ToDictionary(section => section, _ => false)), showLevels: @this.ForJson(defaultFilters), enableFilters: @this.ForJson(!Model.ShowRaw), screenIds: @this.ForJson(screenIds) @@ -207,7 +211,7 @@ else if (log?.IsValid == true) @if (mod.HasUpdate) { <a href="@mod.UpdateLink" target="_blank"> - @(mod.Version == null ? @mod.UpdateVersion : $"{mod.Version} → {mod.UpdateVersion}") + @(mod.Version == null ? mod.UpdateVersion : $"{mod.Version} → {mod.UpdateVersion}") </a> } else diff --git a/src/SMAPI.Web/Views/Mods/Index.cshtml b/src/SMAPI.Web/Views/Mods/Index.cshtml index 416468e4..4b6400ad 100644 --- a/src/SMAPI.Web/Views/Mods/Index.cshtml +++ b/src/SMAPI.Web/Views/Mods/Index.cshtml @@ -1,3 +1,7 @@ +@{ + #nullable disable +} + @using Humanizer @using Humanizer.Localisation @using StardewModdingAPI.Web.Framework diff --git a/src/SMAPI.Web/Views/Shared/_Layout.cshtml b/src/SMAPI.Web/Views/Shared/_Layout.cshtml index 67dcd3b3..7c86a68c 100644 --- a/src/SMAPI.Web/Views/Shared/_Layout.cshtml +++ b/src/SMAPI.Web/Views/Shared/_Layout.cshtml @@ -1,3 +1,7 @@ +@{ + #nullable disable +} + @using Microsoft.Extensions.Options @using StardewModdingAPI.Web.Framework @using StardewModdingAPI.Web.Framework.ConfigModels diff --git a/src/SMAPI.Web/Views/_ViewStart.cshtml b/src/SMAPI.Web/Views/_ViewStart.cshtml index a5f10045..0dbac246 100644 --- a/src/SMAPI.Web/Views/_ViewStart.cshtml +++ b/src/SMAPI.Web/Views/_ViewStart.cshtml @@ -1,3 +1,7 @@ -@{ +@{ + #nullable disable +} + +@{ Layout = "_Layout"; } diff --git a/src/SMAPI.Web/wwwroot/Content/css/file-upload.css b/src/SMAPI.Web/wwwroot/Content/css/file-upload.css index ff170691..f29d46aa 100644 --- a/src/SMAPI.Web/wwwroot/Content/css/file-upload.css +++ b/src/SMAPI.Web/wwwroot/Content/css/file-upload.css @@ -11,7 +11,7 @@ border-radius: 5px; border: 1px solid #000088; outline: none; - box-shadow: inset 0px 0px 1px 1px rgba(0, 0, 192, .2); + box-shadow: inset 0 0 1px 1px rgba(0, 0, 192, .2); } #submit { diff --git a/src/SMAPI.sln.DotSettings b/src/SMAPI.sln.DotSettings index b85185d5..5b35c615 100644 --- a/src/SMAPI.sln.DotSettings +++ b/src/SMAPI.sln.DotSettings @@ -73,5 +73,6 @@ <s:Boolean x:Key="/Default/UserDictionary/Words/=tilesheets/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=tilesheet_0027s/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=unloadable/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/UserDictionary/Words/=versioning/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=virally/@EntryIndexedValue">True</s:Boolean> </wpf:ResourceDictionary>
\ No newline at end of file diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 76f4ef87..2d9ab666 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; @@ -170,9 +172,6 @@ namespace StardewModdingAPI /// <summary>The target game platform as a SMAPI toolkit constant.</summary> internal static Platform Platform { get; } = (Platform)Constants.TargetPlatform; - /// <summary>The language code for non-translated mod assets.</summary> - internal static LocalizedContentManager.LanguageCode DefaultLanguage { get; } = LocalizedContentManager.LanguageCode.en; - /********* ** Internal methods @@ -294,21 +293,6 @@ namespace StardewModdingAPI return new PlatformAssemblyMap(targetPlatform, removeAssemblyReferences.ToArray(), targetAssemblies.ToArray()); } - /// <summary>Get whether the game assembly was patched by Stardew64Installer.</summary> - /// <param name="version">The version of Stardew64Installer which was applied to the game assembly, if any.</param> - internal static bool IsPatchedByStardew64Installer(out ISemanticVersion version) - { - PropertyInfo property = typeof(Game1).GetProperty("Stardew64InstallerVersion"); - if (property == null) - { - version = null; - return false; - } - - version = new SemanticVersion((string)property.GetValue(null)); - return true; - } - /********* ** Private methods diff --git a/src/SMAPI/Context.cs b/src/SMAPI/Context.cs index a745592c..e906375b 100644 --- a/src/SMAPI/Context.cs +++ b/src/SMAPI/Context.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using StardewModdingAPI.Enums; using StardewModdingAPI.Events; @@ -14,16 +16,16 @@ namespace StardewModdingAPI ** Fields *********/ /// <summary>Whether the player has loaded a save and the world has finished initializing.</summary> - private static readonly PerScreen<bool> IsWorldReadyForScreen = new PerScreen<bool>(); + private static readonly PerScreen<bool> IsWorldReadyForScreen = new(); /// <summary>The current stage in the game's loading process.</summary> - private static readonly PerScreen<LoadStage> LoadStageForScreen = new PerScreen<LoadStage>(); + private static readonly PerScreen<LoadStage> LoadStageForScreen = new(); /// <summary>Whether a player save has been loaded.</summary> - internal static bool IsSaveLoaded => Game1.hasLoadedGame && !(Game1.activeClickableMenu is TitleMenu); + internal static bool IsSaveLoaded => Game1.hasLoadedGame && Game1.activeClickableMenu is not TitleMenu; /// <summary>Whether the game is currently writing to the save file.</summary> - internal static bool IsSaving => Game1.activeClickableMenu is SaveGameMenu || Game1.activeClickableMenu is ShippingMenu; // saving is performed by SaveGameMenu, but it's wrapped by ShippingMenu on days when the player shipping something + internal static bool IsSaving => Game1.activeClickableMenu is SaveGameMenu or ShippingMenu; // saving is performed by SaveGameMenu, but it's wrapped by ShippingMenu on days when the player shipping something /// <summary>The active split-screen instance IDs.</summary> internal static readonly ISet<int> ActiveScreenIds = new HashSet<int>(); @@ -39,7 +41,7 @@ namespace StardewModdingAPI } /// <summary>Whether the in-game world is completely unloaded and not in the process of being loaded. The world may still exist in memory at this point, but should be ignored.</summary> - internal static bool IsWorldFullyUnloaded => Context.LoadStage == LoadStage.ReturningToTitle || Context.LoadStage == LoadStage.None; + internal static bool IsWorldFullyUnloaded => Context.LoadStage is LoadStage.ReturningToTitle or LoadStage.None; /********* @@ -86,7 +88,7 @@ namespace StardewModdingAPI public static bool HasRemotePlayers => Context.IsMultiplayer && !Game1.hasLocalClientsOnly; /// <summary>Whether the current player is the main player. This is always true in single-player, and true when hosting in multiplayer.</summary> - public static bool IsMainPlayer => Game1.IsMasterGame && Context.ScreenId == 0 && !(TitleMenu.subMenu is FarmhandMenu); + public static bool IsMainPlayer => Game1.IsMasterGame && Context.ScreenId == 0 && TitleMenu.subMenu is not FarmhandMenu; /********* diff --git a/src/SMAPI/Events/AssetReadyEventArgs.cs b/src/SMAPI/Events/AssetReadyEventArgs.cs index 2c308f18..19e5a9df 100644 --- a/src/SMAPI/Events/AssetReadyEventArgs.cs +++ b/src/SMAPI/Events/AssetReadyEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/AssetRequestedEventArgs.cs b/src/SMAPI/Events/AssetRequestedEventArgs.cs index c0cbd8fb..3c51c95d 100644 --- a/src/SMAPI/Events/AssetRequestedEventArgs.cs +++ b/src/SMAPI/Events/AssetRequestedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; @@ -99,7 +101,7 @@ namespace StardewModdingAPI.Events mod: this.Mod, priority: priority, onBehalfOf: null, - _ => this.Mod.Mod.Helper.Content.Load<TAsset>(relativePath)) + _ => this.Mod.Mod.Helper.ModContent.Load<TAsset>(relativePath)) ); } diff --git a/src/SMAPI/Events/AssetsInvalidatedEventArgs.cs b/src/SMAPI/Events/AssetsInvalidatedEventArgs.cs index 614cdf49..bd0df598 100644 --- a/src/SMAPI/Events/AssetsInvalidatedEventArgs.cs +++ b/src/SMAPI/Events/AssetsInvalidatedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Collections.Immutable; diff --git a/src/SMAPI/Events/BuildingListChangedEventArgs.cs b/src/SMAPI/Events/BuildingListChangedEventArgs.cs index 74f37710..ba9574cc 100644 --- a/src/SMAPI/Events/BuildingListChangedEventArgs.cs +++ b/src/SMAPI/Events/BuildingListChangedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI/Events/ButtonPressedEventArgs.cs b/src/SMAPI/Events/ButtonPressedEventArgs.cs index 1b30fd23..94684513 100644 --- a/src/SMAPI/Events/ButtonPressedEventArgs.cs +++ b/src/SMAPI/Events/ButtonPressedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewModdingAPI.Framework.Input; diff --git a/src/SMAPI/Events/ButtonReleasedEventArgs.cs b/src/SMAPI/Events/ButtonReleasedEventArgs.cs index 40ec1cc1..6ff3727d 100644 --- a/src/SMAPI/Events/ButtonReleasedEventArgs.cs +++ b/src/SMAPI/Events/ButtonReleasedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewModdingAPI.Framework.Input; diff --git a/src/SMAPI/Events/ButtonsChangedEventArgs.cs b/src/SMAPI/Events/ButtonsChangedEventArgs.cs index a5e87735..c63d34e6 100644 --- a/src/SMAPI/Events/ButtonsChangedEventArgs.cs +++ b/src/SMAPI/Events/ButtonsChangedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI/Events/ChestInventoryChangedEventArgs.cs b/src/SMAPI/Events/ChestInventoryChangedEventArgs.cs index 4b4c4210..bc8ac0c0 100644 --- a/src/SMAPI/Events/ChestInventoryChangedEventArgs.cs +++ b/src/SMAPI/Events/ChestInventoryChangedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using StardewValley; diff --git a/src/SMAPI/Events/CursorMovedEventArgs.cs b/src/SMAPI/Events/CursorMovedEventArgs.cs index 43ff90ce..f3e7513b 100644 --- a/src/SMAPI/Events/CursorMovedEventArgs.cs +++ b/src/SMAPI/Events/CursorMovedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/DebrisListChangedEventArgs.cs b/src/SMAPI/Events/DebrisListChangedEventArgs.cs index 61b7590a..56b1f30a 100644 --- a/src/SMAPI/Events/DebrisListChangedEventArgs.cs +++ b/src/SMAPI/Events/DebrisListChangedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI/Events/FurnitureListChangedEventArgs.cs b/src/SMAPI/Events/FurnitureListChangedEventArgs.cs index 683f4620..cda1b6cc 100644 --- a/src/SMAPI/Events/FurnitureListChangedEventArgs.cs +++ b/src/SMAPI/Events/FurnitureListChangedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI/Events/IContentEvents.cs b/src/SMAPI/Events/IContentEvents.cs index d537db70..109f9753 100644 --- a/src/SMAPI/Events/IContentEvents.cs +++ b/src/SMAPI/Events/IContentEvents.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/IDisplayEvents.cs b/src/SMAPI/Events/IDisplayEvents.cs index dbf8d90f..b8b89120 100644 --- a/src/SMAPI/Events/IDisplayEvents.cs +++ b/src/SMAPI/Events/IDisplayEvents.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewValley; diff --git a/src/SMAPI/Events/IGameLoopEvents.cs b/src/SMAPI/Events/IGameLoopEvents.cs index 6855737b..52bac3f8 100644 --- a/src/SMAPI/Events/IGameLoopEvents.cs +++ b/src/SMAPI/Events/IGameLoopEvents.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/IInputEvents.cs b/src/SMAPI/Events/IInputEvents.cs index 081c40c0..01ceb224 100644 --- a/src/SMAPI/Events/IInputEvents.cs +++ b/src/SMAPI/Events/IInputEvents.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/IModEvents.cs b/src/SMAPI/Events/IModEvents.cs index 2603961b..a1aacbce 100644 --- a/src/SMAPI/Events/IModEvents.cs +++ b/src/SMAPI/Events/IModEvents.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Events { /// <summary>Manages access to events raised by SMAPI.</summary> diff --git a/src/SMAPI/Events/IMultiplayerEvents.cs b/src/SMAPI/Events/IMultiplayerEvents.cs index af9b5f17..c50eaf04 100644 --- a/src/SMAPI/Events/IMultiplayerEvents.cs +++ b/src/SMAPI/Events/IMultiplayerEvents.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/IPlayerEvents.cs b/src/SMAPI/Events/IPlayerEvents.cs index 81e17b1a..9d18bfad 100644 --- a/src/SMAPI/Events/IPlayerEvents.cs +++ b/src/SMAPI/Events/IPlayerEvents.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/ISpecialisedEvents.cs b/src/SMAPI/Events/ISpecialisedEvents.cs index bf70956d..0ec5bf54 100644 --- a/src/SMAPI/Events/ISpecialisedEvents.cs +++ b/src/SMAPI/Events/ISpecialisedEvents.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/IWorldEvents.cs b/src/SMAPI/Events/IWorldEvents.cs index c023e1f0..785dfa8f 100644 --- a/src/SMAPI/Events/IWorldEvents.cs +++ b/src/SMAPI/Events/IWorldEvents.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/InventoryChangedEventArgs.cs b/src/SMAPI/Events/InventoryChangedEventArgs.cs index 40cd4128..58c0ff8f 100644 --- a/src/SMAPI/Events/InventoryChangedEventArgs.cs +++ b/src/SMAPI/Events/InventoryChangedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using StardewValley; diff --git a/src/SMAPI/Events/ItemStackSizeChange.cs b/src/SMAPI/Events/ItemStackSizeChange.cs index 35369be2..5d0986aa 100644 --- a/src/SMAPI/Events/ItemStackSizeChange.cs +++ b/src/SMAPI/Events/ItemStackSizeChange.cs @@ -1,3 +1,5 @@ +#nullable disable + using StardewValley; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs b/src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs index 59d79f0f..aedb0e46 100644 --- a/src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs +++ b/src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI/Events/LevelChangedEventArgs.cs b/src/SMAPI/Events/LevelChangedEventArgs.cs index c7303603..3beb9fd5 100644 --- a/src/SMAPI/Events/LevelChangedEventArgs.cs +++ b/src/SMAPI/Events/LevelChangedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewModdingAPI.Enums; using StardewValley; diff --git a/src/SMAPI/Events/LocaleChangedEventArgs.cs b/src/SMAPI/Events/LocaleChangedEventArgs.cs index 09d3f6e5..015e7ec8 100644 --- a/src/SMAPI/Events/LocaleChangedEventArgs.cs +++ b/src/SMAPI/Events/LocaleChangedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using LanguageCode = StardewValley.LocalizedContentManager.LanguageCode; diff --git a/src/SMAPI/Events/LocationListChangedEventArgs.cs b/src/SMAPI/Events/LocationListChangedEventArgs.cs index 1ebb3e2d..055463dd 100644 --- a/src/SMAPI/Events/LocationListChangedEventArgs.cs +++ b/src/SMAPI/Events/LocationListChangedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI/Events/MenuChangedEventArgs.cs b/src/SMAPI/Events/MenuChangedEventArgs.cs index 977ba38b..362accec 100644 --- a/src/SMAPI/Events/MenuChangedEventArgs.cs +++ b/src/SMAPI/Events/MenuChangedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewValley.Menus; diff --git a/src/SMAPI/Events/ModMessageReceivedEventArgs.cs b/src/SMAPI/Events/ModMessageReceivedEventArgs.cs index d75a7540..671bdf38 100644 --- a/src/SMAPI/Events/ModMessageReceivedEventArgs.cs +++ b/src/SMAPI/Events/ModMessageReceivedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewModdingAPI.Framework.Networking; using StardewModdingAPI.Toolkit.Serialization; diff --git a/src/SMAPI/Events/NpcListChangedEventArgs.cs b/src/SMAPI/Events/NpcListChangedEventArgs.cs index 3a37f1e7..fb6dc1c5 100644 --- a/src/SMAPI/Events/NpcListChangedEventArgs.cs +++ b/src/SMAPI/Events/NpcListChangedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI/Events/ObjectListChangedEventArgs.cs b/src/SMAPI/Events/ObjectListChangedEventArgs.cs index b21d2867..b1a636aa 100644 --- a/src/SMAPI/Events/ObjectListChangedEventArgs.cs +++ b/src/SMAPI/Events/ObjectListChangedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI/Events/PeerConnectedEventArgs.cs b/src/SMAPI/Events/PeerConnectedEventArgs.cs index bfaa2bd3..3d11a3b5 100644 --- a/src/SMAPI/Events/PeerConnectedEventArgs.cs +++ b/src/SMAPI/Events/PeerConnectedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/PeerContextReceivedEventArgs.cs b/src/SMAPI/Events/PeerContextReceivedEventArgs.cs index 151a295c..35a4b20d 100644 --- a/src/SMAPI/Events/PeerContextReceivedEventArgs.cs +++ b/src/SMAPI/Events/PeerContextReceivedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/PeerDisconnectedEventArgs.cs b/src/SMAPI/Events/PeerDisconnectedEventArgs.cs index 8517988a..0675b8fe 100644 --- a/src/SMAPI/Events/PeerDisconnectedEventArgs.cs +++ b/src/SMAPI/Events/PeerDisconnectedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/RenderedActiveMenuEventArgs.cs b/src/SMAPI/Events/RenderedActiveMenuEventArgs.cs index efd4163b..3da0b4b4 100644 --- a/src/SMAPI/Events/RenderedActiveMenuEventArgs.cs +++ b/src/SMAPI/Events/RenderedActiveMenuEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Microsoft.Xna.Framework.Graphics; using StardewValley; diff --git a/src/SMAPI/Events/RenderedEventArgs.cs b/src/SMAPI/Events/RenderedEventArgs.cs index d6341b19..e8beaaac 100644 --- a/src/SMAPI/Events/RenderedEventArgs.cs +++ b/src/SMAPI/Events/RenderedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Microsoft.Xna.Framework.Graphics; using StardewValley; diff --git a/src/SMAPI/Events/RenderedHudEventArgs.cs b/src/SMAPI/Events/RenderedHudEventArgs.cs index 46e89013..b25ecd4c 100644 --- a/src/SMAPI/Events/RenderedHudEventArgs.cs +++ b/src/SMAPI/Events/RenderedHudEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Microsoft.Xna.Framework.Graphics; using StardewValley; diff --git a/src/SMAPI/Events/RenderedWorldEventArgs.cs b/src/SMAPI/Events/RenderedWorldEventArgs.cs index 56145381..a99d6ab3 100644 --- a/src/SMAPI/Events/RenderedWorldEventArgs.cs +++ b/src/SMAPI/Events/RenderedWorldEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Microsoft.Xna.Framework.Graphics; using StardewValley; diff --git a/src/SMAPI/Events/RenderingActiveMenuEventArgs.cs b/src/SMAPI/Events/RenderingActiveMenuEventArgs.cs index 103f56df..3e3f3258 100644 --- a/src/SMAPI/Events/RenderingActiveMenuEventArgs.cs +++ b/src/SMAPI/Events/RenderingActiveMenuEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Microsoft.Xna.Framework.Graphics; using StardewValley; diff --git a/src/SMAPI/Events/RenderingEventArgs.cs b/src/SMAPI/Events/RenderingEventArgs.cs index 5acbef09..8f6b3557 100644 --- a/src/SMAPI/Events/RenderingEventArgs.cs +++ b/src/SMAPI/Events/RenderingEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Microsoft.Xna.Framework.Graphics; using StardewValley; diff --git a/src/SMAPI/Events/RenderingHudEventArgs.cs b/src/SMAPI/Events/RenderingHudEventArgs.cs index 84c96ecd..87269b90 100644 --- a/src/SMAPI/Events/RenderingHudEventArgs.cs +++ b/src/SMAPI/Events/RenderingHudEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Microsoft.Xna.Framework.Graphics; using StardewValley; diff --git a/src/SMAPI/Events/RenderingWorldEventArgs.cs b/src/SMAPI/Events/RenderingWorldEventArgs.cs index d0d44789..2fc9964f 100644 --- a/src/SMAPI/Events/RenderingWorldEventArgs.cs +++ b/src/SMAPI/Events/RenderingWorldEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Microsoft.Xna.Framework.Graphics; using StardewValley; diff --git a/src/SMAPI/Events/TerrainFeatureListChangedEventArgs.cs b/src/SMAPI/Events/TerrainFeatureListChangedEventArgs.cs index cdf1e6dc..77a73102 100644 --- a/src/SMAPI/Events/TerrainFeatureListChangedEventArgs.cs +++ b/src/SMAPI/Events/TerrainFeatureListChangedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI/Events/WarpedEventArgs.cs b/src/SMAPI/Events/WarpedEventArgs.cs index 9afe4a4e..92a8ea77 100644 --- a/src/SMAPI/Events/WarpedEventArgs.cs +++ b/src/SMAPI/Events/WarpedEventArgs.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewValley; diff --git a/src/SMAPI/Framework/Command.cs b/src/SMAPI/Framework/Command.cs index 8c9df47d..776ba238 100644 --- a/src/SMAPI/Framework/Command.cs +++ b/src/SMAPI/Framework/Command.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Framework diff --git a/src/SMAPI/Framework/CommandManager.cs b/src/SMAPI/Framework/CommandManager.cs index ff540ad8..df798b0c 100644 --- a/src/SMAPI/Framework/CommandManager.cs +++ b/src/SMAPI/Framework/CommandManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -65,7 +67,7 @@ namespace StardewModdingAPI.Framework /// <exception cref="ArgumentException">There's already a command with that name.</exception> public CommandManager Add(IInternalCommand command, IMonitor monitor) { - return this.Add(null, command.Name, command.Description, (name, args) => command.HandleCommand(args, monitor)); + return this.Add(null, command.Name, command.Description, (_, args) => command.HandleCommand(args, monitor)); } /// <summary>Get a command by its unique name.</summary> @@ -166,7 +168,7 @@ namespace StardewModdingAPI.Framework { bool inQuotes = false; IList<string> args = new List<string>(); - StringBuilder currentArg = new StringBuilder(); + StringBuilder currentArg = new(); foreach (char ch in input) { if (ch == '"') diff --git a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs index 45b34556..fcfa928e 100644 --- a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs +++ b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -30,7 +32,7 @@ namespace StardewModdingAPI.Framework.Commands { SearchResult[] matches = this.FilterPatches(args).OrderBy(p => p.MethodName).ToArray(); - StringBuilder result = new StringBuilder(); + StringBuilder result = new(); if (!matches.Any()) result.AppendLine("No current patches match your search."); diff --git a/src/SMAPI/Framework/Commands/HelpCommand.cs b/src/SMAPI/Framework/Commands/HelpCommand.cs index baf3116e..eb6c74f5 100644 --- a/src/SMAPI/Framework/Commands/HelpCommand.cs +++ b/src/SMAPI/Framework/Commands/HelpCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Linq; namespace StardewModdingAPI.Framework.Commands diff --git a/src/SMAPI/Framework/Commands/IInternalCommand.cs b/src/SMAPI/Framework/Commands/IInternalCommand.cs index abf105b6..32e3e9f1 100644 --- a/src/SMAPI/Framework/Commands/IInternalCommand.cs +++ b/src/SMAPI/Framework/Commands/IInternalCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Framework.Commands { /// <summary>A core SMAPI console command.</summary> diff --git a/src/SMAPI/Framework/Commands/ReloadI18nCommand.cs b/src/SMAPI/Framework/Commands/ReloadI18nCommand.cs index 12328bb6..2043b35e 100644 --- a/src/SMAPI/Framework/Commands/ReloadI18nCommand.cs +++ b/src/SMAPI/Framework/Commands/ReloadI18nCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Framework.Commands diff --git a/src/SMAPI/Framework/Content/AssetData.cs b/src/SMAPI/Framework/Content/AssetData.cs index 05be8a3b..be4a7ce6 100644 --- a/src/SMAPI/Framework/Content/AssetData.cs +++ b/src/SMAPI/Framework/Content/AssetData.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Framework.Content diff --git a/src/SMAPI/Framework/Content/AssetDataForDictionary.cs b/src/SMAPI/Framework/Content/AssetDataForDictionary.cs index 735b651c..06dbe259 100644 --- a/src/SMAPI/Framework/Content/AssetDataForDictionary.cs +++ b/src/SMAPI/Framework/Content/AssetDataForDictionary.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index b0f1b5c7..8e59cd27 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; diff --git a/src/SMAPI/Framework/Content/AssetDataForMap.cs b/src/SMAPI/Framework/Content/AssetDataForMap.cs index 26e4986e..0425e195 100644 --- a/src/SMAPI/Framework/Content/AssetDataForMap.cs +++ b/src/SMAPI/Framework/Content/AssetDataForMap.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -96,8 +98,8 @@ namespace StardewModdingAPI.Framework.Content for (int y = 0; y < sourceArea.Value.Height; y++) { // calculate tile positions - Point sourcePos = new Point(sourceArea.Value.X + x, sourceArea.Value.Y + y); - Point targetPos = new Point(targetArea.Value.X + x, targetArea.Value.Y + y); + Point sourcePos = new(sourceArea.Value.X + x, sourceArea.Value.Y + y); + Point targetPos = new(targetArea.Value.X + x, targetArea.Value.Y + y); // replace tiles on target-only layers if (replaceAll) @@ -147,7 +149,7 @@ namespace StardewModdingAPI.Framework.Content { switch (sourceTile) { - case StaticTile _: + case StaticTile: return new StaticTile(targetLayer, targetSheet, sourceTile.BlendMode, sourceTile.TileIndex); case AnimatedTile animatedTile: diff --git a/src/SMAPI/Framework/Content/AssetDataForObject.cs b/src/SMAPI/Framework/Content/AssetDataForObject.cs index d91873ae..4a6df64b 100644 --- a/src/SMAPI/Framework/Content/AssetDataForObject.cs +++ b/src/SMAPI/Framework/Content/AssetDataForObject.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; @@ -47,9 +49,9 @@ namespace StardewModdingAPI.Framework.Content /// <inheritdoc /> public TData GetData<TData>() { - if (!(this.Data is TData)) + if (this.Data is not TData data) throw new InvalidCastException($"The content data of type {this.Data.GetType().FullName} can't be converted to the requested {typeof(TData).FullName}."); - return (TData)this.Data; + return data; } } } diff --git a/src/SMAPI/Framework/Content/AssetEditOperation.cs b/src/SMAPI/Framework/Content/AssetEditOperation.cs index 818209fa..1b7d0c93 100644 --- a/src/SMAPI/Framework/Content/AssetEditOperation.cs +++ b/src/SMAPI/Framework/Content/AssetEditOperation.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Framework/Content/AssetInfo.cs b/src/SMAPI/Framework/Content/AssetInfo.cs index f5da5d69..51dcc61f 100644 --- a/src/SMAPI/Framework/Content/AssetInfo.cs +++ b/src/SMAPI/Framework/Content/AssetInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; diff --git a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs index 981eed40..7f53db9b 100644 --- a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs +++ b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Reflection; using StardewModdingAPI.Internal; @@ -36,7 +38,7 @@ namespace StardewModdingAPI.Framework.Content this.Instance = instance ?? throw new ArgumentNullException(nameof(instance)); this.WasAdded = wasAdded; - if (!(instance is IAssetEditor) && !(instance is IAssetLoader)) + if (instance is not (IAssetEditor or IAssetLoader)) throw new InvalidCastException($"The provided {nameof(instance)} value must be an {nameof(IAssetEditor)} or {nameof(IAssetLoader)} instance."); } diff --git a/src/SMAPI/Framework/Content/AssetLoadOperation.cs b/src/SMAPI/Framework/Content/AssetLoadOperation.cs index b12958d6..73e60e24 100644 --- a/src/SMAPI/Framework/Content/AssetLoadOperation.cs +++ b/src/SMAPI/Framework/Content/AssetLoadOperation.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Framework/Content/AssetName.cs b/src/SMAPI/Framework/Content/AssetName.cs index a1d37b0b..4d583d82 100644 --- a/src/SMAPI/Framework/Content/AssetName.cs +++ b/src/SMAPI/Framework/Content/AssetName.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewModdingAPI.Toolkit.Utilities; using StardewValley; @@ -107,7 +109,7 @@ namespace StardewModdingAPI.Framework.Content return this.BaseName.Equals(assetName?.BaseName, StringComparison.OrdinalIgnoreCase); if (assetName is AssetName impl) - return this.ComparableName == impl?.ComparableName; + return this.ComparableName == impl.ComparableName; return this.Name.Equals(assetName?.Name, StringComparison.OrdinalIgnoreCase); } diff --git a/src/SMAPI/Framework/Content/AssetOperationGroup.cs b/src/SMAPI/Framework/Content/AssetOperationGroup.cs index a2fcb722..e3c3f92c 100644 --- a/src/SMAPI/Framework/Content/AssetOperationGroup.cs +++ b/src/SMAPI/Framework/Content/AssetOperationGroup.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Framework.Content { /// <summary>A set of operations to apply to an asset for a given <see cref="IAssetEditor"/> or <see cref="IAssetLoader"/> implementation.</summary> diff --git a/src/SMAPI/Framework/Content/ContentCache.cs b/src/SMAPI/Framework/Content/ContentCache.cs index 8e0c6228..4e620d28 100644 --- a/src/SMAPI/Framework/Content/ContentCache.cs +++ b/src/SMAPI/Framework/Content/ContentCache.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics.Contracts; diff --git a/src/SMAPI/Framework/Content/TilesheetReference.cs b/src/SMAPI/Framework/Content/TilesheetReference.cs index 0919bb44..cdc4bc62 100644 --- a/src/SMAPI/Framework/Content/TilesheetReference.cs +++ b/src/SMAPI/Framework/Content/TilesheetReference.cs @@ -1,4 +1,5 @@ -using System.Numerics; +#nullable disable + using xTile.Dimensions; namespace StardewModdingAPI.Framework.Content diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 108257bf..81820b05 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -15,7 +17,7 @@ using StardewModdingAPI.Framework.Utilities; using StardewModdingAPI.Internal; using StardewModdingAPI.Metadata; using StardewModdingAPI.Toolkit.Serialization; -using StardewModdingAPI.Toolkit.Utilities; +using StardewModdingAPI.Utilities; using StardewValley; using StardewValley.GameData; using xTile; @@ -80,6 +82,9 @@ namespace StardewModdingAPI.Framework /// <summary>The cached asset load/edit operations to apply, indexed by asset name.</summary> private readonly TickCacheDictionary<IAssetName, AssetOperationGroup[]> AssetOperationsByKey = new(); + /// <summary>The previously created case-insensitive path caches by root path.</summary> + private readonly Dictionary<string, CaseInsensitivePathCache> CaseInsensitivePathCaches = new(StringComparer.OrdinalIgnoreCase); + /********* ** Accessors @@ -91,9 +96,11 @@ namespace StardewModdingAPI.Framework public LocalizedContentManager.LanguageCode Language => this.MainContentManager.Language; /// <summary>Interceptors which provide the initial versions of matching assets.</summary> + [Obsolete] public IList<ModLinked<IAssetLoader>> Loaders { get; } = new List<ModLinked<IAssetLoader>>(); /// <summary>Interceptors which edit matching assets after they're loaded.</summary> + [Obsolete] public IList<ModLinked<IAssetEditor>> Editors { get; } = new List<ModLinked<IAssetEditor>>(); /// <summary>The absolute path to the <see cref="ContentManager.RootDirectory"/>.</summary> @@ -156,7 +163,7 @@ namespace StardewModdingAPI.Framework ); this.ContentManagers.Add(contentManagerForAssetPropagation); this.VanillaContentManager = new LocalizedContentManager(serviceProvider, rootDirectory); - this.CoreAssets = new CoreAssetPropagator(this.MainContentManager, contentManagerForAssetPropagation, this.Monitor, reflection, aggressiveMemoryOptimizations, this.ParseAssetName); + this.CoreAssets = new CoreAssetPropagator(this.MainContentManager, contentManagerForAssetPropagation, this.Monitor, reflection, aggressiveMemoryOptimizations, name => this.ParseAssetName(name, allowLocales: true)); this.LocaleCodes = new Lazy<Dictionary<string, LocalizedContentManager.LanguageCode>>(() => this.GetLocaleCodes(customLanguages: Enumerable.Empty<ModLanguage>())); } @@ -205,7 +212,8 @@ namespace StardewModdingAPI.Framework reflection: this.Reflection, jsonHelper: this.JsonHelper, onDisposing: this.OnDisposing, - aggressiveMemoryOptimizations: this.AggressiveMemoryOptimizations + aggressiveMemoryOptimizations: this.AggressiveMemoryOptimizations, + relativePathCache: this.GetCaseInsensitivePathCache(rootDirectory) ); this.ContentManagers.Add(manager); return manager; @@ -269,11 +277,17 @@ namespace StardewModdingAPI.Framework /// <summary>Parse a raw asset name.</summary> /// <param name="rawName">The raw asset name to parse.</param> + /// <param name="allowLocales">Whether to parse locales in the <paramref name="rawName"/>. If this is false, any locale codes in the name are treated as if they were part of the base name (e.g. for mod files).</param> /// <exception cref="ArgumentException">The <paramref name="rawName"/> is null or empty.</exception> - public AssetName ParseAssetName(string rawName) + public AssetName ParseAssetName(string rawName, bool allowLocales) { return !string.IsNullOrWhiteSpace(rawName) - ? AssetName.Parse(rawName, parseLocale: locale => this.LocaleCodes.Value.TryGetValue(locale, out LocalizedContentManager.LanguageCode langCode) ? langCode : null) + ? AssetName.Parse( + rawName: rawName, + parseLocale: allowLocales + ? locale => this.LocaleCodes.Value.TryGetValue(locale, out LocalizedContentManager.LanguageCode langCode) ? langCode : null + : _ => null + ) : throw new ArgumentException("The asset name can't be null or empty.", nameof(rawName)); } @@ -303,7 +317,7 @@ namespace StardewModdingAPI.Framework if (parts.Length != 3) // managed key prefix, mod id, relative path return false; contentManagerID = Path.Combine(parts[0], parts[1]); - relativePath = this.ParseAssetName(parts[2]); + relativePath = this.ParseAssetName(parts[2], allowLocales: false); return true; } @@ -357,7 +371,7 @@ namespace StardewModdingAPI.Framework string locale = this.GetLocale(); return this.InvalidateCache((_, rawName, type) => { - IAssetName assetName = this.ParseAssetName(rawName); + IAssetName assetName = this.ParseAssetName(rawName, allowLocales: true); IAssetInfo info = new AssetInfo(locale, assetName, type, this.MainContentManager.AssertAndNormalizeAssetName); return predicate(info); }, dispose); @@ -378,7 +392,7 @@ namespace StardewModdingAPI.Framework { foreach ((string key, object asset) in contentManager.InvalidateCache((key, type) => predicate(contentManager, key, type), dispose)) { - AssetName assetName = this.ParseAssetName(key); + AssetName assetName = this.ParseAssetName(key, allowLocales: true); if (!invalidatedAssets.ContainsKey(assetName)) invalidatedAssets[assetName] = asset.GetType(); } @@ -394,7 +408,7 @@ namespace StardewModdingAPI.Framework continue; // get map path - AssetName mapPath = this.ParseAssetName(this.MainContentManager.AssertAndNormalizeAssetName(location.mapPath.Value)); + AssetName mapPath = this.ParseAssetName(this.MainContentManager.AssertAndNormalizeAssetName(location.mapPath.Value), allowLocales: true); if (!invalidatedAssets.ContainsKey(mapPath) && predicate(this.MainContentManager, mapPath.Name, typeof(Map))) invalidatedAssets[mapPath] = typeof(Map); } @@ -471,6 +485,18 @@ namespace StardewModdingAPI.Framework }); } + /// <summary>Get a dictionary of relative paths within a root path, for case-insensitive file lookups.</summary> + /// <param name="rootPath">The root path to scan.</param> + public CaseInsensitivePathCache GetCaseInsensitivePathCache(string rootPath) + { + rootPath = PathUtilities.NormalizePath(rootPath); + + if (!this.CaseInsensitivePathCaches.TryGetValue(rootPath, out CaseInsensitivePathCache cache)) + this.CaseInsensitivePathCaches[rootPath] = cache = new CaseInsensitivePathCache(rootPath); + + return cache; + } + /// <summary>Get the tilesheet ID order used by the unmodified version of a map asset.</summary> /// <param name="assetName">The asset path relative to the loader root directory, not including the <c>.xnb</c> extension.</param> public TilesheetReference[] GetVanillaTilesheetIds(string assetName) @@ -584,6 +610,7 @@ namespace StardewModdingAPI.Framework yield return group; // legacy load operations +#pragma warning disable CS0612, CS0618 // deprecated code foreach (ModLinked<IAssetLoader> loader in this.Loaders) { // check if loader applies @@ -631,6 +658,27 @@ namespace StardewModdingAPI.Framework continue; } + // HACK + // + // If two editors have the same priority, they're applied in registration order (so + // whichever was registered first is applied first). Mods often depend on this + // behavior, like Json Assets registering its interceptors before Content Patcher. + // + // Unfortunately the old & new content APIs have separate lists, so new-API + // interceptors always ran before old-API interceptors with the same priority, + // regardless of the registration order *between* APIs. Since the new API works in + // a fundamentally different way (i.e. loads/edits are defined on asset request + // instead of by registering a global 'editor' or 'loader' class), there's no way + // to track registration order between them. + // + // Until we drop the old content API in SMAPI 4.0.0, this sets the priority for + // specific legacy editors to maintain compatibility. + AssetEditPriority priority = editor.Data.GetType().FullName switch + { + "JsonAssets.Framework.ContentInjector1" => AssetEditPriority.Default - 1, // must be applied before Content Patcher + _ => AssetEditPriority.Default + }; + // add operation yield return new AssetOperationGroup( mod: editor.Mod, @@ -639,7 +687,7 @@ namespace StardewModdingAPI.Framework { new AssetEditOperation( mod: editor.Mod, - priority: AssetEditPriority.Default, + priority: priority, onBehalfOf: null, applyEdit: assetData => editor.Data.Edit<T>( this.GetLegacyAssetData(assetData) @@ -648,6 +696,7 @@ namespace StardewModdingAPI.Framework } ); } +#pragma warning restore CS0612, CS0618 } /// <summary>Get an asset info compatible with legacy <see cref="IAssetLoader"/> and <see cref="IAssetEditor"/> instances, which always expect the base name.</summary> diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index 2d921cc3..4594d235 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -119,7 +121,7 @@ namespace StardewModdingAPI.Framework.ContentManagers public sealed override T Load<T>(string assetName, LanguageCode language) { assetName = this.PrenormalizeRawAssetName(assetName); - IAssetName parsedName = this.Coordinator.ParseAssetName(assetName); + IAssetName parsedName = this.Coordinator.ParseAssetName(assetName, allowLocales: this.TryLocalizeKeys); return this.LoadLocalized<T>(parsedName, language, useCache: true); } @@ -161,7 +163,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // use cached key string rawName = LocalizedContentManager.localizedAssetNames[assetName.Name]; if (assetName.Name != rawName) - assetName = this.Coordinator.ParseAssetName(rawName); + assetName = this.Coordinator.ParseAssetName(rawName, allowLocales: this.TryLocalizeKeys); return this.LoadExact<T>(assetName, useCache: useCache); } @@ -213,7 +215,7 @@ namespace StardewModdingAPI.Framework.ContentManagers IDictionary<string, object> removeAssets = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); this.Cache.Remove((key, asset) => { - string baseAssetName = this.Coordinator.ParseAssetName(key).BaseName; + string baseAssetName = this.Coordinator.ParseAssetName(key, allowLocales: this.TryLocalizeKeys).BaseName; // check if asset should be removed bool remove = removeAssets.ContainsKey(baseAssetName); @@ -308,7 +310,7 @@ namespace StardewModdingAPI.Framework.ContentManagers { return useCache ? base.LoadBase<T>(assetName.Name) - : base.ReadAsset<T>(assetName.Name, disposable => this.Disposables.Add(new WeakReference<IDisposable>(disposable))); + : this.ReadAsset<T>(assetName.Name, disposable => this.Disposables.Add(new WeakReference<IDisposable>(disposable))); } /// <summary>Add tracking data to an asset and add it to the cache.</summary> diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index 3d37e32a..f4e1bda4 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Globalization; @@ -235,7 +237,7 @@ namespace StardewModdingAPI.Framework.ContentManagers mod.LogAsMod($"Mod incorrectly set asset '{info.Name}'{this.GetOnBehalfOfLabel(editor.OnBehalfOf)} to a null value; ignoring override.", LogLevel.Warn); asset = GetNewData(prevAsset); } - else if (!(asset.Data is T)) + else if (asset.Data is not T) { mod.LogAsMod($"Mod incorrectly set asset '{asset.Name}'{this.GetOnBehalfOfLabel(editor.OnBehalfOf)} to incompatible type '{asset.Data.GetType()}', expected '{typeof(T)}'; ignoring override.", LogLevel.Warn); asset = GetNewData(prevAsset); diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs index 3f7188da..46d5d24e 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Globalization; using Microsoft.Xna.Framework.Graphics; diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs index 90095492..c8b2ae64 100644 --- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Content; @@ -36,6 +38,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <summary>Load an asset through the content pipeline, using a localized variant of the <paramref name="assetName"/> if available.</summary> /// <typeparam name="T">The type of asset to load.</typeparam> /// <param name="assetName">The asset name relative to the loader root directory.</param> + /// <param name="language">The language for which to load the asset.</param> /// <param name="useCache">Whether to read/write the loaded asset to the asset cache.</param> T LoadLocalized<T>(IAssetName assetName, LocalizedContentManager.LanguageCode language, bool useCache); diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 63b40d66..8051c296 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Globalization; using System.IO; @@ -9,7 +11,7 @@ using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Toolkit.Serialization; -using StardewModdingAPI.Toolkit.Utilities; +using StardewModdingAPI.Utilities; using StardewValley; using xTile; using xTile.Format; @@ -32,6 +34,9 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <summary>The game content manager used for map tilesheets not provided by the mod.</summary> private readonly IContentManager GameContentManager; + /// <summary>A case-insensitive lookup of relative paths within the <see cref="ContentManager.RootDirectory"/>.</summary> + private readonly CaseInsensitivePathCache RelativePathCache; + /// <summary>If a map tilesheet's image source has no file extensions, the file extensions to check for in the local mod folder.</summary> private readonly string[] LocalTilesheetExtensions = { ".png", ".xnb" }; @@ -52,10 +57,12 @@ namespace StardewModdingAPI.Framework.ContentManagers /// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param> /// <param name="onDisposing">A callback to invoke when the content manager is being disposed.</param> /// <param name="aggressiveMemoryOptimizations">Whether to enable more aggressive memory optimizations.</param> - public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action<BaseContentManager> onDisposing, bool aggressiveMemoryOptimizations) + /// <param name="relativePathCache">A case-insensitive lookup of relative paths within the <paramref name="rootDirectory"/>.</param> + public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action<BaseContentManager> onDisposing, bool aggressiveMemoryOptimizations, CaseInsensitivePathCache relativePathCache) : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: true, aggressiveMemoryOptimizations: aggressiveMemoryOptimizations) { this.GameContentManager = gameContentManager; + this.RelativePathCache = relativePathCache; this.JsonHelper = jsonHelper; this.ModName = modName; @@ -88,101 +95,38 @@ namespace StardewModdingAPI.Framework.ContentManagers if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string contentManagerID, out IAssetName relativePath)) { if (contentManagerID != this.Name) - throw new SContentLoadException($"Can't load managed asset key '{assetName}' through content manager '{this.Name}' for a different mod."); + throw this.GetLoadError(assetName, "can't load a different mod's managed asset key through this mod content manager."); assetName = relativePath; } } // get local asset - SContentLoadException GetContentError(string reasonPhrase) => new($"Failed loading asset '{assetName}' from {this.Name}: {reasonPhrase}"); T asset; try { // get file FileInfo file = this.GetModFile(assetName.Name); if (!file.Exists) - throw GetContentError("the specified path doesn't exist."); + throw this.GetLoadError(assetName, "the specified path doesn't exist."); // load content - switch (file.Extension.ToLower()) + asset = file.Extension.ToLower() switch { - // XNB file - case ".xnb": - { - // the underlying content manager adds a .xnb extension implicitly, so - // we need to strip it here to avoid trying to load a '.xnb.xnb' file. - IAssetName loadName = this.Coordinator.ParseAssetName(assetName.Name[..^".xnb".Length]); - - // load asset - asset = this.RawLoad<T>(loadName, useCache: false); - if (asset is Map map) - { - map.assetPath = loadName.Name; - this.FixTilesheetPaths(map, relativeMapPath: loadName.Name, fixEagerPathPrefixes: true); - } - } - break; - - // unpacked Bitmap font - case ".fnt": - { - string source = File.ReadAllText(file.FullName); - asset = (T)(object)new XmlSource(source); - } - break; - - // unpacked data - case ".json": - { - if (!this.JsonHelper.ReadJsonFileIfExists(file.FullName, out asset)) - throw GetContentError("the JSON file is invalid."); // should never happen since we check for file existence above - } - break; - - // 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); - asset = (T)(object)texture; - } - break; - - // unpacked map - case ".tbin": - case ".tmx": - { - // 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); - map.assetPath = assetName.Name; - this.FixTilesheetPaths(map, relativeMapPath: assetName.Name, fixEagerPathPrefixes: false); - asset = (T)(object)map; - } - break; - - default: - throw GetContentError($"unknown file extension '{file.Extension}'; must be one of '.fnt', '.json', '.png', '.tbin', or '.xnb'."); - } + ".fnt" => this.LoadFont<T>(assetName, file), + ".json" => this.LoadDataFile<T>(assetName, file), + ".png" => this.LoadImageFile<T>(assetName, file), + ".tbin" or ".tmx" => this.LoadMapFile<T>(assetName, file), + ".xnb" => this.LoadXnbFile<T>(assetName), + _ => this.HandleUnknownFileType<T>(assetName, file) + }; } - catch (Exception ex) when (!(ex is SContentLoadException)) + catch (Exception ex) when (ex is not SContentLoadException) { - throw new SContentLoadException($"The content manager failed loading content asset '{assetName}' from {this.Name}.", ex); + throw this.GetLoadError(assetName, "an unexpected occurred.", ex); } // track & return asset - this.TrackAsset(assetName, asset, useCache); + this.TrackAsset(assetName, asset, useCache: false); return asset; } @@ -198,20 +142,125 @@ namespace StardewModdingAPI.Framework.ContentManagers public IAssetName GetInternalAssetKey(string key) { FileInfo file = this.GetModFile(key); - string relativePath = PathUtilities.GetRelativePath(this.RootDirectory, file.FullName); + string relativePath = Path.GetRelativePath(this.RootDirectory, file.FullName); string internalKey = Path.Combine(this.Name, relativePath); - return this.Coordinator.ParseAssetName(internalKey); + return this.Coordinator.ParseAssetName(internalKey, allowLocales: false); } /********* ** Private methods *********/ + /// <summary>Load an unpacked font file (<c>.fnt</c>).</summary> + /// <typeparam name="T">The type of asset to load.</typeparam> + /// <param name="assetName">The asset name relative to the loader root directory.</param> + /// <param name="file">The file to load.</param> + private T LoadFont<T>(IAssetName assetName, FileInfo file) + { + // validate + if (!typeof(T).IsAssignableFrom(typeof(XmlSource))) + throw this.GetLoadError(assetName, $"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(XmlSource)}'."); + + // load + string source = File.ReadAllText(file.FullName); + return (T)(object)new XmlSource(source); + } + + /// <summary>Load an unpacked data file (<c>.json</c>).</summary> + /// <typeparam name="T">The type of asset to load.</typeparam> + /// <param name="assetName">The asset name relative to the loader root directory.</param> + /// <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)) + throw this.GetLoadError(assetName, "the JSON file is invalid."); // should never happen since we check for file existence before calling this method + + return asset; + } + + /// <summary>Load an unpacked image file (<c>.json</c>).</summary> + /// <typeparam name="T">The type of asset to load.</typeparam> + /// <param name="assetName">The asset name relative to the loader root directory.</param> + /// <param name="file">The file to load.</param> + private T LoadImageFile<T>(IAssetName assetName, FileInfo file) + { + // validate + if (typeof(T) != typeof(Texture2D)) + throw this.GetLoadError(assetName, $"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Texture2D)}'."); + + // load + using FileStream stream = File.OpenRead(file.FullName); + Texture2D texture = Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream); + texture = this.PremultiplyTransparency(texture); + return (T)(object)texture; + } + + /// <summary>Load an unpacked image file (<c>.tbin</c> or <c>.tmx</c>).</summary> + /// <typeparam name="T">The type of asset to load.</typeparam> + /// <param name="assetName">The asset name relative to the loader root directory.</param> + /// <param name="file">The file to load.</param> + private T LoadMapFile<T>(IAssetName assetName, FileInfo file) + { + // validate + if (typeof(T) != typeof(Map)) + throw this.GetLoadError(assetName, $"can't read file with extension '{file.Extension}' as type '{typeof(T)}'; must be type '{typeof(Map)}'."); + + // load + FormatManager formatManager = FormatManager.Instance; + Map map = formatManager.LoadMap(file.FullName); + map.assetPath = assetName.Name; + this.FixTilesheetPaths(map, relativeMapPath: assetName.Name, fixEagerPathPrefixes: false); + return (T)(object)map; + } + + /// <summary>Load a packed file (<c>.xnb</c>).</summary> + /// <typeparam name="T">The type of asset to load.</typeparam> + /// <param name="assetName">The asset name relative to the loader root directory.</param> + private T LoadXnbFile<T>(IAssetName assetName) + { + // the underlying content manager adds a .xnb extension implicitly, so + // we need to strip it here to avoid trying to load a '.xnb.xnb' file. + IAssetName loadName = assetName.Name.EndsWith(".xnb", StringComparison.OrdinalIgnoreCase) + ? this.Coordinator.ParseAssetName(assetName.Name[..^".xnb".Length], allowLocales: false) + : assetName; + + // load asset + T asset = this.RawLoad<T>(loadName, useCache: false); + if (asset is Map map) + { + map.assetPath = loadName.Name; + this.FixTilesheetPaths(map, relativeMapPath: loadName.Name, fixEagerPathPrefixes: true); + } + + return asset; + } + + /// <summary>Handle a request to load a file type that isn't supported by SMAPI.</summary> + /// <typeparam name="T">The expected file type.</typeparam> + /// <param name="assetName">The asset name relative to the loader root directory.</param> + /// <param name="file">The file to load.</param> + private T HandleUnknownFileType<T>(IAssetName assetName, FileInfo file) + { + throw this.GetLoadError(assetName, $"unknown file extension '{file.Extension}'; must be one of '.fnt', '.json', '.png', '.tbin', '.tmx', or '.xnb'."); + } + + /// <summary>Get an error which indicates that an asset couldn't be loaded.</summary> + /// <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) + { + return new($"Failed loading asset '{assetName}' from {this.Name}: {reasonPhrase}", exception); + } + /// <summary>Get a file from the mod folder.</summary> /// <param name="path">The asset path relative to the content folder.</param> private FileInfo GetModFile(string path) { + // map to case-insensitive path if needed + path = this.RelativePathCache.GetFilePath(path); + // try exact match FileInfo file = new(Path.Combine(this.FullRootDirectory, path)); @@ -343,7 +392,7 @@ namespace StardewModdingAPI.Framework.ContentManagers } // get from game assets - IAssetName contentKey = this.Coordinator.ParseAssetName(this.GetContentKeyForTilesheetImageSource(relativePath)); + IAssetName contentKey = this.Coordinator.ParseAssetName(this.GetContentKeyForTilesheetImageSource(relativePath), allowLocales: false); try { this.GameContentManager.LoadLocalized<Texture2D>(contentKey, this.GameContentManager.Language, useCache: true); // no need to bypass cache here, since we're not storing the asset diff --git a/src/SMAPI/Framework/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs index 3920354e..2d33a22e 100644 --- a/src/SMAPI/Framework/ContentPack.cs +++ b/src/SMAPI/Framework/ContentPack.cs @@ -1,9 +1,10 @@ +#nullable disable + using System; -using System.Collections.Generic; using System.IO; using StardewModdingAPI.Framework.ModHelpers; using StardewModdingAPI.Toolkit.Serialization; -using StardewModdingAPI.Toolkit.Utilities; +using StardewModdingAPI.Utilities; namespace StardewModdingAPI.Framework { @@ -16,8 +17,8 @@ namespace StardewModdingAPI.Framework /// <summary>Encapsulates SMAPI's JSON file parsing.</summary> private readonly JsonHelper JsonHelper; - /// <summary>A cache of case-insensitive => exact relative paths within the content pack, for case-insensitive file lookups on Linux/macOS.</summary> - private readonly IDictionary<string, string> RelativePaths = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + /// <summary>A case-insensitive lookup of relative paths within the <see cref="DirectoryPath"/>.</summary> + private readonly CaseInsensitivePathCache RelativePathCache; /********* @@ -48,19 +49,15 @@ namespace StardewModdingAPI.Framework /// <param name="content">Provides an API for loading content assets from the content pack's folder.</param> /// <param name="translation">Provides translations stored in the content pack's <c>i18n</c> folder.</param> /// <param name="jsonHelper">Encapsulates SMAPI's JSON file parsing.</param> - public ContentPack(string directoryPath, IManifest manifest, IModContentHelper content, TranslationHelper translation, JsonHelper jsonHelper) + /// <param name="relativePathCache">A case-insensitive lookup of relative paths within the <paramref name="directoryPath"/>.</param> + public ContentPack(string directoryPath, IManifest manifest, IModContentHelper content, TranslationHelper translation, JsonHelper jsonHelper, CaseInsensitivePathCache relativePathCache) { this.DirectoryPath = directoryPath; this.Manifest = manifest; this.ModContent = content; this.TranslationImpl = translation; this.JsonHelper = jsonHelper; - - foreach (string path in Directory.EnumerateFiles(this.DirectoryPath, "*", SearchOption.AllDirectories)) - { - string relativePath = path.Substring(this.DirectoryPath.Length + 1); - this.RelativePaths[relativePath] = relativePath; - } + this.RelativePathCache = relativePathCache; } /// <inheritdoc /> @@ -90,8 +87,7 @@ namespace StardewModdingAPI.Framework FileInfo file = this.GetFile(path, out path); this.JsonHelper.WriteJsonFile(file.FullName, data); - if (!this.RelativePaths.ContainsKey(path)) - this.RelativePaths[path] = path; + this.RelativePathCache.Add(path); } /// <inheritdoc /> @@ -112,18 +108,6 @@ namespace StardewModdingAPI.Framework /********* ** Private methods *********/ - /// <summary>Get the real relative path from a case-insensitive path.</summary> - /// <param name="relativePath">The normalized relative path.</param> - private string GetCaseInsensitiveRelativePath(string relativePath) - { - if (!PathUtilities.IsSafeRelativePath(relativePath)) - throw new InvalidOperationException($"You must call {nameof(IContentPack)} methods with a relative path."); - - return !string.IsNullOrWhiteSpace(relativePath) && this.RelativePaths.TryGetValue(relativePath, out string caseInsensitivePath) - ? caseInsensitivePath - : relativePath; - } - /// <summary>Get the underlying file info.</summary> /// <param name="relativePath">The normalized file path relative to the content pack directory.</param> private FileInfo GetFile(string relativePath) @@ -136,7 +120,11 @@ namespace StardewModdingAPI.Framework /// <param name="actualRelativePath">The relative path after case-insensitive matching.</param> private FileInfo GetFile(string relativePath, out string actualRelativePath) { - actualRelativePath = this.GetCaseInsensitiveRelativePath(relativePath); + if (!PathUtilities.IsSafeRelativePath(relativePath)) + throw new InvalidOperationException($"You must call {nameof(IContentPack)} methods with a relative path."); + + actualRelativePath = this.RelativePathCache.GetFilePath(relativePath); + return new FileInfo(Path.Combine(this.DirectoryPath, actualRelativePath)); } } diff --git a/src/SMAPI/Framework/CursorPosition.cs b/src/SMAPI/Framework/CursorPosition.cs index 107481e7..8f36a554 100644 --- a/src/SMAPI/Framework/CursorPosition.cs +++ b/src/SMAPI/Framework/CursorPosition.cs @@ -1,3 +1,5 @@ +#nullable disable + using Microsoft.Xna.Framework; using StardewValley; diff --git a/src/SMAPI/Framework/DeprecationManager.cs b/src/SMAPI/Framework/DeprecationManager.cs index fc1b434b..fe1b623f 100644 --- a/src/SMAPI/Framework/DeprecationManager.cs +++ b/src/SMAPI/Framework/DeprecationManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -11,7 +13,7 @@ namespace StardewModdingAPI.Framework ** Fields *********/ /// <summary>The deprecations which have already been logged (as 'mod name::noun phrase::version').</summary> - private readonly HashSet<string> LoggedDeprecations = new HashSet<string>(StringComparer.OrdinalIgnoreCase); + private readonly HashSet<string> LoggedDeprecations = new(StringComparer.OrdinalIgnoreCase); /// <summary>Encapsulates monitoring and logging for a given module.</summary> private readonly IMonitor Monitor; diff --git a/src/SMAPI/Framework/DeprecationWarning.cs b/src/SMAPI/Framework/DeprecationWarning.cs index 5201b06c..f155358b 100644 --- a/src/SMAPI/Framework/DeprecationWarning.cs +++ b/src/SMAPI/Framework/DeprecationWarning.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Framework { /// <summary>A deprecation warning for a mod.</summary> diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs index 41540047..c977e73d 100644 --- a/src/SMAPI/Framework/Events/EventManager.cs +++ b/src/SMAPI/Framework/Events/EventManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using StardewModdingAPI.Events; namespace StardewModdingAPI.Framework.Events diff --git a/src/SMAPI/Framework/Events/IManagedEvent.cs b/src/SMAPI/Framework/Events/IManagedEvent.cs index e4e3ca08..57277576 100644 --- a/src/SMAPI/Framework/Events/IManagedEvent.cs +++ b/src/SMAPI/Framework/Events/IManagedEvent.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Framework.Events { /// <summary>Metadata for an event raised by SMAPI.</summary> diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs index 154ef659..8fa31165 100644 --- a/src/SMAPI/Framework/Events/ManagedEvent.cs +++ b/src/SMAPI/Framework/Events/ManagedEvent.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -18,7 +20,7 @@ namespace StardewModdingAPI.Framework.Events protected readonly ModRegistry ModRegistry; /// <summary>The underlying event handlers.</summary> - private readonly List<ManagedEventHandler<TEventArgs>> Handlers = new List<ManagedEventHandler<TEventArgs>>(); + private readonly List<ManagedEventHandler<TEventArgs>> Handlers = new(); /// <summary>A cached snapshot of <see cref="Handlers"/>, or <c>null</c> to rebuild it next raise.</summary> private ManagedEventHandler<TEventArgs>[] CachedHandlers = Array.Empty<ManagedEventHandler<TEventArgs>>(); diff --git a/src/SMAPI/Framework/Events/ManagedEventHandler.cs b/src/SMAPI/Framework/Events/ManagedEventHandler.cs index 28e88be0..f31bc04d 100644 --- a/src/SMAPI/Framework/Events/ManagedEventHandler.cs +++ b/src/SMAPI/Framework/Events/ManagedEventHandler.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewModdingAPI.Events; @@ -42,7 +44,7 @@ namespace StardewModdingAPI.Framework.Events /// <inheritdoc /> public int CompareTo(object obj) { - if (!(obj is ManagedEventHandler<TEventArgs> other)) + if (obj is not ManagedEventHandler<TEventArgs> other) throw new ArgumentException("Can't compare to an unrelated object type."); int priorityCompare = -this.Priority.CompareTo(other.Priority); // higher value = sort first diff --git a/src/SMAPI/Framework/Events/ModContentEvents.cs b/src/SMAPI/Framework/Events/ModContentEvents.cs index beb96031..f198b793 100644 --- a/src/SMAPI/Framework/Events/ModContentEvents.cs +++ b/src/SMAPI/Framework/Events/ModContentEvents.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Framework/Events/ModDisplayEvents.cs b/src/SMAPI/Framework/Events/ModDisplayEvents.cs index 48f55324..b2110cce 100644 --- a/src/SMAPI/Framework/Events/ModDisplayEvents.cs +++ b/src/SMAPI/Framework/Events/ModDisplayEvents.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Framework/Events/ModEvents.cs b/src/SMAPI/Framework/Events/ModEvents.cs index 1fb3482c..e8f8885d 100644 --- a/src/SMAPI/Framework/Events/ModEvents.cs +++ b/src/SMAPI/Framework/Events/ModEvents.cs @@ -1,3 +1,5 @@ +#nullable disable + using StardewModdingAPI.Events; namespace StardewModdingAPI.Framework.Events diff --git a/src/SMAPI/Framework/Events/ModEventsBase.cs b/src/SMAPI/Framework/Events/ModEventsBase.cs index 77708fc1..295caa0d 100644 --- a/src/SMAPI/Framework/Events/ModEventsBase.cs +++ b/src/SMAPI/Framework/Events/ModEventsBase.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Framework.Events { /// <summary>An internal base class for event API classes.</summary> diff --git a/src/SMAPI/Framework/Events/ModGameLoopEvents.cs b/src/SMAPI/Framework/Events/ModGameLoopEvents.cs index 5f0db369..51803daf 100644 --- a/src/SMAPI/Framework/Events/ModGameLoopEvents.cs +++ b/src/SMAPI/Framework/Events/ModGameLoopEvents.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Framework/Events/ModInputEvents.cs b/src/SMAPI/Framework/Events/ModInputEvents.cs index 40edf806..6af79c59 100644 --- a/src/SMAPI/Framework/Events/ModInputEvents.cs +++ b/src/SMAPI/Framework/Events/ModInputEvents.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Framework/Events/ModMultiplayerEvents.cs b/src/SMAPI/Framework/Events/ModMultiplayerEvents.cs index b90f64fa..7d3ce510 100644 --- a/src/SMAPI/Framework/Events/ModMultiplayerEvents.cs +++ b/src/SMAPI/Framework/Events/ModMultiplayerEvents.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Framework/Events/ModPlayerEvents.cs b/src/SMAPI/Framework/Events/ModPlayerEvents.cs index b2d89e9a..dac8f05b 100644 --- a/src/SMAPI/Framework/Events/ModPlayerEvents.cs +++ b/src/SMAPI/Framework/Events/ModPlayerEvents.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs b/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs index 7980208b..4b438034 100644 --- a/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs +++ b/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Framework/Events/ModWorldEvents.cs b/src/SMAPI/Framework/Events/ModWorldEvents.cs index a7b7d799..614945c7 100644 --- a/src/SMAPI/Framework/Events/ModWorldEvents.cs +++ b/src/SMAPI/Framework/Events/ModWorldEvents.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Framework/Exceptions/SAssemblyLoadFailedException.cs b/src/SMAPI/Framework/Exceptions/SAssemblyLoadFailedException.cs index ec9279f1..4e03a687 100644 --- a/src/SMAPI/Framework/Exceptions/SAssemblyLoadFailedException.cs +++ b/src/SMAPI/Framework/Exceptions/SAssemblyLoadFailedException.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Framework.Exceptions diff --git a/src/SMAPI/Framework/Exceptions/SContentLoadException.cs b/src/SMAPI/Framework/Exceptions/SContentLoadException.cs index 85d85e3d..c21a6b0e 100644 --- a/src/SMAPI/Framework/Exceptions/SContentLoadException.cs +++ b/src/SMAPI/Framework/Exceptions/SContentLoadException.cs @@ -1,4 +1,6 @@ -using System; +#nullable disable + +using System; using Microsoft.Xna.Framework.Content; namespace StardewModdingAPI.Framework.Exceptions diff --git a/src/SMAPI/Framework/GameVersion.cs b/src/SMAPI/Framework/GameVersion.cs index b69c6757..aa91d8f3 100644 --- a/src/SMAPI/Framework/GameVersion.cs +++ b/src/SMAPI/Framework/GameVersion.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; diff --git a/src/SMAPI/Framework/IModMetadata.cs b/src/SMAPI/Framework/IModMetadata.cs index cb876ee4..800b198a 100644 --- a/src/SMAPI/Framework/IModMetadata.cs +++ b/src/SMAPI/Framework/IModMetadata.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using StardewModdingAPI.Framework.ModHelpers; diff --git a/src/SMAPI/Framework/Input/GamePadStateBuilder.cs b/src/SMAPI/Framework/Input/GamePadStateBuilder.cs index ad254828..21168b7a 100644 --- a/src/SMAPI/Framework/Input/GamePadStateBuilder.cs +++ b/src/SMAPI/Framework/Input/GamePadStateBuilder.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework; diff --git a/src/SMAPI/Framework/Input/IInputStateBuilder.cs b/src/SMAPI/Framework/Input/IInputStateBuilder.cs index 28d62439..3fb62686 100644 --- a/src/SMAPI/Framework/Input/IInputStateBuilder.cs +++ b/src/SMAPI/Framework/Input/IInputStateBuilder.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; namespace StardewModdingAPI.Framework.Input diff --git a/src/SMAPI/Framework/Input/KeyboardStateBuilder.cs b/src/SMAPI/Framework/Input/KeyboardStateBuilder.cs index eadb7a8a..81ca0ebb 100644 --- a/src/SMAPI/Framework/Input/KeyboardStateBuilder.cs +++ b/src/SMAPI/Framework/Input/KeyboardStateBuilder.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework.Input; @@ -14,7 +16,7 @@ namespace StardewModdingAPI.Framework.Input private KeyboardState? State; /// <summary>The pressed buttons.</summary> - private readonly HashSet<Keys> PressedButtons = new HashSet<Keys>(); + private readonly HashSet<Keys> PressedButtons = new(); /********* diff --git a/src/SMAPI/Framework/Input/MouseStateBuilder.cs b/src/SMAPI/Framework/Input/MouseStateBuilder.cs index c2a0891b..85b38d32 100644 --- a/src/SMAPI/Framework/Input/MouseStateBuilder.cs +++ b/src/SMAPI/Framework/Input/MouseStateBuilder.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using Microsoft.Xna.Framework.Input; diff --git a/src/SMAPI/Framework/Input/SInputState.cs b/src/SMAPI/Framework/Input/SInputState.cs index a8d1f371..37b3c8ef 100644 --- a/src/SMAPI/Framework/Input/SInputState.cs +++ b/src/SMAPI/Framework/Input/SInputState.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -21,10 +23,10 @@ namespace StardewModdingAPI.Framework.Input private Vector2? LastPlayerTile; /// <summary>The buttons to press until the game next handles input.</summary> - private readonly HashSet<SButton> CustomPressedKeys = new HashSet<SButton>(); + private readonly HashSet<SButton> CustomPressedKeys = new(); /// <summary>The buttons to consider released until the actual button is released.</summary> - private readonly HashSet<SButton> CustomReleasedKeys = new HashSet<SButton>(); + private readonly HashSet<SButton> CustomReleasedKeys = new(); /// <summary>Whether there are new overrides in <see cref="CustomPressedKeys"/> or <see cref="CustomReleasedKeys"/> that haven't been applied to the previous state.</summary> private bool HasNewOverrides; @@ -72,8 +74,8 @@ namespace StardewModdingAPI.Framework.Input var controller = new GamePadStateBuilder(base.GetGamePadState()); var keyboard = new KeyboardStateBuilder(base.GetKeyboardState()); var mouse = new MouseStateBuilder(base.GetMouseState()); - Vector2 cursorAbsolutePos = new Vector2((mouse.X * zoomMultiplier) + Game1.viewport.X, (mouse.Y * zoomMultiplier) + Game1.viewport.Y); - Vector2? playerTilePos = Context.IsPlayerFree ? Game1.player.getTileLocation() : (Vector2?)null; + Vector2 cursorAbsolutePos = new((mouse.X * zoomMultiplier) + Game1.viewport.X, (mouse.Y * zoomMultiplier) + Game1.viewport.Y); + Vector2? playerTilePos = Context.IsPlayerFree ? Game1.player.getTileLocation() : null; HashSet<SButton> reallyDown = new HashSet<SButton>(this.GetPressedButtons(keyboard, mouse, controller)); // apply overrides @@ -203,8 +205,8 @@ namespace StardewModdingAPI.Framework.Input /// <param name="zoomMultiplier">The multiplier applied to pixel coordinates to adjust them for pixel zoom.</param> private CursorPosition GetCursorPosition(MouseState mouseState, Vector2 absolutePixels, float zoomMultiplier) { - Vector2 screenPixels = new Vector2(mouseState.X * zoomMultiplier, mouseState.Y * zoomMultiplier); - Vector2 tile = new Vector2((int)((Game1.viewport.X + screenPixels.X) / Game1.tileSize), (int)((Game1.viewport.Y + screenPixels.Y) / Game1.tileSize)); + Vector2 screenPixels = new(mouseState.X * zoomMultiplier, mouseState.Y * zoomMultiplier); + Vector2 tile = new((int)((Game1.viewport.X + screenPixels.X) / Game1.tileSize), (int)((Game1.viewport.Y + screenPixels.Y) / Game1.tileSize)); Vector2 grabTile = (Game1.mouseCursorTransparency > 0 && Utility.tileWithinRadiusOfPlayer((int)tile.X, (int)tile.Y, 1, Game1.player)) // derived from Game1.pressActionButton ? tile : Game1.player.GetGrabTile(); @@ -234,7 +236,7 @@ namespace StardewModdingAPI.Framework.Input isDown: pressed.Contains(button) ); - if (button == SButton.MouseLeft || button == SButton.MouseMiddle || button == SButton.MouseRight || button == SButton.MouseX1 || button == SButton.MouseX2) + if (button is SButton.MouseLeft or SButton.MouseMiddle or SButton.MouseRight or SButton.MouseX1 or SButton.MouseX2) mouseOverrides[button] = newState; else if (button.TryGetKeyboard(out Keys _)) keyboardOverrides[button] = newState; diff --git a/src/SMAPI/Framework/InternalExtensions.cs b/src/SMAPI/Framework/InternalExtensions.cs index fe10b045..a1d87487 100644 --- a/src/SMAPI/Framework/InternalExtensions.cs +++ b/src/SMAPI/Framework/InternalExtensions.cs @@ -1,7 +1,8 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Threading; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.Events; diff --git a/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs b/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs index bad69a2a..a0957b90 100644 --- a/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs +++ b/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.IO; using System.Text; diff --git a/src/SMAPI/Framework/Logging/LogFileManager.cs b/src/SMAPI/Framework/Logging/LogFileManager.cs index 6ab2bdfb..0b6f9ad2 100644 --- a/src/SMAPI/Framework/Logging/LogFileManager.cs +++ b/src/SMAPI/Framework/Logging/LogFileManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.IO; diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index a8a8b6ee..dab7f554 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; diff --git a/src/SMAPI/Framework/ModHelpers/BaseHelper.cs b/src/SMAPI/Framework/ModHelpers/BaseHelper.cs index 5a3d4bed..1cd1a6b3 100644 --- a/src/SMAPI/Framework/ModHelpers/BaseHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/BaseHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Framework.ModHelpers { /// <summary>The common base class for mod helpers.</summary> diff --git a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs index 69382009..c2b5092e 100644 --- a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Framework.ModHelpers diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index b0064532..e72e397e 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -107,7 +109,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <inheritdoc /> public T Load<T>(string key, ContentSource source = ContentSource.ModFolder) { - IAssetName assetName = this.ContentCore.ParseAssetName(key); + IAssetName assetName = this.ContentCore.ParseAssetName(key, allowLocales: source == ContentSource.GameContent); try { @@ -124,7 +126,7 @@ namespace StardewModdingAPI.Framework.ModHelpers throw new SContentLoadException($"{this.ModName} failed loading content asset '{key}' from {source}: unknown content source '{source}'."); } } - catch (Exception ex) when (!(ex is SContentLoadException)) + catch (Exception ex) when (ex is not SContentLoadException) { throw new SContentLoadException($"{this.ModName} failed loading content asset '{key}' from {source}.", ex); } @@ -157,21 +159,21 @@ namespace StardewModdingAPI.Framework.ModHelpers public bool InvalidateCache(string key) { string actualKey = this.GetActualAssetKey(key, ContentSource.GameContent); - this.Monitor.Log($"Requested cache invalidation for '{actualKey}'.", LogLevel.Trace); + this.Monitor.Log($"Requested cache invalidation for '{actualKey}'."); return this.ContentCore.InvalidateCache(asset => asset.Name.IsEquivalentTo(actualKey)).Any(); } /// <inheritdoc /> public bool InvalidateCache<T>() { - this.Monitor.Log($"Requested cache invalidation for all assets of type {typeof(T)}. This is an expensive operation and should be avoided if possible.", LogLevel.Trace); - return this.ContentCore.InvalidateCache((contentManager, key, type) => typeof(T).IsAssignableFrom(type)).Any(); + this.Monitor.Log($"Requested cache invalidation for all assets of type {typeof(T)}. This is an expensive operation and should be avoided if possible."); + return this.ContentCore.InvalidateCache((_, _, type) => typeof(T).IsAssignableFrom(type)).Any(); } /// <inheritdoc /> public bool InvalidateCache(Func<IAssetInfo, bool> predicate) { - this.Monitor.Log("Requested cache invalidation for all assets matching a predicate.", LogLevel.Trace); + this.Monitor.Log("Requested cache invalidation for all assets matching a predicate."); return this.ContentCore.InvalidateCache(predicate).Any(); } @@ -183,7 +185,7 @@ namespace StardewModdingAPI.Framework.ModHelpers assetName ??= $"temp/{Guid.NewGuid():N}"; - return new AssetDataForObject(this.CurrentLocale, this.ContentCore.ParseAssetName(assetName), data, this.NormalizeAssetName); + return new AssetDataForObject(this.CurrentLocale, this.ContentCore.ParseAssetName(assetName, allowLocales: true/* no way to know if it's a game or mod asset here*/), data, this.NormalizeAssetName); } diff --git a/src/SMAPI/Framework/ModHelpers/ContentPackHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentPackHelper.cs index d39abc7d..336214e2 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentPackHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentPackHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; diff --git a/src/SMAPI/Framework/ModHelpers/DataHelper.cs b/src/SMAPI/Framework/ModHelpers/DataHelper.cs index 4cbfd73f..86a34ee8 100644 --- a/src/SMAPI/Framework/ModHelpers/DataHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/DataHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; diff --git a/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs index 42a4de20..956bac7f 100644 --- a/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using StardewModdingAPI.Framework.Content; @@ -58,13 +60,13 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <inheritdoc /> public IAssetName ParseAssetName(string rawName) { - return this.ContentCore.ParseAssetName(rawName); + return this.ContentCore.ParseAssetName(rawName, allowLocales: true); } /// <inheritdoc /> public T Load<T>(string key) { - IAssetName assetName = this.ContentCore.ParseAssetName(key); + IAssetName assetName = this.ContentCore.ParseAssetName(key, allowLocales: true); return this.Load<T>(assetName); } @@ -73,7 +75,7 @@ namespace StardewModdingAPI.Framework.ModHelpers { try { - return this.GameContentManager.LoadLocalized<T>(assetName, this.CurrentLocaleConstant, useCache: false); + return this.GameContentManager.LoadLocalized<T>(assetName, this.CurrentLocaleConstant, useCache: true); } catch (Exception ex) when (ex is not SContentLoadException) { @@ -117,7 +119,7 @@ namespace StardewModdingAPI.Framework.ModHelpers assetName ??= $"temp/{Guid.NewGuid():N}"; - return new AssetDataForObject(this.CurrentLocale, this.ContentCore.ParseAssetName(assetName), data, key => this.ParseAssetName(key).Name); + return new AssetDataForObject(this.CurrentLocale, this.ContentCore.ParseAssetName(assetName, allowLocales: true), data, key => this.ParseAssetName(key).Name); } /// <summary>Get the underlying game content manager.</summary> diff --git a/src/SMAPI/Framework/ModHelpers/InputHelper.cs b/src/SMAPI/Framework/ModHelpers/InputHelper.cs index 88caf4c3..29f80d87 100644 --- a/src/SMAPI/Framework/ModHelpers/InputHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/InputHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewModdingAPI.Framework.Input; using StardewModdingAPI.Utilities; diff --git a/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs index 45899dd7..90064354 100644 --- a/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs @@ -1,7 +1,11 @@ +#nullable disable + using System; +using Microsoft.Xna.Framework.Content; using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.ContentManagers; using StardewModdingAPI.Framework.Exceptions; +using StardewModdingAPI.Utilities; namespace StardewModdingAPI.Framework.ModHelpers { @@ -20,6 +24,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <summary>The friendly mod name for use in errors.</summary> private readonly string ModName; + /// <summary>A case-insensitive lookup of relative paths within the <see cref="ContentManager.RootDirectory"/>.</summary> + private readonly CaseInsensitivePathCache RelativePathCache; + /********* ** Public methods @@ -30,7 +37,8 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <param name="modID">The unique ID of the relevant mod.</param> /// <param name="modName">The friendly mod name for use in errors.</param> /// <param name="gameContentManager">The game content manager used for map tilesheets not provided by the mod.</param> - public ModContentHelper(ContentCoordinator contentCore, string modFolderPath, string modID, string modName, IContentManager gameContentManager) + /// <param name="relativePathCache">A case-insensitive lookup of relative paths within the <paramref name="relativePathCache"/>.</param> + public ModContentHelper(ContentCoordinator contentCore, string modFolderPath, string modID, string modName, IContentManager gameContentManager, CaseInsensitivePathCache relativePathCache) : base(modID) { string managedAssetPrefix = contentCore.GetManagedAssetPrefix(modID); @@ -38,12 +46,15 @@ namespace StardewModdingAPI.Framework.ModHelpers this.ContentCore = contentCore; this.ModContentManager = contentCore.CreateModContentManager(managedAssetPrefix, modName, modFolderPath, gameContentManager); this.ModName = modName; + this.RelativePathCache = relativePathCache; } /// <inheritdoc /> public T Load<T>(string relativePath) { - IAssetName assetName = this.ContentCore.ParseAssetName(relativePath); + relativePath = this.RelativePathCache.GetAssetName(relativePath); + + IAssetName assetName = this.ContentCore.ParseAssetName(relativePath, allowLocales: false); try { @@ -58,6 +69,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <inheritdoc /> public IAssetName GetInternalAssetName(string relativePath) { + relativePath = this.RelativePathCache.GetAssetName(relativePath); return this.ModContentManager.GetInternalAssetKey(relativePath); } @@ -67,9 +79,11 @@ namespace StardewModdingAPI.Framework.ModHelpers if (data == null) throw new ArgumentNullException(nameof(data), "Can't get a patch helper for a null value."); - relativePath ??= $"temp/{Guid.NewGuid():N}"; + relativePath = relativePath != null + ? this.RelativePathCache.GetAssetName(relativePath) + : $"temp/{Guid.NewGuid():N}"; - return new AssetDataForObject(this.ContentCore.GetLocale(), this.ContentCore.ParseAssetName(relativePath), data, key => this.ContentCore.ParseAssetName(key).Name); + return new AssetDataForObject(this.ContentCore.GetLocale(), this.ContentCore.ParseAssetName(relativePath, allowLocales: false), data, key => this.ContentCore.ParseAssetName(key, allowLocales: false).Name); } } } diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index d28faacc..3cfe52bf 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.IO; using StardewModdingAPI.Events; @@ -13,7 +15,7 @@ namespace StardewModdingAPI.Framework.ModHelpers *********/ /// <summary>The backing field for <see cref="Content"/>.</summary> [Obsolete] - private readonly IContentHelper ContentImpl; + private readonly ContentHelper ContentImpl; /********* @@ -93,7 +95,13 @@ namespace StardewModdingAPI.Framework.ModHelpers /// <param name="translationHelper">An API for reading translations stored in the mod's <c>i18n</c> folder.</param> /// <exception cref="ArgumentNullException">An argument is null or empty.</exception> /// <exception cref="InvalidOperationException">The <paramref name="modDirectory"/> path does not exist on disk.</exception> - public ModHelper(string modID, string modDirectory, Func<SInputState> currentInputState, IModEvents events, IContentHelper contentHelper, IGameContentHelper gameContentHelper, IModContentHelper modContentHelper, IContentPackHelper contentPackHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper) + public ModHelper( + string modID, string modDirectory, Func<SInputState> currentInputState, IModEvents events, +#pragma warning disable CS0612 // deprecated code + ContentHelper contentHelper, +#pragma warning restore CS0612 + IGameContentHelper gameContentHelper, IModContentHelper modContentHelper, IContentPackHelper contentPackHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper + ) : base(modID) { // validate directory @@ -104,7 +112,9 @@ namespace StardewModdingAPI.Framework.ModHelpers // initialize this.DirectoryPath = modDirectory; +#pragma warning disable CS0612 // deprecated code this.ContentImpl = contentHelper ?? throw new ArgumentNullException(nameof(contentHelper)); +#pragma warning restore CS0612 this.GameContent = gameContentHelper ?? throw new ArgumentNullException(nameof(gameContentHelper)); this.ModContent = modContentHelper ?? throw new ArgumentNullException(nameof(modContentHelper)); this.ContentPacks = contentPackHelper ?? throw new ArgumentNullException(nameof(contentPackHelper)); @@ -118,6 +128,13 @@ namespace StardewModdingAPI.Framework.ModHelpers this.Events = events; } + /// <summary>Get the underlying instance for <see cref="IContentHelper"/>.</summary> + [Obsolete] + public ContentHelper GetLegacyContentHelper() + { + return this.ContentImpl; + } + /**** ** Mod config file ****/ diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 95eb03f3..e277e6fa 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using StardewModdingAPI.Framework.Reflection; @@ -69,7 +71,7 @@ namespace StardewModdingAPI.Framework.ModHelpers // get raw API IModMetadata mod = this.Registry.Get(uniqueID); if (mod?.Api != null && this.AccessedModApis.Add(mod.Manifest.UniqueID)) - this.Monitor.Log($"Accessed mod-provided API for {mod.DisplayName}.", LogLevel.Trace); + this.Monitor.Log($"Accessed mod-provided API for {mod.DisplayName}."); return mod?.Api; } diff --git a/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs b/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs index a7ce8692..96b074e2 100644 --- a/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using StardewModdingAPI.Framework.Networking; using StardewValley; diff --git a/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs b/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs index 5a4ea742..24cbd01c 100644 --- a/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Reflection; using StardewModdingAPI.Framework.Reflection; diff --git a/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs b/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs index 869664fe..37345a76 100644 --- a/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using StardewValley; diff --git a/src/SMAPI/Framework/ModLinked.cs b/src/SMAPI/Framework/ModLinked.cs index 8cfe6f5f..5a3e38ca 100644 --- a/src/SMAPI/Framework/ModLinked.cs +++ b/src/SMAPI/Framework/ModLinked.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Framework { /// <summary>A generic tuple which links something to a mod.</summary> diff --git a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs index 8e2f5ef3..1d4ddf72 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using Mono.Cecil; diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoadStatus.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoadStatus.cs index 11be19fc..d2d5d83b 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoadStatus.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoadStatus.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Framework.ModLoading { /// <summary>Indicates the result of an assembly load.</summary> diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index cb5fa2ae..070ee803 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; @@ -36,13 +38,13 @@ namespace StardewModdingAPI.Framework.ModLoading private readonly AssemblyDefinitionResolver AssemblyDefinitionResolver; /// <summary>Provides assembly symbol readers for Mono.Cecil.</summary> - private readonly SymbolReaderProvider SymbolReaderProvider = new SymbolReaderProvider(); + private readonly SymbolReaderProvider SymbolReaderProvider = new(); /// <summary>Provides assembly symbol writers for Mono.Cecil.</summary> - private readonly SymbolWriterProvider SymbolWriterProvider = new SymbolWriterProvider(); + private readonly SymbolWriterProvider SymbolWriterProvider = new(); /// <summary>The objects to dispose as part of this instance.</summary> - private readonly HashSet<IDisposable> Disposables = new HashSet<IDisposable>(); + private readonly HashSet<IDisposable> Disposables = new(); /// <summary>Whether to rewrite mods for compatibility.</summary> private readonly bool RewriteMods; @@ -138,11 +140,11 @@ namespace StardewModdingAPI.Framework.ModLoading if (changed) { if (!oneAssembly) - this.Monitor.Log($" Loading {assembly.File.Name} (rewritten)...", LogLevel.Trace); + this.Monitor.Log($" Loading {assembly.File.Name} (rewritten)..."); // load assembly - using MemoryStream outAssemblyStream = new MemoryStream(); - using MemoryStream outSymbolStream = new MemoryStream(); + using MemoryStream outAssemblyStream = new(); + using MemoryStream outSymbolStream = new(); assembly.Definition.Write(outAssemblyStream, new WriterParameters { WriteSymbols = true, SymbolStream = outSymbolStream, SymbolWriterProvider = this.SymbolWriterProvider }); byte[] bytes = outAssemblyStream.ToArray(); lastAssembly = Assembly.Load(bytes, outSymbolStream.ToArray()); @@ -150,7 +152,7 @@ namespace StardewModdingAPI.Framework.ModLoading else { if (!oneAssembly) - this.Monitor.Log($" Loading {assembly.File.Name}...", LogLevel.Trace); + this.Monitor.Log($" Loading {assembly.File.Name}..."); lastAssembly = Assembly.UnsafeLoadFrom(assembly.File.FullName); } @@ -241,7 +243,7 @@ namespace StardewModdingAPI.Framework.ModLoading try { // read assembly with symbols - FileInfo symbolsFile = new FileInfo(Path.Combine(Path.GetDirectoryName(file.FullName)!, Path.GetFileNameWithoutExtension(file.FullName)) + ".pdb"); + FileInfo symbolsFile = new(Path.Combine(Path.GetDirectoryName(file.FullName)!, Path.GetFileNameWithoutExtension(file.FullName)) + ".pdb"); if (symbolsFile.Exists) this.SymbolReaderProvider.TryAddSymbolData(file.Name, () => this.TrackForDisposal(symbolsFile.OpenRead())); assembly = this.TrackForDisposal(AssemblyDefinition.ReadAssembly(readStream, new ReaderParameters(ReadingMode.Immediate) { AssemblyResolver = assemblyResolver, InMemory = true, ReadSymbols = true, SymbolReaderProvider = this.SymbolReaderProvider })); @@ -266,7 +268,7 @@ namespace StardewModdingAPI.Framework.ModLoading // yield referenced assemblies foreach (AssemblyNameReference dependency in assembly.MainModule.AssemblyReferences) { - FileInfo dependencyFile = new FileInfo(Path.Combine(file.Directory.FullName, $"{dependency.Name}.dll")); + FileInfo dependencyFile = new(Path.Combine(file.Directory.FullName, $"{dependency.Name}.dll")); foreach (AssemblyParseResult result in this.GetReferencedLocalAssemblies(dependencyFile, visitedAssemblyNames, assemblyResolver)) yield return result; } @@ -333,7 +335,7 @@ namespace StardewModdingAPI.Framework.ModLoading // find or rewrite code IInstructionHandler[] handlers = new InstructionMetadata().GetHandlers(this.ParanoidMode, platformChanged, this.RewriteMods).ToArray(); - RecursiveRewriter rewriter = new RecursiveRewriter( + RecursiveRewriter rewriter = new( module: module, rewriteModule: curModule => { diff --git a/src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs b/src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs index b56a776c..56bd5a8b 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.IO; using Mono.Cecil; diff --git a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs index 124951a5..7c94beb7 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Linq; using Mono.Cecil; diff --git a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs index 68415123..96b4098a 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Linq; using Mono.Cecil; diff --git a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs index d2340f01..7d3c1fd7 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs @@ -1,3 +1,5 @@ +#nullable disable + using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; diff --git a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs index 99344848..b2f2e193 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs @@ -1,3 +1,5 @@ +#nullable disable + using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs index 8c1cae2b..81f90498 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Linq; using Mono.Cecil; diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs index d305daf4..001d1986 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using Mono.Cecil; using Mono.Cecil.Cil; diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs index 24ab2eca..4c589ed8 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Mono.Cecil; using StardewModdingAPI.Framework.ModLoading.Framework; diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs index 260a8df8..04a5b970 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using Mono.Cecil; diff --git a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs index d5d1b38e..bea786cd 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using Mono.Cecil; diff --git a/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs b/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs index 4f14a579..09ff78f7 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs index d7cb2471..8f47fbdd 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using System.Reflection; diff --git a/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs b/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs index d41732f8..126504e3 100644 --- a/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs +++ b/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using Mono.Cecil; diff --git a/src/SMAPI/Framework/ModLoading/IncompatibleInstructionException.cs b/src/SMAPI/Framework/ModLoading/IncompatibleInstructionException.cs index 1f9add30..29406f2a 100644 --- a/src/SMAPI/Framework/ModLoading/IncompatibleInstructionException.cs +++ b/src/SMAPI/Framework/ModLoading/IncompatibleInstructionException.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Framework.ModLoading diff --git a/src/SMAPI/Framework/ModLoading/InvalidModStateException.cs b/src/SMAPI/Framework/ModLoading/InvalidModStateException.cs index 075e237a..9dca9bc4 100644 --- a/src/SMAPI/Framework/ModLoading/InvalidModStateException.cs +++ b/src/SMAPI/Framework/ModLoading/InvalidModStateException.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Framework.ModLoading diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs index 9e6bc61f..0e698bfd 100644 --- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs +++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index 4b05d1e5..2842c11a 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; @@ -32,11 +34,8 @@ namespace StardewModdingAPI.Framework.ModLoading ModDataRecordVersionedFields dataRecord = modDatabase.Get(manifest?.UniqueID)?.GetVersionedFields(manifest); // apply defaults - if (manifest != null && dataRecord != null) - { - if (dataRecord.UpdateKey != null) - manifest.UpdateKeys = new[] { dataRecord.UpdateKey }; - } + if (manifest != null && dataRecord?.UpdateKey is not null) + manifest.OverrideUpdateKeys(dataRecord.UpdateKey); // build metadata bool shouldIgnore = folder.Type == ModType.Ignored; @@ -240,7 +239,7 @@ namespace StardewModdingAPI.Framework.ModLoading // initialize metadata mods = mods.ToArray(); var sortedMods = new Stack<IModMetadata>(); - var states = mods.ToDictionary(mod => mod, mod => ModDependencyStatus.Queued); + var states = mods.ToDictionary(mod => mod, _ => ModDependencyStatus.Queued); // handle failed mods foreach (IModMetadata mod in mods.Where(m => m.Status == ModMetadataStatus.Failed)) @@ -401,7 +400,7 @@ namespace StardewModdingAPI.Framework.ModLoading { foreach (string modRootPath in Directory.GetDirectories(rootPath)) { - DirectoryInfo directory = new DirectoryInfo(modRootPath); + DirectoryInfo directory = new(modRootPath); // if a folder only contains another folder, check the inner folder instead while (!directory.GetFiles().Any() && directory.GetDirectories().Length == 1) diff --git a/src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs b/src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs index d4366294..0898f095 100644 --- a/src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs +++ b/src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs index be2a1c58..c05005b8 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs index 135bd218..fea8c100 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs index 5162dda4..93124591 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics.CodeAnalysis; using System.Reflection; diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs index 5f68f8d9..20a30f8f 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Diagnostics.CodeAnalysis; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/ArchitectureAssemblyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/ArchitectureAssemblyRewriter.cs index cc830216..4985d72a 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/ArchitectureAssemblyRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/ArchitectureAssemblyRewriter.cs @@ -1,3 +1,5 @@ +#nullable disable + using Mono.Cecil; using StardewModdingAPI.Framework.ModLoading.Framework; diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs index 857a2230..806fca62 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Reflection; diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs index 922d4bc4..92397c58 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using HarmonyLib; using Mono.Cecil; @@ -34,7 +36,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters public override bool Handle(ModuleDefinition module, TypeReference type, Action<TypeReference> replaceWith) { // detect Harmony - if (!(type.Scope is AssemblyNameReference scope) || scope.Name != "0Harmony") + if (type.Scope is not AssemblyNameReference scope || scope.Name != "0Harmony") return false; // rewrite Harmony 1.x type to Harmony 2.0 type diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs index 57f1dd17..fc06e779 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Linq; using Mono.Cecil; @@ -37,7 +39,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters // skip if not broken FieldDefinition fieldDefinition = fieldRef.Resolve(); - if (fieldDefinition != null && !fieldDefinition.HasConstant) + if (fieldDefinition?.HasConstant == false) return false; // rewrite if possible diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs index 89de437e..4860072c 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Linq; using Mono.Cecil; diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs index 9933e2ca..00daf337 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using Mono.Cecil; diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs index ad5cb96f..bdc4c4f3 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Mono.Cecil; using StardewModdingAPI.Framework.ModLoading.Framework; diff --git a/src/SMAPI/Framework/ModLoading/Symbols/SymbolReader.cs b/src/SMAPI/Framework/ModLoading/Symbols/SymbolReader.cs index 2171895d..55b7e0c8 100644 --- a/src/SMAPI/Framework/ModLoading/Symbols/SymbolReader.cs +++ b/src/SMAPI/Framework/ModLoading/Symbols/SymbolReader.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.IO; using Mono.Cecil; using Mono.Cecil.Cil; diff --git a/src/SMAPI/Framework/ModLoading/Symbols/SymbolReaderProvider.cs b/src/SMAPI/Framework/ModLoading/Symbols/SymbolReaderProvider.cs index 44074337..4af7c1e7 100644 --- a/src/SMAPI/Framework/ModLoading/Symbols/SymbolReaderProvider.cs +++ b/src/SMAPI/Framework/ModLoading/Symbols/SymbolReaderProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; @@ -16,7 +18,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Symbols private readonly ISymbolReaderProvider BaseProvider = new DefaultSymbolReaderProvider(throwIfNoSymbol: false); /// <summary>The symbol data loaded by absolute assembly path.</summary> - private readonly Dictionary<string, Stream> SymbolsByAssemblyPath = new Dictionary<string, Stream>(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary<string, Stream> SymbolsByAssemblyPath = new(StringComparer.OrdinalIgnoreCase); /********* diff --git a/src/SMAPI/Framework/ModLoading/Symbols/SymbolWriterProvider.cs b/src/SMAPI/Framework/ModLoading/Symbols/SymbolWriterProvider.cs index 8f7e05d1..c2ac4cd6 100644 --- a/src/SMAPI/Framework/ModLoading/Symbols/SymbolWriterProvider.cs +++ b/src/SMAPI/Framework/ModLoading/Symbols/SymbolWriterProvider.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.IO; using Mono.Cecil; using Mono.Cecil.Cil; diff --git a/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs b/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs index a4ac54e2..248c29fc 100644 --- a/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs +++ b/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI/Framework/ModRegistry.cs b/src/SMAPI/Framework/ModRegistry.cs index ef389337..cae38637 100644 --- a/src/SMAPI/Framework/ModRegistry.cs +++ b/src/SMAPI/Framework/ModRegistry.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics; @@ -13,7 +15,7 @@ namespace StardewModdingAPI.Framework ** Fields *********/ /// <summary>The registered mod data.</summary> - private readonly List<IModMetadata> Mods = new List<IModMetadata>(); + private readonly List<IModMetadata> Mods = new(); /// <summary>An assembly full name => mod lookup.</summary> private readonly IDictionary<string, IModMetadata> ModNamesByAssembly = new Dictionary<string, IModMetadata>(); @@ -94,10 +96,8 @@ namespace StardewModdingAPI.Framework public IModMetadata GetFromStack() { // get stack frames - StackTrace stack = new StackTrace(); + StackTrace stack = new(); StackFrame[] frames = stack.GetFrames(); - if (frames == null) - return null; // search stack for a source assembly foreach (StackFrame frame in frames) diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index 9174aea6..e74d73b5 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -26,7 +28,7 @@ namespace StardewModdingAPI.Framework.Models }; /// <summary>The default values for <see cref="SuppressUpdateChecks"/>, to log changes if different.</summary> - private static readonly HashSet<string> DefaultSuppressUpdateChecks = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet<string> DefaultSuppressUpdateChecks = new(StringComparer.OrdinalIgnoreCase) { "SMAPI.ConsoleCommands", "SMAPI.ErrorHandler", @@ -52,9 +54,6 @@ namespace StardewModdingAPI.Framework.Models /// <summary>SMAPI's GitHub project name, used to perform update checks.</summary> public string GitHubProjectName { get; set; } - /// <summary>Stardew64Installer's GitHub project name, used to perform update checks.</summary> - public string Stardew64InstallerGitHubProjectName { get; set; } - /// <summary>The base URL for SMAPI's web API, used to perform update checks.</summary> public string WebApiBaseUrl { get; set; } diff --git a/src/SMAPI/Framework/Monitor.cs b/src/SMAPI/Framework/Monitor.cs index ab76e7c0..de145d1d 100644 --- a/src/SMAPI/Framework/Monitor.cs +++ b/src/SMAPI/Framework/Monitor.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -92,7 +94,7 @@ namespace StardewModdingAPI.Framework public void VerboseLog(string message) { if (this.IsVerbose) - this.Log(message, LogLevel.Trace); + this.Log(message); } /// <summary>Write a newline to the console and log file.</summary> diff --git a/src/SMAPI/Framework/Networking/ModMessageModel.cs b/src/SMAPI/Framework/Networking/ModMessageModel.cs index 4f694f9c..4e7d01eb 100644 --- a/src/SMAPI/Framework/Networking/ModMessageModel.cs +++ b/src/SMAPI/Framework/Networking/ModMessageModel.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Linq; using Newtonsoft.Json.Linq; diff --git a/src/SMAPI/Framework/Networking/MultiplayerPeer.cs b/src/SMAPI/Framework/Networking/MultiplayerPeer.cs index 3923700f..8ee5c309 100644 --- a/src/SMAPI/Framework/Networking/MultiplayerPeer.cs +++ b/src/SMAPI/Framework/Networking/MultiplayerPeer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI/Framework/Networking/MultiplayerPeerMod.cs b/src/SMAPI/Framework/Networking/MultiplayerPeerMod.cs index 8087dc7e..6fdb9e54 100644 --- a/src/SMAPI/Framework/Networking/MultiplayerPeerMod.cs +++ b/src/SMAPI/Framework/Networking/MultiplayerPeerMod.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Framework.Networking { internal class MultiplayerPeerMod : IMultiplayerPeerMod diff --git a/src/SMAPI/Framework/Networking/RemoteContextModModel.cs b/src/SMAPI/Framework/Networking/RemoteContextModModel.cs index 9795d971..0383576c 100644 --- a/src/SMAPI/Framework/Networking/RemoteContextModModel.cs +++ b/src/SMAPI/Framework/Networking/RemoteContextModModel.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Framework.Networking { /// <summary>Metadata about an installed mod exchanged with connected computers.</summary> diff --git a/src/SMAPI/Framework/Networking/RemoteContextModel.cs b/src/SMAPI/Framework/Networking/RemoteContextModel.cs index 7befb151..37fafa67 100644 --- a/src/SMAPI/Framework/Networking/RemoteContextModel.cs +++ b/src/SMAPI/Framework/Networking/RemoteContextModel.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Framework.Networking { /// <summary>Metadata about the game, SMAPI, and installed mods exchanged with connected computers.</summary> diff --git a/src/SMAPI/Framework/Networking/SGalaxyNetClient.cs b/src/SMAPI/Framework/Networking/SGalaxyNetClient.cs index 01095c66..8e19b4a7 100644 --- a/src/SMAPI/Framework/Networking/SGalaxyNetClient.cs +++ b/src/SMAPI/Framework/Networking/SGalaxyNetClient.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Galaxy.Api; using StardewValley.Network; diff --git a/src/SMAPI/Framework/Networking/SGalaxyNetServer.cs b/src/SMAPI/Framework/Networking/SGalaxyNetServer.cs index ac9cf313..07a004a2 100644 --- a/src/SMAPI/Framework/Networking/SGalaxyNetServer.cs +++ b/src/SMAPI/Framework/Networking/SGalaxyNetServer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -45,8 +47,8 @@ namespace StardewModdingAPI.Framework.Networking [SuppressMessage("ReSharper", "AccessToDisposedClosure", Justification = "The callback is invoked synchronously.")] protected override void onReceiveMessage(GalaxyID peer, Stream messageStream) { - using IncomingMessage message = new IncomingMessage(); - using BinaryReader reader = new BinaryReader(messageStream); + using IncomingMessage message = new(); + using BinaryReader reader = new(messageStream); message.Read(reader); ulong peerID = peer.ToUint64(); // note: GalaxyID instances get reused, so need to store the underlying ID instead @@ -57,7 +59,7 @@ namespace StardewModdingAPI.Framework.Networking else if (message.MessageType == StardewValley.Multiplayer.playerIntroduction) { NetFarmerRoot farmer = this.Multiplayer.readFarmer(message.Reader); - GalaxyID capturedPeer = new GalaxyID(peerID); + GalaxyID capturedPeer = new(peerID); this.gameServer.checkFarmhandRequest(Convert.ToString(peerID), this.getConnectionId(peer), farmer, msg => this.sendMessage(capturedPeer, msg), () => this.peers[farmer.Value.UniqueMultiplayerID] = capturedPeer.ToUint64()); } }); diff --git a/src/SMAPI/Framework/Networking/SLidgrenClient.cs b/src/SMAPI/Framework/Networking/SLidgrenClient.cs index 39876744..ecf18cbd 100644 --- a/src/SMAPI/Framework/Networking/SLidgrenClient.cs +++ b/src/SMAPI/Framework/Networking/SLidgrenClient.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewValley.Network; diff --git a/src/SMAPI/Framework/Networking/SLidgrenServer.cs b/src/SMAPI/Framework/Networking/SLidgrenServer.cs index 05c8b872..c0b247c8 100644 --- a/src/SMAPI/Framework/Networking/SLidgrenServer.cs +++ b/src/SMAPI/Framework/Networking/SLidgrenServer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -44,9 +46,9 @@ namespace StardewModdingAPI.Framework.Networking { // add hook to call multiplayer core NetConnection peer = rawMessage.SenderConnection; - using IncomingMessage message = new IncomingMessage(); + using IncomingMessage message = new(); using Stream readStream = new NetBufferReadStream(rawMessage); - using BinaryReader reader = new BinaryReader(readStream); + using BinaryReader reader = new(readStream); while (rawMessage.LengthBits - rawMessage.Position >= 8) { diff --git a/src/SMAPI/Framework/Reflection/CacheEntry.cs b/src/SMAPI/Framework/Reflection/CacheEntry.cs index 912662e3..6b18d204 100644 --- a/src/SMAPI/Framework/Reflection/CacheEntry.cs +++ b/src/SMAPI/Framework/Reflection/CacheEntry.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Reflection; namespace StardewModdingAPI.Framework.Reflection diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs index 40adde8e..4c49e219 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Reflection; using System.Reflection.Emit; using Nanoray.Pintail; diff --git a/src/SMAPI/Framework/Reflection/ReflectedField.cs b/src/SMAPI/Framework/Reflection/ReflectedField.cs index 3c4da4fc..921876b9 100644 --- a/src/SMAPI/Framework/Reflection/ReflectedField.cs +++ b/src/SMAPI/Framework/Reflection/ReflectedField.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Reflection; diff --git a/src/SMAPI/Framework/Reflection/ReflectedMethod.cs b/src/SMAPI/Framework/Reflection/ReflectedMethod.cs index 26112806..50f89b40 100644 --- a/src/SMAPI/Framework/Reflection/ReflectedMethod.cs +++ b/src/SMAPI/Framework/Reflection/ReflectedMethod.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Reflection; diff --git a/src/SMAPI/Framework/Reflection/ReflectedProperty.cs b/src/SMAPI/Framework/Reflection/ReflectedProperty.cs index 42d7bb59..a6d8c75c 100644 --- a/src/SMAPI/Framework/Reflection/ReflectedProperty.cs +++ b/src/SMAPI/Framework/Reflection/ReflectedProperty.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Reflection; diff --git a/src/SMAPI/Framework/Reflection/Reflector.cs b/src/SMAPI/Framework/Reflection/Reflector.cs index 889c7ed6..d5938c3f 100644 --- a/src/SMAPI/Framework/Reflection/Reflector.cs +++ b/src/SMAPI/Framework/Reflection/Reflector.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using System.Reflection; @@ -13,7 +15,7 @@ namespace StardewModdingAPI.Framework.Reflection ** Fields *********/ /// <summary>The cached fields and methods found via reflection.</summary> - private readonly MemoryCache Cache = new MemoryCache(typeof(Reflector).FullName); + private readonly MemoryCache Cache = new(typeof(Reflector).FullName); /// <summary>The sliding cache expiration time.</summary> private readonly TimeSpan SlidingCacheExpiry = TimeSpan.FromMinutes(5); @@ -268,7 +270,7 @@ namespace StardewModdingAPI.Framework.Reflection // fetch & cache new value TMemberInfo result = fetch(); - CacheEntry cacheEntry = new CacheEntry(result != null, result); + CacheEntry cacheEntry = new(result != null, result); this.Cache.Add(key, cacheEntry, new CacheItemPolicy { SlidingExpiration = this.SlidingCacheExpiry }); return result; } diff --git a/src/SMAPI/Framework/Rendering/SDisplayDevice.cs b/src/SMAPI/Framework/Rendering/SDisplayDevice.cs index 85e69ae6..8718bcb1 100644 --- a/src/SMAPI/Framework/Rendering/SDisplayDevice.cs +++ b/src/SMAPI/Framework/Rendering/SDisplayDevice.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; diff --git a/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs b/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs index cb499c6b..21edaedd 100644 --- a/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs +++ b/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; diff --git a/src/SMAPI/Framework/RequestExitDelegate.cs b/src/SMAPI/Framework/RequestExitDelegate.cs index 810c399b..93ef1cf9 100644 --- a/src/SMAPI/Framework/RequestExitDelegate.cs +++ b/src/SMAPI/Framework/RequestExitDelegate.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Framework { /// <summary>A delegate which requests that SMAPI immediately exit the game. This should only be invoked when an irrecoverable fatal error happens that risks save corruption or game-breaking bugs.</summary> diff --git a/src/SMAPI/Framework/SChatBox.cs b/src/SMAPI/Framework/SChatBox.cs index e000d1cd..d6286c12 100644 --- a/src/SMAPI/Framework/SChatBox.cs +++ b/src/SMAPI/Framework/SChatBox.cs @@ -1,3 +1,5 @@ +#nullable disable + using StardewValley; using StardewValley.Menus; diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 44f46179..1a58d84b 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -158,9 +160,12 @@ namespace StardewModdingAPI.Framework /// <remarks>This is only intended for use by external code like the Error Handler mod.</remarks> internal static SCore Instance { get; private set; } - /// <summary>The number of update ticks which have already executed. This is similar to <see cref="Game1.ticks"/>, but incremented more consistently for every tick.</summary> + /// <summary>The number of game update ticks which have already executed. This is similar to <see cref="Game1.ticks"/>, but incremented more consistently for every tick.</summary> internal static uint TicksElapsed { get; private set; } + /// <summary>A specialized form of <see cref="TicksElapsed"/> which is incremented each time SMAPI performs a processing tick (whether that's a game update, one wait cycle while synchronizing code, etc).</summary> + internal static uint ProcessTicksElapsed { get; private set; } + /********* ** Public methods @@ -231,13 +236,13 @@ namespace StardewModdingAPI.Framework this.Toolkit.JsonHelper.JsonSettings.Converters.Add(converter); // add error handlers - AppDomain.CurrentDomain.UnhandledException += (sender, e) => this.Monitor.Log($"Critical app domain exception: {e.ExceptionObject}", LogLevel.Error); + AppDomain.CurrentDomain.UnhandledException += (_, e) => this.Monitor.Log($"Critical app domain exception: {e.ExceptionObject}", LogLevel.Error); // add more lenient assembly resolver - AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => AssemblyLoader.ResolveAssembly(e.Name); + AppDomain.CurrentDomain.AssemblyResolve += (_, e) => AssemblyLoader.ResolveAssembly(e.Name); // hook locale event - LocalizedContentManager.OnLanguageChange += locale => this.OnLocaleChanged(); + LocalizedContentManager.OnLanguageChange += _ => this.OnLocaleChanged(); // override game this.Multiplayer = new SMultiplayer(this.Monitor, this.EventManager, this.Toolkit.JsonHelper, this.ModRegistry, this.Reflection, this.OnModMessageReceived, this.Settings.LogNetworkTraffic); @@ -560,6 +565,7 @@ namespace StardewModdingAPI.Framework finally { SCore.TicksElapsed++; + SCore.ProcessTicksElapsed++; } } @@ -578,7 +584,7 @@ namespace StardewModdingAPI.Framework *********/ if (this.JustReturnedToTitle) { - if (!(Game1.mapDisplayDevice is SDisplayDevice)) + if (Game1.mapDisplayDevice is not SDisplayDevice) Game1.mapDisplayDevice = this.GetMapDisplayDevice(); this.JustReturnedToTitle = false; @@ -633,6 +639,8 @@ namespace StardewModdingAPI.Framework this.Reflection.GetMethod(Game1.game1, "UpdateTitleScreen").Invoke(Game1.currentGameTime); // run game logic to change music on load, etc while (Game1.currentLoader?.MoveNext() == true) { + SCore.ProcessTicksElapsed++; + // raise load stage changed switch (Game1.currentLoader.Current) { @@ -1446,41 +1454,6 @@ namespace StardewModdingAPI.Framework this.LogManager.WriteUpdateMarker(updateFound.ToString(), updateUrl); } - // check Stardew64Installer version - if (Constants.IsPatchedByStardew64Installer(out ISemanticVersion patchedByVersion)) - { - ISemanticVersion updateFound = null; - string updateUrl = null; - try - { - // fetch update check - ModEntryModel response = client.GetModInfo(new[] { new ModSearchEntryModel("Steviegt6.Stardew64Installer", patchedByVersion, new[] { $"GitHub:{this.Settings.Stardew64InstallerGitHubProjectName}" }) }, apiVersion: Constants.ApiVersion, gameVersion: Constants.GameVersion, platform: Constants.Platform).Single().Value; - updateFound = response.SuggestedUpdate?.Version; - updateUrl = response.SuggestedUpdate?.Url ?? Constants.HomePageUrl; - - // log message - if (updateFound != null) - this.Monitor.Log($"You can update Stardew64Installer to {updateFound}: {updateUrl}", LogLevel.Alert); - else - this.Monitor.Log(" Stardew64Installer okay."); - - // show errors - if (response.Errors.Any()) - { - this.Monitor.Log("Couldn't check for a new version of Stardew64Installer. This won't affect your game, but you may not be notified of new versions if this keeps happening.", LogLevel.Warn); - this.Monitor.Log($"Error: {string.Join("\n", response.Errors)}"); - } - } - catch (Exception ex) - { - this.Monitor.Log("Couldn't check for a new version of Stardew64Installer. This won't affect your game, but you won't be notified of new versions if this keeps happening.", LogLevel.Warn); - this.Monitor.Log(ex is WebException && ex.InnerException == null - ? $"Error: {ex.Message}" - : $"Error: {ex.GetLogSummary()}" - ); - } - } - // check mod versions if (mods.Any()) { @@ -1622,10 +1595,11 @@ namespace StardewModdingAPI.Framework // initialize loaded non-content-pack mods this.Monitor.Log("Launching mods...", LogLevel.Debug); +#pragma warning disable CS0612, CS0618 // deprecated code foreach (IModMetadata metadata in loadedMods) { // add interceptors - if (metadata.Mod.Helper.Content is ContentHelper helper) + if (metadata.Mod.Helper is ModHelper helper) { // ReSharper disable SuspiciousTypeConversion.Global if (metadata.Mod is IAssetEditor editor) @@ -1653,9 +1627,11 @@ namespace StardewModdingAPI.Framework } // ReSharper restore SuspiciousTypeConversion.Global - helper.ObservableAssetEditors.CollectionChanged += (sender, e) => this.OnAssetInterceptorsChanged(metadata, e.NewItems?.Cast<IAssetEditor>(), e.OldItems?.Cast<IAssetEditor>(), this.ContentCore.Editors); - helper.ObservableAssetLoaders.CollectionChanged += (sender, e) => this.OnAssetInterceptorsChanged(metadata, e.NewItems?.Cast<IAssetLoader>(), e.OldItems?.Cast<IAssetLoader>(), this.ContentCore.Loaders); + ContentHelper content = helper.GetLegacyContentHelper(); + content.ObservableAssetEditors.CollectionChanged += (_, e) => this.OnAssetInterceptorsChanged(metadata, e.NewItems?.Cast<IAssetEditor>(), e.OldItems?.Cast<IAssetEditor>(), this.ContentCore.Editors); + content.ObservableAssetLoaders.CollectionChanged += (_, e) => this.OnAssetInterceptorsChanged(metadata, e.NewItems?.Cast<IAssetLoader>(), e.OldItems?.Cast<IAssetLoader>(), this.ContentCore.Loaders); } +#pragma warning restore CS0612, CS0618 // call entry method try @@ -1777,10 +1753,11 @@ namespace StardewModdingAPI.Framework { IManifest manifest = mod.Manifest; IMonitor monitor = this.LogManager.GetMonitor(mod.DisplayName); + CaseInsensitivePathCache relativePathCache = this.ContentCore.GetCaseInsensitivePathCache(mod.DirectoryPath); GameContentHelper gameContentHelper = new(this.ContentCore, manifest.UniqueID, mod.DisplayName, monitor); - IModContentHelper modContentHelper = new ModContentHelper(this.ContentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager()); + IModContentHelper modContentHelper = new ModContentHelper(this.ContentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager(), relativePathCache); TranslationHelper translationHelper = new(manifest.UniqueID, contentCore.GetLocale(), contentCore.Language); - IContentPack contentPack = new ContentPack(mod.DirectoryPath, manifest, modContentHelper, translationHelper, jsonHelper); + IContentPack contentPack = new ContentPack(mod.DirectoryPath, manifest, modContentHelper, translationHelper, jsonHelper, relativePathCache); mod.SetMod(contentPack, monitor, translationHelper); this.ModRegistry.Add(mod); @@ -1858,11 +1835,14 @@ namespace StardewModdingAPI.Framework IContentPack CreateFakeContentPack(string packDirPath, IManifest packManifest) { IMonitor packMonitor = this.LogManager.GetMonitor(packManifest.Name); + + CaseInsensitivePathCache relativePathCache = this.ContentCore.GetCaseInsensitivePathCache(packDirPath); + GameContentHelper gameContentHelper = new(contentCore, packManifest.UniqueID, packManifest.Name, packMonitor); - IModContentHelper packContentHelper = new ModContentHelper(contentCore, packDirPath, packManifest.UniqueID, packManifest.Name, gameContentHelper.GetUnderlyingContentManager()); + IModContentHelper packContentHelper = new ModContentHelper(contentCore, packDirPath, packManifest.UniqueID, packManifest.Name, gameContentHelper.GetUnderlyingContentManager(), relativePathCache); TranslationHelper packTranslationHelper = new(packManifest.UniqueID, contentCore.GetLocale(), contentCore.Language); - ContentPack contentPack = new(packDirPath, packManifest, packContentHelper, packTranslationHelper, this.Toolkit.JsonHelper); + ContentPack contentPack = new(packDirPath, packManifest, packContentHelper, packTranslationHelper, this.Toolkit.JsonHelper, relativePathCache); this.ReloadTranslationsForTemporaryContentPack(mod, contentPack); mod.FakeContentPacks.Add(new WeakReference<ContentPack>(contentPack)); return contentPack; @@ -1870,9 +1850,12 @@ namespace StardewModdingAPI.Framework IModEvents events = new ModEvents(mod, this.EventManager); ICommandHelper commandHelper = new CommandHelper(mod, this.CommandManager); - IContentHelper contentHelper = new ContentHelper(contentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, monitor); + CaseInsensitivePathCache relativePathCache = this.ContentCore.GetCaseInsensitivePathCache(mod.DirectoryPath); +#pragma warning disable CS0612 // deprecated code + ContentHelper contentHelper = new(contentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, monitor); +#pragma warning restore CS0612 GameContentHelper gameContentHelper = new(contentCore, manifest.UniqueID, mod.DisplayName, monitor); - IModContentHelper modContentHelper = new ModContentHelper(contentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager()); + IModContentHelper modContentHelper = new ModContentHelper(contentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager(), relativePathCache); IContentPackHelper contentPackHelper = new ContentPackHelper(manifest.UniqueID, new Lazy<IContentPack[]>(GetContentPacks), CreateFakeContentPack); IDataHelper dataHelper = new DataHelper(manifest.UniqueID, mod.DirectoryPath, jsonHelper); IReflectionHelper reflectionHelper = new ReflectionHelper(manifest.UniqueID, mod.DisplayName, this.Reflection); diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 104cf330..7ca89eec 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; @@ -37,7 +39,7 @@ namespace StardewModdingAPI.Framework private readonly EventManager Events; /// <summary>The maximum number of consecutive attempts SMAPI should make to recover from a draw error.</summary> - private readonly Countdown DrawCrashTimer = new Countdown(60); // 60 ticks = roughly one second + private readonly Countdown DrawCrashTimer = new(60); // 60 ticks = roughly one second /// <summary>Simplifies access to private game code.</summary> private readonly Reflector Reflection; @@ -71,14 +73,14 @@ namespace StardewModdingAPI.Framework public WatcherCore Watchers { get; private set; } /// <summary>A snapshot of the current <see cref="Watchers"/> state.</summary> - public WatcherSnapshot WatcherSnapshot { get; } = new WatcherSnapshot(); + public WatcherSnapshot WatcherSnapshot { get; } = new(); /// <summary>Whether the current update tick is the first one for this instance.</summary> public bool IsFirstTick = true; /// <summary>The number of ticks until SMAPI should notify mods that the game has loaded.</summary> /// <remarks>Skipping a few frames ensures the game finishes initializing the world before mods try to change it.</remarks> - public Countdown AfterLoadTimer { get; } = new Countdown(5); + public Countdown AfterLoadTimer { get; } = new(5); /// <summary>Whether the game is saving and SMAPI has already raised <see cref="IGameLoopEvents.Saving"/>.</summary> public bool IsBetweenSaveEvents { get; set; } @@ -624,7 +626,7 @@ namespace StardewModdingAPI.Framework if (tile != null) { Vector2 vector_draw_position = Game1.GlobalToLocal(Game1.viewport, tile_position * 64f); - Location draw_location = new Location((int)vector_draw_position.X, (int)vector_draw_position.Y); + Location draw_location = new((int)vector_draw_position.X, (int)vector_draw_position.Y); Game1.mapDisplayDevice.DrawTile(tile, draw_location, (tile_position.Y * 64f - 1f) / 10000f); } } diff --git a/src/SMAPI/Framework/SGameRunner.cs b/src/SMAPI/Framework/SGameRunner.cs index b816bb7c..dae314af 100644 --- a/src/SMAPI/Framework/SGameRunner.cs +++ b/src/SMAPI/Framework/SGameRunner.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -93,7 +95,7 @@ namespace StardewModdingAPI.Framework /// <param name="instanceIndex">The instance index.</param> public override Game1 CreateGameInstance(PlayerIndex playerIndex = PlayerIndex.One, int instanceIndex = 0) { - SInputState inputState = new SInputState(); + SInputState inputState = new(); return new SGame(playerIndex, instanceIndex, this.Monitor, this.Reflection, this.Events, inputState, this.ModHooks, this.Multiplayer, this.ExitGameImmediately, this.OnPlayerInstanceUpdating, this.OnGameContentLoaded); } diff --git a/src/SMAPI/Framework/SModHooks.cs b/src/SMAPI/Framework/SModHooks.cs index 101e022a..7941e102 100644 --- a/src/SMAPI/Framework/SModHooks.cs +++ b/src/SMAPI/Framework/SModHooks.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Threading.Tasks; using StardewValley; diff --git a/src/SMAPI/Framework/SMultiplayer.cs b/src/SMAPI/Framework/SMultiplayer.cs index 5956b63f..de3c25a5 100644 --- a/src/SMAPI/Framework/SMultiplayer.cs +++ b/src/SMAPI/Framework/SMultiplayer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; @@ -56,10 +58,10 @@ namespace StardewModdingAPI.Framework private readonly bool LogNetworkTraffic; /// <summary>The backing field for <see cref="Peers"/>.</summary> - private readonly PerScreen<IDictionary<long, MultiplayerPeer>> PeersImpl = new PerScreen<IDictionary<long, MultiplayerPeer>>(() => new Dictionary<long, MultiplayerPeer>()); + private readonly PerScreen<IDictionary<long, MultiplayerPeer>> PeersImpl = new(() => new Dictionary<long, MultiplayerPeer>()); /// <summary>The backing field for <see cref="HostPeer"/>.</summary> - private readonly PerScreen<MultiplayerPeer> HostPeerImpl = new PerScreen<MultiplayerPeer>(); + private readonly PerScreen<MultiplayerPeer> HostPeerImpl = new(); /********* @@ -111,20 +113,20 @@ namespace StardewModdingAPI.Framework { switch (client) { - case LidgrenClient _: + case LidgrenClient: { string address = this.Reflection.GetField<string>(client, "address").GetValue(); return new SLidgrenClient(address, this.OnClientProcessingMessage, this.OnClientSendingMessage); } - case GalaxyNetClient _: + case GalaxyNetClient: { GalaxyID address = this.Reflection.GetField<GalaxyID>(client, "lobbyId").GetValue(); return new SGalaxyNetClient(address, this.OnClientProcessingMessage, this.OnClientSendingMessage); } default: - this.Monitor.Log($"Unknown multiplayer client type: {client.GetType().AssemblyQualifiedName}", LogLevel.Trace); + this.Monitor.Log($"Unknown multiplayer client type: {client.GetType().AssemblyQualifiedName}"); return client; } } @@ -135,20 +137,20 @@ namespace StardewModdingAPI.Framework { switch (server) { - case LidgrenServer _: + case LidgrenServer: { IGameServer gameServer = this.Reflection.GetField<IGameServer>(server, "gameServer").GetValue(); return new SLidgrenServer(gameServer, this, this.OnServerProcessingMessage); } - case GalaxyNetServer _: + case GalaxyNetServer: { IGameServer gameServer = this.Reflection.GetField<IGameServer>(server, "gameServer").GetValue(); return new SGalaxyNetServer(gameServer, this, this.OnServerProcessingMessage); } default: - this.Monitor.Log($"Unknown multiplayer server type: {server.GetType().AssemblyQualifiedName}", LogLevel.Trace); + this.Monitor.Log($"Unknown multiplayer server type: {server.GetType().AssemblyQualifiedName}"); return server; } } @@ -160,7 +162,7 @@ namespace StardewModdingAPI.Framework protected void OnClientSendingMessage(OutgoingMessage message, Action<OutgoingMessage> sendMessage, Action resume) { if (this.LogNetworkTraffic) - this.Monitor.Log($"CLIENT SEND {(MessageType)message.MessageType} {message.FarmerID}", LogLevel.Trace); + this.Monitor.Log($"CLIENT SEND {(MessageType)message.MessageType} {message.FarmerID}"); switch (message.MessageType) { @@ -184,7 +186,7 @@ namespace StardewModdingAPI.Framework public void OnServerProcessingMessage(IncomingMessage message, Action<OutgoingMessage> sendMessage, Action resume) { if (this.LogNetworkTraffic) - this.Monitor.Log($"SERVER RECV {(MessageType)message.MessageType} {message.FarmerID}", LogLevel.Trace); + this.Monitor.Log($"SERVER RECV {(MessageType)message.MessageType} {message.FarmerID}"); switch (message.MessageType) { @@ -193,10 +195,10 @@ namespace StardewModdingAPI.Framework { // parse message RemoteContextModel model = this.ReadContext(message.Reader); - this.Monitor.Log($"Received context for farmhand {message.FarmerID} running {(model != null ? $"SMAPI {model.ApiVersion} with {model.Mods.Length} mods" : "vanilla")}.", LogLevel.Trace); + this.Monitor.Log($"Received context for farmhand {message.FarmerID} running {(model != null ? $"SMAPI {model.ApiVersion} with {model.Mods.Length} mods" : "vanilla")}."); // store peer - MultiplayerPeer newPeer = new MultiplayerPeer( + MultiplayerPeer newPeer = new( playerID: message.FarmerID, screenID: this.GetScreenId(message.FarmerID), model: model, @@ -243,8 +245,8 @@ namespace StardewModdingAPI.Framework // store peer if new if (!this.Peers.ContainsKey(message.FarmerID)) { - this.Monitor.Log($"Received connection for vanilla player {message.FarmerID}.", LogLevel.Trace); - MultiplayerPeer peer = new MultiplayerPeer( + this.Monitor.Log($"Received connection for vanilla player {message.FarmerID}."); + MultiplayerPeer peer = new( playerID: message.FarmerID, screenID: this.GetScreenId(message.FarmerID), model: null, @@ -280,7 +282,7 @@ namespace StardewModdingAPI.Framework public void OnClientProcessingMessage(IncomingMessage message, Action<OutgoingMessage> sendMessage, Action resume) { if (this.LogNetworkTraffic) - this.Monitor.Log($"CLIENT RECV {(MessageType)message.MessageType} {message.FarmerID}", LogLevel.Trace); + this.Monitor.Log($"CLIENT RECV {(MessageType)message.MessageType} {message.FarmerID}"); switch (message.MessageType) { @@ -289,10 +291,10 @@ namespace StardewModdingAPI.Framework { // parse message RemoteContextModel model = this.ReadContext(message.Reader); - this.Monitor.Log($"Received context for {(model?.IsHost == true ? "host" : "farmhand")} {message.FarmerID} running {(model != null ? $"SMAPI {model.ApiVersion} with {model.Mods.Length} mods" : "vanilla")}.", LogLevel.Trace); + this.Monitor.Log($"Received context for {(model?.IsHost == true ? "host" : "farmhand")} {message.FarmerID} running {(model != null ? $"SMAPI {model.ApiVersion} with {model.Mods.Length} mods" : "vanilla")}."); // store peer - MultiplayerPeer peer = new MultiplayerPeer( + MultiplayerPeer peer = new( playerID: message.FarmerID, screenID: this.GetScreenId(message.FarmerID), model: model, @@ -314,7 +316,7 @@ namespace StardewModdingAPI.Framework // store peer if (!this.Peers.ContainsKey(message.FarmerID) && this.HostPeer == null) { - this.Monitor.Log($"Received connection for vanilla host {message.FarmerID}.", LogLevel.Trace); + this.Monitor.Log($"Received connection for vanilla host {message.FarmerID}."); var peer = new MultiplayerPeer( playerID: message.FarmerID, screenID: this.GetScreenId(message.FarmerID), @@ -341,7 +343,7 @@ namespace StardewModdingAPI.Framework sendMessage: sendMessage, isHost: this.HostPeer == null ); - this.Monitor.Log($"Received connection for vanilla {(peer.IsHost ? "host" : "farmhand")} {message.FarmerID}.", LogLevel.Trace); + this.Monitor.Log($"Received connection for vanilla {(peer.IsHost ? "host" : "farmhand")} {message.FarmerID}."); this.AddPeer(peer, canBeHost: true); } @@ -367,7 +369,7 @@ namespace StardewModdingAPI.Framework { if (this.Peers.TryGetValue(playerID, out MultiplayerPeer peer)) { - this.Monitor.Log($"Player quit: {playerID}", LogLevel.Trace); + this.Monitor.Log($"Player quit: {playerID}"); this.Peers.Remove(playerID); this.EventManager.PeerDisconnected.Raise(new PeerDisconnectedEventArgs(peer)); } @@ -420,7 +422,7 @@ namespace StardewModdingAPI.Framework } // get data to send - ModMessageModel model = new ModMessageModel( + ModMessageModel model = new( fromPlayerID: Game1.player.UniqueMultiplayerID, fromModID: fromModID, toModIDs: toModIDs, @@ -434,7 +436,7 @@ namespace StardewModdingAPI.Framework if (sendToSelf) { if (this.LogNetworkTraffic) - this.Monitor.Log($"Broadcasting '{messageType}' message to self: {data}.", LogLevel.Trace); + this.Monitor.Log($"Broadcasting '{messageType}' message to self: {data}."); this.OnModMessageReceived(model); } @@ -447,7 +449,7 @@ namespace StardewModdingAPI.Framework foreach (MultiplayerPeer peer in sendToPeers) { if (this.LogNetworkTraffic) - this.Monitor.Log($"Broadcasting '{messageType}' message to farmhand {peer.PlayerID}: {data}.", LogLevel.Trace); + this.Monitor.Log($"Broadcasting '{messageType}' message to farmhand {peer.PlayerID}: {data}."); peer.SendMessage(new OutgoingMessage((byte)MessageType.ModMessage, peer.PlayerID, data)); } @@ -455,7 +457,7 @@ namespace StardewModdingAPI.Framework else if (this.HostPeer?.HasSmapi == true) { if (this.LogNetworkTraffic) - this.Monitor.Log($"Broadcasting '{messageType}' message to host {this.HostPeer.PlayerID}: {data}.", LogLevel.Trace); + this.Monitor.Log($"Broadcasting '{messageType}' message to host {this.HostPeer.PlayerID}: {data}."); this.HostPeer.SendMessage(new OutgoingMessage((byte)MessageType.ModMessage, this.HostPeer.PlayerID, data)); } @@ -504,7 +506,7 @@ namespace StardewModdingAPI.Framework ModMessageModel model = this.JsonHelper.Deserialize<ModMessageModel>(json); HashSet<long> playerIDs = new HashSet<long>(model.ToPlayerIDs ?? this.GetKnownPlayerIDs()); if (this.LogNetworkTraffic) - this.Monitor.Log($"Received message: {json}.", LogLevel.Trace); + this.Monitor.Log($"Received message: {json}."); // notify local mods if (playerIDs.Contains(Game1.player.UniqueMultiplayerID)) @@ -513,7 +515,7 @@ namespace StardewModdingAPI.Framework // forward to other players if (Context.IsMainPlayer && playerIDs.Any(p => p != Game1.player.UniqueMultiplayerID)) { - ModMessageModel newModel = new ModMessageModel(model); + ModMessageModel newModel = new(model); foreach (long playerID in playerIDs) { if (playerID != Game1.player.UniqueMultiplayerID && playerID != model.FromPlayerID && this.Peers.TryGetValue(playerID, out MultiplayerPeer peer)) @@ -544,7 +546,7 @@ namespace StardewModdingAPI.Framework /// <summary>Get the fields to include in a context sync message sent to other players.</summary> private object[] GetContextSyncMessageFields() { - RemoteContextModel model = new RemoteContextModel + RemoteContextModel model = new() { IsHost = Context.IsWorldReady && Context.IsMainPlayer, Platform = Constants.TargetPlatform, @@ -571,7 +573,7 @@ namespace StardewModdingAPI.Framework if (!peer.HasSmapi) return new object[] { "{}" }; - RemoteContextModel model = new RemoteContextModel + RemoteContextModel model = new() { IsHost = peer.IsHost, Platform = peer.Platform.Value, diff --git a/src/SMAPI/Framework/Serialization/KeybindConverter.cs b/src/SMAPI/Framework/Serialization/KeybindConverter.cs index 93a274a8..f3bab20d 100644 --- a/src/SMAPI/Framework/Serialization/KeybindConverter.cs +++ b/src/SMAPI/Framework/Serialization/KeybindConverter.cs @@ -36,7 +36,7 @@ namespace StardewModdingAPI.Framework.Serialization /// <param name="objectType">The object type.</param> /// <param name="existingValue">The object being read.</param> /// <param name="serializer">The calling serializer.</param> - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { string path = reader.Path; @@ -44,7 +44,7 @@ namespace StardewModdingAPI.Framework.Serialization { case JsonToken.Null: return objectType == typeof(Keybind) - ? (object)new Keybind() + ? new Keybind() : new KeybindList(); case JsonToken.String: @@ -74,7 +74,7 @@ namespace StardewModdingAPI.Framework.Serialization /// <param name="writer">The JSON writer.</param> /// <param name="value">The value.</param> /// <param name="serializer">The calling serializer.</param> - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { writer.WriteValue(value?.ToString()); } diff --git a/src/SMAPI/Framework/Singleton.cs b/src/SMAPI/Framework/Singleton.cs index 399a8bf0..da16c48e 100644 --- a/src/SMAPI/Framework/Singleton.cs +++ b/src/SMAPI/Framework/Singleton.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Framework { /// <summary>Provides singleton instances of a given type.</summary> @@ -5,6 +7,6 @@ namespace StardewModdingAPI.Framework internal static class Singleton<T> where T : new() { /// <summary>The singleton instance.</summary> - public static T Instance { get; } = new T(); + public static T Instance { get; } = new(); } } diff --git a/src/SMAPI/Framework/SnapshotDiff.cs b/src/SMAPI/Framework/SnapshotDiff.cs index 5b6288ff..eb2aebe1 100644 --- a/src/SMAPI/Framework/SnapshotDiff.cs +++ b/src/SMAPI/Framework/SnapshotDiff.cs @@ -1,3 +1,5 @@ +#nullable disable + using StardewModdingAPI.Framework.StateTracking; namespace StardewModdingAPI.Framework diff --git a/src/SMAPI/Framework/SnapshotItemListDiff.cs b/src/SMAPI/Framework/SnapshotItemListDiff.cs index e8ab1b1e..97942783 100644 --- a/src/SMAPI/Framework/SnapshotItemListDiff.cs +++ b/src/SMAPI/Framework/SnapshotItemListDiff.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Linq; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Framework/SnapshotListDiff.cs b/src/SMAPI/Framework/SnapshotListDiff.cs index 2d0efa0d..1d585c15 100644 --- a/src/SMAPI/Framework/SnapshotListDiff.cs +++ b/src/SMAPI/Framework/SnapshotListDiff.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using StardewModdingAPI.Framework.StateTracking; @@ -11,10 +13,10 @@ namespace StardewModdingAPI.Framework ** Fields *********/ /// <summary>The removed values.</summary> - private readonly List<T> RemovedImpl = new List<T>(); + private readonly List<T> RemovedImpl = new(); /// <summary>The added values.</summary> - private readonly List<T> AddedImpl = new List<T>(); + private readonly List<T> AddedImpl = new(); /********* diff --git a/src/SMAPI/Framework/StateTracking/ChestTracker.cs b/src/SMAPI/Framework/StateTracking/ChestTracker.cs index 65f58ee7..28335200 100644 --- a/src/SMAPI/Framework/StateTracking/ChestTracker.cs +++ b/src/SMAPI/Framework/StateTracking/ChestTracker.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -18,10 +20,10 @@ namespace StardewModdingAPI.Framework.StateTracking private readonly IDictionary<Item, int> StackSizes; /// <summary>Items added since the last update.</summary> - private readonly HashSet<Item> Added = new HashSet<Item>(new ObjectReferenceComparer<Item>()); + private readonly HashSet<Item> Added = new(new ObjectReferenceComparer<Item>()); /// <summary>Items removed since the last update.</summary> - private readonly HashSet<Item> Removed = new HashSet<Item>(new ObjectReferenceComparer<Item>()); + private readonly HashSet<Item> Removed = new(new ObjectReferenceComparer<Item>()); /// <summary>The underlying inventory watcher.</summary> private readonly ICollectionWatcher<Item> InventoryWatcher; diff --git a/src/SMAPI/Framework/StateTracking/Comparers/EquatableComparer.cs b/src/SMAPI/Framework/StateTracking/Comparers/EquatableComparer.cs index a96ffdb6..987e1820 100644 --- a/src/SMAPI/Framework/StateTracking/Comparers/EquatableComparer.cs +++ b/src/SMAPI/Framework/StateTracking/Comparers/EquatableComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Runtime.CompilerServices; diff --git a/src/SMAPI/Framework/StateTracking/Comparers/GenericEqualsComparer.cs b/src/SMAPI/Framework/StateTracking/Comparers/GenericEqualsComparer.cs index cc1d6553..f6b04583 100644 --- a/src/SMAPI/Framework/StateTracking/Comparers/GenericEqualsComparer.cs +++ b/src/SMAPI/Framework/StateTracking/Comparers/GenericEqualsComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Runtime.CompilerServices; diff --git a/src/SMAPI/Framework/StateTracking/Comparers/ObjectReferenceComparer.cs b/src/SMAPI/Framework/StateTracking/Comparers/ObjectReferenceComparer.cs index ef9adafb..8d3a7eb9 100644 --- a/src/SMAPI/Framework/StateTracking/Comparers/ObjectReferenceComparer.cs +++ b/src/SMAPI/Framework/StateTracking/Comparers/ObjectReferenceComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Runtime.CompilerServices; diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/BaseDisposableWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/BaseDisposableWatcher.cs index 60006c51..03bf84d9 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/BaseDisposableWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/BaseDisposableWatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs index 32ec8c7e..52e1dbad 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Linq; @@ -17,10 +19,10 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers private HashSet<TValue> LastValues; /// <summary>The pairs added since the last reset.</summary> - private readonly List<TValue> AddedImpl = new List<TValue>(); + private readonly List<TValue> AddedImpl = new(); /// <summary>The pairs removed since the last reset.</summary> - private readonly List<TValue> RemovedImpl = new List<TValue>(); + private readonly List<TValue> RemovedImpl = new(); /********* diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs index 5ca4b9f4..4f94294c 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ImmutableCollectionWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ImmutableCollectionWatcher.cs index 009e0282..94ce0c8e 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/ImmutableCollectionWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ImmutableCollectionWatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; @@ -11,7 +13,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers ** Accessors *********/ /// <summary>A singleton collection watcher instance.</summary> - public static ImmutableCollectionWatcher<TValue> Instance { get; } = new ImmutableCollectionWatcher<TValue>(); + public static ImmutableCollectionWatcher<TValue> Instance { get; } = new(); /// <summary>Whether the collection changed since the last reset.</summary> public bool IsChanged { get; } = false; diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs index 21e84c47..e662c433 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using Netcode; @@ -15,10 +17,10 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers private readonly NetCollection<TValue> Field; /// <summary>The pairs added since the last reset.</summary> - private readonly List<TValue> AddedImpl = new List<TValue>(); + private readonly List<TValue> AddedImpl = new(); /// <summary>The pairs removed since the last reset.</summary> - private readonly List<TValue> RemovedImpl = new List<TValue>(); + private readonly List<TValue> RemovedImpl = new(); /********* diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs index e6882f7e..0d7f2ad2 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using Netcode; diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetListWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetListWatcher.cs index 0b4d3030..a97e754c 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetListWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetListWatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using Netcode; using StardewModdingAPI.Framework.StateTracking.Comparers; diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs index 48d5d681..26641750 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using Netcode; namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs index c29d2783..82e5387e 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; @@ -16,13 +18,13 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers private readonly ObservableCollection<TValue> Field; /// <summary>The pairs added since the last reset.</summary> - private readonly List<TValue> AddedImpl = new List<TValue>(); + private readonly List<TValue> AddedImpl = new(); /// <summary>The pairs removed since the last reset.</summary> - private readonly List<TValue> RemovedImpl = new List<TValue>(); + private readonly List<TValue> RemovedImpl = new(); /// <summary>The previous values as of the last update.</summary> - private readonly List<TValue> PreviousValues = new List<TValue>(); + private readonly List<TValue> PreviousValues = new(); /********* diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs index bde43486..0b99914c 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Collections.ObjectModel; diff --git a/src/SMAPI/Framework/StateTracking/ICollectionWatcher.cs b/src/SMAPI/Framework/StateTracking/ICollectionWatcher.cs index 7a7759e3..74c9313b 100644 --- a/src/SMAPI/Framework/StateTracking/ICollectionWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/ICollectionWatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; namespace StardewModdingAPI.Framework.StateTracking diff --git a/src/SMAPI/Framework/StateTracking/IDictionaryWatcher.cs b/src/SMAPI/Framework/StateTracking/IDictionaryWatcher.cs index 691ed377..81fb7460 100644 --- a/src/SMAPI/Framework/StateTracking/IDictionaryWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/IDictionaryWatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; namespace StardewModdingAPI.Framework.StateTracking diff --git a/src/SMAPI/Framework/StateTracking/IValueWatcher.cs b/src/SMAPI/Framework/StateTracking/IValueWatcher.cs index 4afca972..7d46053c 100644 --- a/src/SMAPI/Framework/StateTracking/IValueWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/IValueWatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Framework.StateTracking { /// <summary>A watcher which tracks changes to a value.</summary> diff --git a/src/SMAPI/Framework/StateTracking/IWatcher.cs b/src/SMAPI/Framework/StateTracking/IWatcher.cs index 8c7fa51c..3603b6f8 100644 --- a/src/SMAPI/Framework/StateTracking/IWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/IWatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Framework.StateTracking diff --git a/src/SMAPI/Framework/StateTracking/LocationTracker.cs b/src/SMAPI/Framework/StateTracking/LocationTracker.cs index 748e4ecc..9c2ff7f0 100644 --- a/src/SMAPI/Framework/StateTracking/LocationTracker.cs +++ b/src/SMAPI/Framework/StateTracking/LocationTracker.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -19,7 +21,7 @@ namespace StardewModdingAPI.Framework.StateTracking ** Fields *********/ /// <summary>The underlying watchers.</summary> - private readonly List<IWatcher> Watchers = new List<IWatcher>(); + private readonly List<IWatcher> Watchers = new(); /********* diff --git a/src/SMAPI/Framework/StateTracking/PlayerTracker.cs b/src/SMAPI/Framework/StateTracking/PlayerTracker.cs index cf49a7c1..367eafea 100644 --- a/src/SMAPI/Framework/StateTracking/PlayerTracker.cs +++ b/src/SMAPI/Framework/StateTracking/PlayerTracker.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -24,7 +26,7 @@ namespace StardewModdingAPI.Framework.StateTracking private GameLocation LastValidLocation; /// <summary>The underlying watchers.</summary> - private readonly List<IWatcher> Watchers = new List<IWatcher>(); + private readonly List<IWatcher> Watchers = new(); /********* diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs index 6c9cc4f5..3d13f92b 100644 --- a/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs +++ b/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using Microsoft.Xna.Framework; using StardewValley; @@ -17,25 +19,25 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots public GameLocation Location { get; } /// <summary>Tracks added or removed buildings.</summary> - public SnapshotListDiff<Building> Buildings { get; } = new SnapshotListDiff<Building>(); + public SnapshotListDiff<Building> Buildings { get; } = new(); /// <summary>Tracks added or removed debris.</summary> - public SnapshotListDiff<Debris> Debris { get; } = new SnapshotListDiff<Debris>(); + public SnapshotListDiff<Debris> Debris { get; } = new(); /// <summary>Tracks added or removed large terrain features.</summary> - public SnapshotListDiff<LargeTerrainFeature> LargeTerrainFeatures { get; } = new SnapshotListDiff<LargeTerrainFeature>(); + public SnapshotListDiff<LargeTerrainFeature> LargeTerrainFeatures { get; } = new(); /// <summary>Tracks added or removed NPCs.</summary> - public SnapshotListDiff<NPC> Npcs { get; } = new SnapshotListDiff<NPC>(); + public SnapshotListDiff<NPC> Npcs { get; } = new(); /// <summary>Tracks added or removed objects.</summary> - public SnapshotListDiff<KeyValuePair<Vector2, Object>> Objects { get; } = new SnapshotListDiff<KeyValuePair<Vector2, Object>>(); + public SnapshotListDiff<KeyValuePair<Vector2, Object>> Objects { get; } = new(); /// <summary>Tracks added or removed terrain features.</summary> - public SnapshotListDiff<KeyValuePair<Vector2, TerrainFeature>> TerrainFeatures { get; } = new SnapshotListDiff<KeyValuePair<Vector2, TerrainFeature>>(); + public SnapshotListDiff<KeyValuePair<Vector2, TerrainFeature>> TerrainFeatures { get; } = new(); /// <summary>Tracks added or removed furniture.</summary> - public SnapshotListDiff<Furniture> Furniture { get; } = new SnapshotListDiff<Furniture>(); + public SnapshotListDiff<Furniture> Furniture { get; } = new(); /// <summary>Tracks changed chest inventories.</summary> public IDictionary<Chest, SnapshotItemListDiff> ChestItems { get; } = new Dictionary<Chest, SnapshotItemListDiff>(); diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs index 72f45a87..bf81a35e 100644 --- a/src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs +++ b/src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -14,7 +16,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots ** Fields *********/ /// <summary>An empty item list diff.</summary> - private readonly SnapshotItemListDiff EmptyItemListDiff = new SnapshotItemListDiff(Array.Empty<Item>(), Array.Empty<Item>(), Array.Empty<ItemStackSizeChange>()); + private readonly SnapshotItemListDiff EmptyItemListDiff = new(Array.Empty<Item>(), Array.Empty<Item>(), Array.Empty<ItemStackSizeChange>()); /********* @@ -24,14 +26,14 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots public Farmer Player { get; } /// <summary>The player's current location.</summary> - public SnapshotDiff<GameLocation> Location { get; } = new SnapshotDiff<GameLocation>(); + public SnapshotDiff<GameLocation> Location { get; } = new(); /// <summary>Tracks changes to the player's skill levels.</summary> public IDictionary<SkillType, SnapshotDiff<int>> Skills { get; } = Enum .GetValues(typeof(SkillType)) .Cast<SkillType>() - .ToDictionary(skill => skill, skill => new SnapshotDiff<int>()); + .ToDictionary(skill => skill, _ => new SnapshotDiff<int>()); /// <summary>Get a list of inventory changes.</summary> public SnapshotItemListDiff Inventory { get; private set; } diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/WatcherSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/WatcherSnapshot.cs index cf51e040..1d43ef26 100644 --- a/src/SMAPI/Framework/StateTracking/Snapshots/WatcherSnapshot.cs +++ b/src/SMAPI/Framework/StateTracking/Snapshots/WatcherSnapshot.cs @@ -1,3 +1,5 @@ +#nullable disable + using Microsoft.Xna.Framework; using StardewValley; using StardewValley.Menus; @@ -11,31 +13,31 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots ** Accessors *********/ /// <summary>Tracks changes to the window size.</summary> - public SnapshotDiff<Point> WindowSize { get; } = new SnapshotDiff<Point>(); + public SnapshotDiff<Point> WindowSize { get; } = new(); /// <summary>Tracks changes to the current player.</summary> public PlayerSnapshot CurrentPlayer { get; private set; } /// <summary>Tracks changes to the time of day (in 24-hour military format).</summary> - public SnapshotDiff<int> Time { get; } = new SnapshotDiff<int>(); + public SnapshotDiff<int> Time { get; } = new(); /// <summary>Tracks changes to the save ID.</summary> - public SnapshotDiff<ulong> SaveID { get; } = new SnapshotDiff<ulong>(); + public SnapshotDiff<ulong> SaveID { get; } = new(); /// <summary>Tracks changes to the game's locations.</summary> - public WorldLocationsSnapshot Locations { get; } = new WorldLocationsSnapshot(); + public WorldLocationsSnapshot Locations { get; } = new(); /// <summary>Tracks changes to <see cref="Game1.activeClickableMenu"/>.</summary> - public SnapshotDiff<IClickableMenu> ActiveMenu { get; } = new SnapshotDiff<IClickableMenu>(); + public SnapshotDiff<IClickableMenu> ActiveMenu { get; } = new(); /// <summary>Tracks changes to the cursor position.</summary> - public SnapshotDiff<ICursorPosition> Cursor { get; } = new SnapshotDiff<ICursorPosition>(); + public SnapshotDiff<ICursorPosition> Cursor { get; } = new(); /// <summary>Tracks changes to the mouse wheel scroll.</summary> - public SnapshotDiff<int> MouseWheelScroll { get; } = new SnapshotDiff<int>(); + public SnapshotDiff<int> MouseWheelScroll { get; } = new(); /// <summary>Tracks changes to the content locale.</summary> - public SnapshotDiff<LocalizedContentManager.LanguageCode> Locale { get; } = new SnapshotDiff<LocalizedContentManager.LanguageCode>(); + public SnapshotDiff<LocalizedContentManager.LanguageCode> Locale { get; } = new(); /********* diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/WorldLocationsSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/WorldLocationsSnapshot.cs index 73ed2d8f..88aac0df 100644 --- a/src/SMAPI/Framework/StateTracking/Snapshots/WorldLocationsSnapshot.cs +++ b/src/SMAPI/Framework/StateTracking/Snapshots/WorldLocationsSnapshot.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Linq; using StardewModdingAPI.Framework.StateTracking.Comparers; @@ -12,14 +14,14 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots ** Fields *********/ /// <summary>A map of tracked locations.</summary> - private readonly Dictionary<GameLocation, LocationSnapshot> LocationsDict = new Dictionary<GameLocation, LocationSnapshot>(new ObjectReferenceComparer<GameLocation>()); + private readonly Dictionary<GameLocation, LocationSnapshot> LocationsDict = new(new ObjectReferenceComparer<GameLocation>()); /********* ** Accessors *********/ /// <summary>Tracks changes to the location list.</summary> - public SnapshotListDiff<GameLocation> LocationList { get; } = new SnapshotListDiff<GameLocation>(); + public SnapshotListDiff<GameLocation> LocationList { get; } = new(); /// <summary>The tracked locations.</summary> public IEnumerable<LocationSnapshot> Locations => this.LocationsDict.Values; diff --git a/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs b/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs index e968d79c..ab02d7d5 100644 --- a/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs +++ b/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; diff --git a/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs index 173438f1..5f0ecfa0 100644 --- a/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs +++ b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs @@ -1,3 +1,5 @@ +#nullable disable + // This temporary utility fixes an esoteric issue in XNA Framework where deserialization depends on // the order of fields returned by Type.GetFields, but that order changes after Harmony/MonoMod use // reflection to access the fields due to an issue in .NET Framework. @@ -55,7 +57,7 @@ namespace MonoMod.Utils .GetType("System.RuntimeType+RuntimeTypeCache") ?.GetMethod("GetPropertyList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - private static readonly ConditionalWeakTable<Type, CacheFixEntry> _CacheFixed = new ConditionalWeakTable<Type, CacheFixEntry>(); + private static readonly ConditionalWeakTable<Type, CacheFixEntry> _CacheFixed = new(); public static void Apply() { @@ -126,7 +128,7 @@ namespace MonoMod.Utils } public static Type GetRealDeclaringType(this MemberInfo member) - => member.DeclaringType ?? member.Module?.GetModuleType(); + => member.DeclaringType ?? member.Module.GetModuleType(); public static void FixReflectionCache(this Type type) { @@ -144,7 +146,7 @@ namespace MonoMod.Utils continue; CacheFixEntry entry = _CacheFixed.GetValue(type, rt => { - CacheFixEntry entryNew = new CacheFixEntry(); + CacheFixEntry entryNew = new(); object cache; Array properties, fields; diff --git a/src/SMAPI/Framework/Translator.cs b/src/SMAPI/Framework/Translator.cs index 4492b17f..144b043c 100644 --- a/src/SMAPI/Framework/Translator.cs +++ b/src/SMAPI/Framework/Translator.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI/Framework/Utilities/ContextHash.cs b/src/SMAPI/Framework/Utilities/ContextHash.cs index 6c0fdc90..46b9099e 100644 --- a/src/SMAPI/Framework/Utilities/ContextHash.cs +++ b/src/SMAPI/Framework/Utilities/ContextHash.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; diff --git a/src/SMAPI/Framework/Utilities/Countdown.cs b/src/SMAPI/Framework/Utilities/Countdown.cs index 342b4258..94c69e73 100644 --- a/src/SMAPI/Framework/Utilities/Countdown.cs +++ b/src/SMAPI/Framework/Utilities/Countdown.cs @@ -1,4 +1,4 @@ -namespace StardewModdingAPI.Framework.Utilities +namespace StardewModdingAPI.Framework.Utilities { /// <summary>Counts down from a baseline value.</summary> internal class Countdown diff --git a/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs b/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs index 1613a480..94ce0069 100644 --- a/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs +++ b/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs @@ -1,6 +1,7 @@ +#nullable disable + using System; using System.Collections.Generic; -using StardewValley; namespace StardewModdingAPI.Framework.Utilities { @@ -13,7 +14,7 @@ namespace StardewModdingAPI.Framework.Utilities ** Fields *********/ /// <summary>The last game tick for which data was cached.</summary> - private int LastGameTick = -1; + private uint? LastGameTick; /// <summary>The underlying cached data.</summary> private readonly Dictionary<TKey, TValue> Cache = new(); @@ -28,10 +29,10 @@ namespace StardewModdingAPI.Framework.Utilities public TValue GetOrSet(TKey cacheKey, Func<TValue> get) { // clear cache on new tick - if (Game1.ticks != this.LastGameTick) + if (SCore.ProcessTicksElapsed != this.LastGameTick) { this.Cache.Clear(); - this.LastGameTick = Game1.ticks; + this.LastGameTick = SCore.ProcessTicksElapsed; } // fetch value diff --git a/src/SMAPI/Framework/WatcherCore.cs b/src/SMAPI/Framework/WatcherCore.cs index 62a0c3b8..bd8d3367 100644 --- a/src/SMAPI/Framework/WatcherCore.cs +++ b/src/SMAPI/Framework/WatcherCore.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.Collections.ObjectModel; using Microsoft.Xna.Framework; @@ -17,7 +19,7 @@ namespace StardewModdingAPI.Framework ** Fields *********/ /// <summary>The underlying watchers for convenience. These are accessible individually as separate properties.</summary> - private readonly List<IWatcher> Watchers = new List<IWatcher>(); + private readonly List<IWatcher> Watchers = new(); /********* diff --git a/src/SMAPI/GamePlatform.cs b/src/SMAPI/GamePlatform.cs index cce5ed8d..8013faa9 100644 --- a/src/SMAPI/GamePlatform.cs +++ b/src/SMAPI/GamePlatform.cs @@ -1,3 +1,5 @@ +#nullable disable + using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingAPI diff --git a/src/SMAPI/IAssetData.cs b/src/SMAPI/IAssetData.cs index 8df59e53..f07340e4 100644 --- a/src/SMAPI/IAssetData.cs +++ b/src/SMAPI/IAssetData.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI diff --git a/src/SMAPI/IAssetDataForDictionary.cs b/src/SMAPI/IAssetDataForDictionary.cs index 1136316f..82ba25cb 100644 --- a/src/SMAPI/IAssetDataForDictionary.cs +++ b/src/SMAPI/IAssetDataForDictionary.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; namespace StardewModdingAPI diff --git a/src/SMAPI/IAssetDataForImage.cs b/src/SMAPI/IAssetDataForImage.cs index 27ed9267..388caa68 100644 --- a/src/SMAPI/IAssetDataForImage.cs +++ b/src/SMAPI/IAssetDataForImage.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; diff --git a/src/SMAPI/IAssetDataForMap.cs b/src/SMAPI/IAssetDataForMap.cs index 47a33de8..89ee28f2 100644 --- a/src/SMAPI/IAssetDataForMap.cs +++ b/src/SMAPI/IAssetDataForMap.cs @@ -1,3 +1,5 @@ +#nullable disable + using Microsoft.Xna.Framework; using xTile; diff --git a/src/SMAPI/IAssetEditor.cs b/src/SMAPI/IAssetEditor.cs index 9f22ed83..f3d91bd0 100644 --- a/src/SMAPI/IAssetEditor.cs +++ b/src/SMAPI/IAssetEditor.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/IAssetInfo.cs b/src/SMAPI/IAssetInfo.cs index 64d10b35..5b4ac479 100644 --- a/src/SMAPI/IAssetInfo.cs +++ b/src/SMAPI/IAssetInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI diff --git a/src/SMAPI/IAssetLoader.cs b/src/SMAPI/IAssetLoader.cs index 96b98793..0d52a481 100644 --- a/src/SMAPI/IAssetLoader.cs +++ b/src/SMAPI/IAssetLoader.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/IAssetName.cs b/src/SMAPI/IAssetName.cs index c91da266..22f5c6b7 100644 --- a/src/SMAPI/IAssetName.cs +++ b/src/SMAPI/IAssetName.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewValley; diff --git a/src/SMAPI/ICommandHelper.cs b/src/SMAPI/ICommandHelper.cs index 9f1c345c..a0c524d6 100644 --- a/src/SMAPI/ICommandHelper.cs +++ b/src/SMAPI/ICommandHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI diff --git a/src/SMAPI/IContentHelper.cs b/src/SMAPI/IContentHelper.cs index 48f6bfd8..0ad209ab 100644 --- a/src/SMAPI/IContentHelper.cs +++ b/src/SMAPI/IContentHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics.Contracts; diff --git a/src/SMAPI/IContentPack.cs b/src/SMAPI/IContentPack.cs index 3c66faff..f853e2b4 100644 --- a/src/SMAPI/IContentPack.cs +++ b/src/SMAPI/IContentPack.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; diff --git a/src/SMAPI/IContentPackHelper.cs b/src/SMAPI/IContentPackHelper.cs index c48a4f86..5464df22 100644 --- a/src/SMAPI/IContentPackHelper.cs +++ b/src/SMAPI/IContentPackHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; namespace StardewModdingAPI diff --git a/src/SMAPI/ICursorPosition.cs b/src/SMAPI/ICursorPosition.cs index 99c1b84d..da6cbb62 100644 --- a/src/SMAPI/ICursorPosition.cs +++ b/src/SMAPI/ICursorPosition.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Microsoft.Xna.Framework; using StardewValley; diff --git a/src/SMAPI/IDataHelper.cs b/src/SMAPI/IDataHelper.cs index 901266d7..4c96367b 100644 --- a/src/SMAPI/IDataHelper.cs +++ b/src/SMAPI/IDataHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI diff --git a/src/SMAPI/IGameContentHelper.cs b/src/SMAPI/IGameContentHelper.cs index 86bc3e0e..4b967993 100644 --- a/src/SMAPI/IGameContentHelper.cs +++ b/src/SMAPI/IGameContentHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; diff --git a/src/SMAPI/IInputHelper.cs b/src/SMAPI/IInputHelper.cs index 2b907b0d..b7ed0838 100644 --- a/src/SMAPI/IInputHelper.cs +++ b/src/SMAPI/IInputHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using StardewModdingAPI.Utilities; namespace StardewModdingAPI diff --git a/src/SMAPI/IMod.cs b/src/SMAPI/IMod.cs index 44ef32c9..0de4961e 100644 --- a/src/SMAPI/IMod.cs +++ b/src/SMAPI/IMod.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI { /// <summary>The implementation for a Stardew Valley mod.</summary> diff --git a/src/SMAPI/IModContentHelper.cs b/src/SMAPI/IModContentHelper.cs index e3431365..815d6848 100644 --- a/src/SMAPI/IModContentHelper.cs +++ b/src/SMAPI/IModContentHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; diff --git a/src/SMAPI/IModHelper.cs b/src/SMAPI/IModHelper.cs index 15e4ed8d..5e4246aa 100644 --- a/src/SMAPI/IModHelper.cs +++ b/src/SMAPI/IModHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/IModInfo.cs b/src/SMAPI/IModInfo.cs index 3c85d454..2788e4fc 100644 --- a/src/SMAPI/IModInfo.cs +++ b/src/SMAPI/IModInfo.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI { /// <summary>Metadata for a loaded mod.</summary> diff --git a/src/SMAPI/IModLinked.cs b/src/SMAPI/IModLinked.cs index 172ee30c..cf08c9c5 100644 --- a/src/SMAPI/IModLinked.cs +++ b/src/SMAPI/IModLinked.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI { /// <summary>An instance linked to a mod.</summary> diff --git a/src/SMAPI/IModRegistry.cs b/src/SMAPI/IModRegistry.cs index 10b3121e..9cab08a1 100644 --- a/src/SMAPI/IModRegistry.cs +++ b/src/SMAPI/IModRegistry.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; namespace StardewModdingAPI diff --git a/src/SMAPI/IMonitor.cs b/src/SMAPI/IMonitor.cs index c400a211..535f56e3 100644 --- a/src/SMAPI/IMonitor.cs +++ b/src/SMAPI/IMonitor.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI { /// <summary>Encapsulates monitoring and logging for a given module.</summary> diff --git a/src/SMAPI/IMultiplayerHelper.cs b/src/SMAPI/IMultiplayerHelper.cs index 4067a676..77a0f3f4 100644 --- a/src/SMAPI/IMultiplayerHelper.cs +++ b/src/SMAPI/IMultiplayerHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using StardewValley; diff --git a/src/SMAPI/IMultiplayerPeer.cs b/src/SMAPI/IMultiplayerPeer.cs index 47084174..e487f100 100644 --- a/src/SMAPI/IMultiplayerPeer.cs +++ b/src/SMAPI/IMultiplayerPeer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; namespace StardewModdingAPI diff --git a/src/SMAPI/IMultiplayerPeerMod.cs b/src/SMAPI/IMultiplayerPeerMod.cs index 005408b1..81978bef 100644 --- a/src/SMAPI/IMultiplayerPeerMod.cs +++ b/src/SMAPI/IMultiplayerPeerMod.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI { /// <summary>Metadata about a mod installed by a connected player.</summary> diff --git a/src/SMAPI/IReflectedField.cs b/src/SMAPI/IReflectedField.cs index 7ff61f29..94dbe6a3 100644 --- a/src/SMAPI/IReflectedField.cs +++ b/src/SMAPI/IReflectedField.cs @@ -1,4 +1,6 @@ -using System.Reflection; +#nullable disable + +using System.Reflection; namespace StardewModdingAPI { diff --git a/src/SMAPI/IReflectedMethod.cs b/src/SMAPI/IReflectedMethod.cs index 646e7301..78e66cb1 100644 --- a/src/SMAPI/IReflectedMethod.cs +++ b/src/SMAPI/IReflectedMethod.cs @@ -1,4 +1,6 @@ -using System.Reflection; +#nullable disable + +using System.Reflection; namespace StardewModdingAPI { diff --git a/src/SMAPI/IReflectedProperty.cs b/src/SMAPI/IReflectedProperty.cs index 73ad9f30..edbf0b21 100644 --- a/src/SMAPI/IReflectedProperty.cs +++ b/src/SMAPI/IReflectedProperty.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Reflection; namespace StardewModdingAPI diff --git a/src/SMAPI/IReflectionHelper.cs b/src/SMAPI/IReflectionHelper.cs index a2b9eb32..bf7270cf 100644 --- a/src/SMAPI/IReflectionHelper.cs +++ b/src/SMAPI/IReflectionHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI diff --git a/src/SMAPI/ITranslationHelper.cs b/src/SMAPI/ITranslationHelper.cs index b30d9b14..3c297731 100644 --- a/src/SMAPI/ITranslationHelper.cs +++ b/src/SMAPI/ITranslationHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using StardewValley; diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 424abc18..b7cec72c 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -1238,6 +1240,9 @@ namespace StardewModdingAPI.Metadata /// <param name="right">The second value to compare.</param> private bool IsSameBaseName(IAssetName left, string right) { + if (left is null || right is null) + return false; + IAssetName parsedB = this.ParseAssetNameOrNull(right); return this.IsSameBaseName(left, parsedB); } @@ -1247,6 +1252,9 @@ namespace StardewModdingAPI.Metadata /// <param name="right">The second value to compare.</param> private bool IsSameBaseName(IAssetName left, IAssetName right) { + if (left is null || right is null) + return false; + return left.IsEquivalentTo(right.BaseName, useBaseName: true); } diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs index 367372b2..5617fd13 100644 --- a/src/SMAPI/Metadata/InstructionMetadata.cs +++ b/src/SMAPI/Metadata/InstructionMetadata.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Mod.cs b/src/SMAPI/Mod.cs index 9af55cd4..2b3750d5 100644 --- a/src/SMAPI/Mod.cs +++ b/src/SMAPI/Mod.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI diff --git a/src/SMAPI/Patches/Game1Patcher.cs b/src/SMAPI/Patches/Game1Patcher.cs index 173a2055..c5d98e9e 100644 --- a/src/SMAPI/Patches/Game1Patcher.cs +++ b/src/SMAPI/Patches/Game1Patcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics.CodeAnalysis; using HarmonyLib; diff --git a/src/SMAPI/Patches/TitleMenuPatcher.cs b/src/SMAPI/Patches/TitleMenuPatcher.cs index b4320ce0..56e5597c 100644 --- a/src/SMAPI/Patches/TitleMenuPatcher.cs +++ b/src/SMAPI/Patches/TitleMenuPatcher.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics.CodeAnalysis; using HarmonyLib; diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 1e3b2000..0c9c2d87 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; @@ -78,7 +80,7 @@ namespace StardewModdingAPI } catch { - continue; + // ignore invalid DLL } } } @@ -206,7 +208,7 @@ namespace StardewModdingAPI } // load SMAPI - using SCore core = new SCore(modsPath, writeToConsole, developerMode); + using SCore core = new(modsPath, writeToConsole, developerMode); core.RunInteractively(); } diff --git a/src/SMAPI/SButton.cs b/src/SMAPI/SButton.cs index ae825696..133ea901 100644 --- a/src/SMAPI/SButton.cs +++ b/src/SMAPI/SButton.cs @@ -674,7 +674,7 @@ namespace StardewModdingAPI } // mouse - if (input == SButton.MouseLeft || input == SButton.MouseRight) + if (input is SButton.MouseLeft or SButton.MouseRight) { button = new InputButton(mouseLeft: input == SButton.MouseLeft); return true; diff --git a/src/SMAPI/SButtonState.cs b/src/SMAPI/SButtonState.cs index 5f3e8d3c..ca2dd83d 100644 --- a/src/SMAPI/SButtonState.cs +++ b/src/SMAPI/SButtonState.cs @@ -23,7 +23,7 @@ namespace StardewModdingAPI /// <param name="state">The button state.</param> public static bool IsDown(this SButtonState state) { - return state == SButtonState.Held || state == SButtonState.Pressed; + return state is SButtonState.Held or SButtonState.Pressed; } } } diff --git a/src/SMAPI/SMAPI.config.json b/src/SMAPI/SMAPI.config.json index e62c8880..49056e83 100644 --- a/src/SMAPI/SMAPI.config.json +++ b/src/SMAPI/SMAPI.config.json @@ -70,11 +70,6 @@ copy all the settings, or you may cause bugs due to overridden changes in future "GitHubProjectName": "Pathoschild/SMAPI", /** - * Stardew64Installer's GitHub project name, used to perform update checks. - */ - "Stardew64InstallerGitHubProjectName": "Steviegt6/Stardew64Installer", - - /** * The base URL for SMAPI's web API, used to perform update checks. */ "WebApiBaseUrl": "https://smapi.io/api/", diff --git a/src/SMAPI/SemanticVersion.cs b/src/SMAPI/SemanticVersion.cs index ae616419..7a6cdcdd 100644 --- a/src/SMAPI/SemanticVersion.cs +++ b/src/SMAPI/SemanticVersion.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using Newtonsoft.Json; namespace StardewModdingAPI @@ -26,10 +27,10 @@ namespace StardewModdingAPI public int PatchVersion => this.Version.PatchVersion; /// <inheritdoc /> - public string PrereleaseTag => this.Version.PrereleaseTag; + public string? PrereleaseTag => this.Version.PrereleaseTag; /// <inheritdoc /> - public string BuildMetadata => this.Version.BuildMetadata; + public string? BuildMetadata => this.Version.BuildMetadata; /********* @@ -41,7 +42,7 @@ namespace StardewModdingAPI /// <param name="patchVersion">The patch version for backwards-compatible bug fixes.</param> /// <param name="prereleaseTag">An optional prerelease tag.</param> /// <param name="buildMetadata">Optional build metadata. This is ignored when determining version precedence.</param> - public SemanticVersion(int majorVersion, int minorVersion, int patchVersion, string prereleaseTag = null, string buildMetadata = null) + public SemanticVersion(int majorVersion, int minorVersion, int patchVersion, string? prereleaseTag = null, string? buildMetadata = null) : this(majorVersion, minorVersion, patchVersion, 0, prereleaseTag, buildMetadata) { } /// <summary>Construct an instance.</summary> @@ -52,7 +53,7 @@ namespace StardewModdingAPI /// <param name="platformRelease">The platform-specific version (if applicable).</param> /// <param name="buildMetadata">Optional build metadata. This is ignored when determining version precedence.</param> [JsonConstructor] - internal SemanticVersion(int majorVersion, int minorVersion, int patchVersion, int platformRelease, string prereleaseTag = null, string buildMetadata = null) + internal SemanticVersion(int majorVersion, int minorVersion, int patchVersion, int platformRelease, string? prereleaseTag = null, string? buildMetadata = null) : this(new Toolkit.SemanticVersion(majorVersion, minorVersion, patchVersion, platformRelease, prereleaseTag, buildMetadata)) { } /// <summary>Construct an instance.</summary> @@ -91,49 +92,49 @@ namespace StardewModdingAPI /// <inheritdoc /> /// <remarks>The implementation is defined by Semantic Version 2.0 (https://semver.org/).</remarks> - public int CompareTo(ISemanticVersion other) + public int CompareTo(ISemanticVersion? other) { return this.Version.CompareTo(other); } /// <inheritdoc /> - public bool IsOlderThan(ISemanticVersion other) + public bool IsOlderThan(ISemanticVersion? other) { return this.Version.IsOlderThan(other); } /// <inheritdoc /> - public bool IsOlderThan(string other) + public bool IsOlderThan(string? other) { return this.Version.IsOlderThan(other); } /// <inheritdoc /> - public bool IsNewerThan(ISemanticVersion other) + public bool IsNewerThan(ISemanticVersion? other) { return this.Version.IsNewerThan(other); } /// <inheritdoc /> - public bool IsNewerThan(string other) + public bool IsNewerThan(string? other) { return this.Version.IsNewerThan(other); } /// <inheritdoc /> - public bool IsBetween(ISemanticVersion min, ISemanticVersion max) + public bool IsBetween(ISemanticVersion? min, ISemanticVersion? max) { return this.Version.IsBetween(min, max); } /// <inheritdoc /> - public bool IsBetween(string min, string max) + public bool IsBetween(string? min, string? max) { return this.Version.IsBetween(min, max); } /// <inheritdoc /> - public bool Equals(ISemanticVersion other) + public bool Equals(ISemanticVersion? other) { return other != null && this.CompareTo(other) == 0; } @@ -154,9 +155,9 @@ namespace StardewModdingAPI /// <param name="version">The version string.</param> /// <param name="parsed">The parsed representation.</param> /// <returns>Returns whether parsing the version succeeded.</returns> - public static bool TryParse(string version, out ISemanticVersion parsed) + public static bool TryParse(string version, [NotNullWhen(true)] out ISemanticVersion? parsed) { - if (Toolkit.SemanticVersion.TryParse(version, out ISemanticVersion versionImpl)) + if (Toolkit.SemanticVersion.TryParse(version, out ISemanticVersion? versionImpl)) { parsed = new SemanticVersion(versionImpl); return true; diff --git a/src/SMAPI/Translation.cs b/src/SMAPI/Translation.cs index 149f6728..ef98a00f 100644 --- a/src/SMAPI/Translation.cs +++ b/src/SMAPI/Translation.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections; using System.Collections.Generic; @@ -74,7 +76,7 @@ namespace StardewModdingAPI { foreach (DictionaryEntry entry in inputLookup) { - string key = entry.Key?.ToString().Trim(); + string key = entry.Key.ToString()?.Trim(); if (key != null) tokenLookup[key] = entry.Value?.ToString(); } diff --git a/src/SMAPI/Utilities/CaseInsensitivePathCache.cs b/src/SMAPI/Utilities/CaseInsensitivePathCache.cs new file mode 100644 index 00000000..4596fdce --- /dev/null +++ b/src/SMAPI/Utilities/CaseInsensitivePathCache.cs @@ -0,0 +1,126 @@ +#nullable disable + +using System; +using System.Collections.Generic; +using System.IO; + +namespace StardewModdingAPI.Utilities +{ + /// <summary>Provides an API for case-insensitive relative path lookups within a root directory.</summary> + internal class CaseInsensitivePathCache + { + /********* + ** Fields + *********/ + /// <summary>The root directory path for relative paths.</summary> + private readonly string RootPath; + + /// <summary>A case-insensitive lookup of file paths within the <see cref="RootPath"/>. Each path is listed in both file path and asset name format, so it's usable in both contexts without needing to re-parse paths.</summary> + private readonly Lazy<Dictionary<string, string>> RelativePathCache; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="rootPath">The root directory path for relative paths.</param> + public CaseInsensitivePathCache(string rootPath) + { + this.RootPath = rootPath; + this.RelativePathCache = new(this.GetRelativePathCache); + } + + /// <summary>Get the exact capitalization for a given relative file path.</summary> + /// <param name="relativePath">The relative path.</param> + /// <remarks>Returns the resolved path in file path format, else the normalized <paramref name="relativePath"/>.</remarks> + public string GetFilePath(string relativePath) + { + return this.GetImpl(PathUtilities.NormalizePath(relativePath)); + } + + /// <summary>Get the exact capitalization for a given asset name.</summary> + /// <param name="relativePath">The relative path.</param> + /// <remarks>Returns the resolved path in asset name format, else the normalized <paramref name="relativePath"/>.</remarks> + public string GetAssetName(string relativePath) + { + return this.GetImpl(PathUtilities.NormalizeAssetName(relativePath)); + } + + /// <summary>Add a relative path that was just created by a SMAPI API.</summary> + /// <param name="relativePath">The relative path. This must already be normalized in asset name or file path format.</param> + public void Add(string relativePath) + { + // skip if cache isn't created yet (no need to add files manually in that case) + if (!this.RelativePathCache.IsValueCreated) + return; + + // skip if already cached + if (this.RelativePathCache.Value.ContainsKey(relativePath)) + return; + + // make sure path exists + relativePath = PathUtilities.NormalizePath(relativePath); + if (!File.Exists(Path.Combine(this.RootPath, relativePath))) + throw new InvalidOperationException($"Can't add relative path '{relativePath}' to the case-insensitive cache for '{this.RootPath}' because that file doesn't exist."); + + // cache path + this.CacheRawPath(this.RelativePathCache.Value, relativePath); + } + + + /********* + ** Private methods + *********/ + /// <summary>Get the exact capitalization for a given relative path.</summary> + /// <param name="relativePath">The relative path. This must already be normalized into asset name or file path format (i.e. using <see cref="PathUtilities.NormalizeAssetName"/> or <see cref="PathUtilities.NormalizePath"/> respectively).</param> + /// <remarks>Returns the resolved path in the same format if found, else returns the path as-is.</remarks> + private string GetImpl(string relativePath) + { + // invalid path + if (string.IsNullOrWhiteSpace(relativePath)) + return relativePath; + + // already cached + if (this.RelativePathCache.Value.TryGetValue(relativePath, out string resolved)) + return resolved; + + // file exists but isn't cached for some reason + // cache it now so any later references to it are case-insensitive + if (File.Exists(Path.Combine(this.RootPath, relativePath))) + { + this.CacheRawPath(this.RelativePathCache.Value, relativePath); + return relativePath; + } + + // no such file, keep capitalization as-is + return relativePath; + } + + /// <summary>Get a case-insensitive lookup of file paths (see <see cref="RelativePathCache"/>).</summary> + private Dictionary<string, string> GetRelativePathCache() + { + Dictionary<string, string> cache = new(StringComparer.OrdinalIgnoreCase); + + foreach (string path in Directory.EnumerateFiles(this.RootPath, "*", SearchOption.AllDirectories)) + { + string relativePath = path.Substring(this.RootPath.Length + 1); + + this.CacheRawPath(cache, relativePath); + } + + return cache; + } + + /// <summary>Add a raw relative path to the cache.</summary> + /// <param name="cache">The cache to update.</param> + /// <param name="relativePath">The relative path to cache, with its exact filesystem capitalization.</param> + private void CacheRawPath(IDictionary<string, string> cache, string relativePath) + { + string filePath = PathUtilities.NormalizePath(relativePath); + string assetName = PathUtilities.NormalizeAssetName(relativePath); + + cache[filePath] = filePath; + cache[assetName] = assetName; + } + } +} diff --git a/src/SMAPI/Utilities/Keybind.cs b/src/SMAPI/Utilities/Keybind.cs index 403ecf4a..7b1acf1d 100644 --- a/src/SMAPI/Utilities/Keybind.cs +++ b/src/SMAPI/Utilities/Keybind.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -118,11 +120,11 @@ namespace StardewModdingAPI.Utilities return SButtonState.None; // mix of held + pressed => pressed - if (states.All(p => p == SButtonState.Pressed || p == SButtonState.Held)) + if (states.All(p => p is SButtonState.Pressed or SButtonState.Held)) return SButtonState.Pressed; // mix of held + released => released - if (states.All(p => p == SButtonState.Held || p == SButtonState.Released)) + if (states.All(p => p is SButtonState.Held or SButtonState.Released)) return SButtonState.Released; // not down last tick or now diff --git a/src/SMAPI/Utilities/KeybindList.cs b/src/SMAPI/Utilities/KeybindList.cs index f8f569af..7b2c396b 100644 --- a/src/SMAPI/Utilities/KeybindList.cs +++ b/src/SMAPI/Utilities/KeybindList.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -139,7 +141,7 @@ namespace StardewModdingAPI.Utilities public bool IsDown() { SButtonState state = this.GetState(); - return state == SButtonState.Pressed || state == SButtonState.Held; + return state is SButtonState.Pressed or SButtonState.Held; } /// <summary>Get whether the input binding was just pressed this tick.</summary> diff --git a/src/SMAPI/Utilities/PathUtilities.cs b/src/SMAPI/Utilities/PathUtilities.cs index 541b163c..4350f441 100644 --- a/src/SMAPI/Utilities/PathUtilities.cs +++ b/src/SMAPI/Utilities/PathUtilities.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using ToolkitPathUtilities = StardewModdingAPI.Toolkit.Utilities.PathUtilities; @@ -20,14 +21,16 @@ namespace StardewModdingAPI.Utilities /// <param name="path">The path to split.</param> /// <param name="limit">The number of segments to match. Any additional segments will be merged into the last returned part.</param> [Pure] - public static string[] GetSegments(string path, int? limit = null) + public static string[] GetSegments(string? path, int? limit = null) { return ToolkitPathUtilities.GetSegments(path, limit); } /// <summary>Normalize an asset name to match how MonoGame's content APIs would normalize and cache it.</summary> /// <param name="assetName">The asset name to normalize.</param> - public static string NormalizeAssetName(string assetName) + [Pure] + [return: NotNullIfNotNull("assetName")] + public static string? NormalizeAssetName(string? assetName) { return ToolkitPathUtilities.NormalizeAssetName(assetName); } @@ -36,7 +39,8 @@ namespace StardewModdingAPI.Utilities /// <param name="path">The file path to normalize.</param> /// <remarks>This should only be used for file paths. For asset names, use <see cref="NormalizeAssetName"/> instead.</remarks> [Pure] - public static string NormalizePath(string path) + [return: NotNullIfNotNull("path")] + public static string? NormalizePath(string? path) { return ToolkitPathUtilities.NormalizePath(path); } @@ -44,7 +48,7 @@ namespace StardewModdingAPI.Utilities /// <summary>Get whether a path is relative and doesn't try to climb out of its containing folder (e.g. doesn't contain <c>../</c>).</summary> /// <param name="path">The path to check.</param> [Pure] - public static bool IsSafeRelativePath(string path) + public static bool IsSafeRelativePath(string? path) { return ToolkitPathUtilities.IsSafeRelativePath(path); } @@ -52,7 +56,7 @@ namespace StardewModdingAPI.Utilities /// <summary>Get whether a string is a valid 'slug', containing only basic characters that are safe in all contexts (e.g. filenames, URLs, etc).</summary> /// <param name="str">The string to check.</param> [Pure] - public static bool IsSlug(string str) + public static bool IsSlug(string? str) { return ToolkitPathUtilities.IsSlug(str); } diff --git a/src/SMAPI/Utilities/PerScreen.cs b/src/SMAPI/Utilities/PerScreen.cs index 20b8fbce..afe3ba91 100644 --- a/src/SMAPI/Utilities/PerScreen.cs +++ b/src/SMAPI/Utilities/PerScreen.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; @@ -76,7 +78,7 @@ namespace StardewModdingAPI.Utilities /// <summary>Remove all active values.</summary> public void ResetAllScreens() { - this.RemoveScreens(p => true); + this.RemoveScreens(_ => true); } diff --git a/src/SMAPI/Utilities/SDate.cs b/src/SMAPI/Utilities/SDate.cs index e10a59f8..b10bc3da 100644 --- a/src/SMAPI/Utilities/SDate.cs +++ b/src/SMAPI/Utilities/SDate.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Linq; using Newtonsoft.Json; |