From a54d58d064df5654e845f9b2b4fc9db5b0189db0 Mon Sep 17 00:00:00 2001 From: Shockah Date: Thu, 10 Feb 2022 16:26:43 +0100 Subject: add TryProxy for any objects --- .../Framework/ModHelpers/ModRegistryHelper.cs | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index ef1ad30c..92c52b00 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -98,5 +98,31 @@ namespace StardewModdingAPI.Framework.ModHelpers return castApi; return this.ProxyFactory.CreateProxy(api, this.ModID, uniqueID); } + + /// + public bool TryProxy(string uniqueID, object toProxy, out TInterface proxy) where TInterface : class + { + try + { + foreach (var toProxyInterface in toProxy.GetType().GetInterfaces()) + { + var unproxyBuilder = this.ProxyFactory.ObtainBuilder(typeof(TInterface), toProxyInterface, this.ModID, uniqueID); + if (unproxyBuilder.TryUnproxy(toProxy, out object targetInstance)) + { + proxy = (TInterface)targetInstance; + return true; + } + } + + var proxyBuilder = this.ProxyFactory.ObtainBuilder(toProxy.GetType(), typeof(TInterface), this.ModID, uniqueID); + proxy = (TInterface)proxyBuilder.ObtainInstance(toProxy, this.ProxyFactory); + return true; + } + catch + { + proxy = null; + return false; + } + } } } -- cgit From ba42bb97d1f1df07e87b02ee0e03eef0227ea5a7 Mon Sep 17 00:00:00 2001 From: Shockah Date: Wed, 16 Feb 2022 19:08:40 +0100 Subject: move proxying to a separate NuGet library --- build/common.targets | 1 + src/SMAPI.Tests/SMAPI.Tests.csproj | 1 + .../Framework/ModHelpers/ModRegistryHelper.cs | 37 +- .../Framework/Reflection/InterfaceProxyBuilder.cs | 405 --------------------- .../Framework/Reflection/InterfaceProxyFactory.cs | 84 ----- .../Framework/Reflection/InterfaceProxyGlue.cs | 34 -- src/SMAPI/Framework/SCore.cs | 16 +- src/SMAPI/IModRegistry.cs | 7 - src/SMAPI/SMAPI.csproj | 1 + 9 files changed, 20 insertions(+), 566 deletions(-) delete mode 100644 src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs delete mode 100644 src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs delete mode 100644 src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/build/common.targets b/build/common.targets index 86624b62..bcb0e9e1 100644 --- a/build/common.targets +++ b/build/common.targets @@ -53,6 +53,7 @@ + diff --git a/src/SMAPI.Tests/SMAPI.Tests.csproj b/src/SMAPI.Tests/SMAPI.Tests.csproj index 8329b2e1..e27dbecc 100644 --- a/src/SMAPI.Tests/SMAPI.Tests.csproj +++ b/src/SMAPI.Tests/SMAPI.Tests.csproj @@ -20,6 +20,7 @@ + diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 92c52b00..93ea6028 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Nanoray.Pintail; using StardewModdingAPI.Framework.Reflection; namespace StardewModdingAPI.Framework.ModHelpers @@ -19,7 +20,7 @@ namespace StardewModdingAPI.Framework.ModHelpers private readonly HashSet AccessedModApis = new HashSet(); /// Generates proxy classes to access mod APIs through an arbitrary interface. - private readonly InterfaceProxyFactory ProxyFactory; + private readonly IProxyManager ProxyManager; /********* @@ -28,13 +29,13 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Construct an instance. /// The unique ID of the relevant mod. /// The underlying mod registry. - /// Generates proxy classes to access mod APIs through an arbitrary interface. + /// Generates proxy classes to access mod APIs through an arbitrary interface. /// Encapsulates monitoring and logging for the mod. - public ModRegistryHelper(string modID, ModRegistry registry, InterfaceProxyFactory proxyFactory, IMonitor monitor) + public ModRegistryHelper(string modID, ModRegistry registry, IProxyManager proxyManager, IMonitor monitor) : base(modID) { this.Registry = registry; - this.ProxyFactory = proxyFactory; + this.ProxyManager = proxyManager; this.Monitor = monitor; } @@ -96,33 +97,7 @@ namespace StardewModdingAPI.Framework.ModHelpers // get API of type if (api is TInterface castApi) return castApi; - return this.ProxyFactory.CreateProxy(api, this.ModID, uniqueID); - } - - /// - public bool TryProxy(string uniqueID, object toProxy, out TInterface proxy) where TInterface : class - { - try - { - foreach (var toProxyInterface in toProxy.GetType().GetInterfaces()) - { - var unproxyBuilder = this.ProxyFactory.ObtainBuilder(typeof(TInterface), toProxyInterface, this.ModID, uniqueID); - if (unproxyBuilder.TryUnproxy(toProxy, out object targetInstance)) - { - proxy = (TInterface)targetInstance; - return true; - } - } - - var proxyBuilder = this.ProxyFactory.ObtainBuilder(toProxy.GetType(), typeof(TInterface), this.ModID, uniqueID); - proxy = (TInterface)proxyBuilder.ObtainInstance(toProxy, this.ProxyFactory); - return true; - } - catch - { - proxy = null; - return false; - } + return this.ProxyManager.ObtainProxy(api, this.ModID, uniqueID); } } } diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs deleted file mode 100644 index 81cfff50..00000000 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs +++ /dev/null @@ -1,405 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using System.Runtime.CompilerServices; - -namespace StardewModdingAPI.Framework.Reflection -{ - /// Generates a proxy class to access a mod API through an arbitrary interface. - internal class InterfaceProxyBuilder - { - /********* - ** Consts - *********/ - private static readonly string TargetFieldName = "__Target"; - private static readonly string GlueFieldName = "__Glue"; - private static readonly MethodInfo ObtainInstanceForProxyTypeNameMethod = typeof(InterfaceProxyGlue).GetMethod(nameof(InterfaceProxyGlue.ObtainInstanceForProxyTypeName), new Type[] { typeof(string), typeof(object) }); - private static readonly MethodInfo UnproxyOrObtainInstanceForProxyTypeNameMethod = typeof(InterfaceProxyGlue).GetMethod(nameof(InterfaceProxyGlue.UnproxyOrObtainInstanceForProxyTypeName), new Type[] { typeof(string), typeof(string), typeof(object) }); - - /********* - ** Fields - *********/ - /// The target class type. - private readonly Type TargetType; - - /// The interfce type. - private readonly Type InterfaceType; - - /// The full name of the generated proxy type. - private readonly string ProxyTypeName; - - /// The generated proxy type. - private Type ProxyType; - - /// A cache of all proxies generated by this builder. - private readonly ConditionalWeakTable ProxyCache = new(); - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The target type. - /// The interface type to implement. - /// The type name to generate. - public InterfaceProxyBuilder(Type targetType, Type interfaceType, string proxyTypeName) - { - // validate and store - this.TargetType = targetType ?? throw new ArgumentNullException(nameof(targetType)); - this.InterfaceType = interfaceType ?? throw new ArgumentNullException(nameof(interfaceType)); - this.ProxyTypeName = proxyTypeName ?? throw new ArgumentNullException(nameof(proxyTypeName)); - if (!interfaceType.IsInterface) - throw new ArgumentException($"{nameof(interfaceType)} is not an interface."); - } - - - /// Creates and sets up the proxy type. - /// The that requested to build a proxy. - /// The CLR module in which to create proxy classes. - /// The unique ID of the mod consuming the API. - /// The unique ID of the mod providing the API. - public void SetupProxyType(InterfaceProxyFactory factory, ModuleBuilder moduleBuilder, string sourceModID, string targetModID) - { - // define proxy type - TypeBuilder proxyBuilder = moduleBuilder.DefineType(this.ProxyTypeName, TypeAttributes.Public | TypeAttributes.Class); - proxyBuilder.AddInterfaceImplementation(this.InterfaceType); - - // create fields to store target instance and proxy factory - FieldBuilder targetField = proxyBuilder.DefineField(TargetFieldName, this.TargetType, FieldAttributes.Private); - FieldBuilder glueField = proxyBuilder.DefineField(GlueFieldName, typeof(InterfaceProxyGlue), FieldAttributes.Private); - - // create constructor which accepts target instance + factory, and sets fields - { - ConstructorBuilder constructor = proxyBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, new[] { this.TargetType, typeof(InterfaceProxyGlue) }); - ILGenerator il = constructor.GetILGenerator(); - - il.Emit(OpCodes.Ldarg_0); // this - // ReSharper disable once AssignNullToNotNullAttribute -- never null - il.Emit(OpCodes.Call, typeof(object).GetConstructor(Array.Empty())); // call base constructor - il.Emit(OpCodes.Ldarg_0); // this - il.Emit(OpCodes.Ldarg_1); // load argument - il.Emit(OpCodes.Stfld, targetField); // set field to loaded argument - il.Emit(OpCodes.Ldarg_0); // this - il.Emit(OpCodes.Ldarg_2); // load argument - il.Emit(OpCodes.Stfld, glueField); // set field to loaded argument - il.Emit(OpCodes.Ret); - } - - var allTargetMethods = this.TargetType.GetMethods().ToList(); - foreach (Type targetInterface in this.TargetType.GetInterfaces()) - { - foreach (MethodInfo targetMethod in targetInterface.GetMethods()) - { - if (!targetMethod.IsAbstract) - allTargetMethods.Add(targetMethod); - } - } - - MatchingTypesResult AreTypesMatching(Type targetType, Type proxyType, MethodTypeMatchingPart part) - { - var typeA = part == MethodTypeMatchingPart.Parameter ? targetType : proxyType; - var typeB = part == MethodTypeMatchingPart.Parameter ? proxyType : targetType; - - if (typeA.IsGenericMethodParameter != typeB.IsGenericMethodParameter) - return MatchingTypesResult.False; - // TODO: decide if "assignable" checking is desired (instead of just 1:1 type equality) - if (typeA.IsGenericMethodParameter ? typeA.GenericParameterPosition == typeB.GenericParameterPosition : typeA.IsAssignableFrom(typeB)) - return MatchingTypesResult.True; - - if (!proxyType.IsGenericMethodParameter) - { - if (proxyType.GetNonRefType().IsInterface) - return MatchingTypesResult.IfProxied; - if (targetType.GetNonRefType().IsInterface) - return MatchingTypesResult.IfProxied; - } - - return MatchingTypesResult.False; - } - - // proxy methods - foreach (MethodInfo proxyMethod in this.InterfaceType.GetMethods()) - { - var proxyMethodParameters = proxyMethod.GetParameters(); - var proxyMethodGenericArguments = proxyMethod.GetGenericArguments(); - - foreach (MethodInfo targetMethod in allTargetMethods) - { - // checking if `targetMethod` matches `proxyMethod` - - if (targetMethod.Name != proxyMethod.Name) - continue; - if (targetMethod.GetGenericArguments().Length != proxyMethodGenericArguments.Length) - continue; - var positionsToProxy = new HashSet(); // null = return type; anything else = parameter position - - switch (AreTypesMatching(targetMethod.ReturnType, proxyMethod.ReturnType, MethodTypeMatchingPart.ReturnType)) - { - case MatchingTypesResult.False: - continue; - case MatchingTypesResult.True: - break; - case MatchingTypesResult.IfProxied: - positionsToProxy.Add(null); - break; - } - - var mParameters = targetMethod.GetParameters(); - if (mParameters.Length != proxyMethodParameters.Length) - continue; - for (int i = 0; i < mParameters.Length; i++) - { - switch (AreTypesMatching(mParameters[i].ParameterType, proxyMethodParameters[i].ParameterType, MethodTypeMatchingPart.Parameter)) - { - case MatchingTypesResult.False: - goto targetMethodLoopContinue; - case MatchingTypesResult.True: - break; - case MatchingTypesResult.IfProxied: - positionsToProxy.Add(i); - break; - } - } - - // method matched; proxying - - this.ProxyMethod(factory, proxyBuilder, proxyMethod, targetMethod, targetField, glueField, positionsToProxy, sourceModID, targetModID); - goto proxyMethodLoopContinue; - targetMethodLoopContinue:; - } - - throw new InvalidOperationException($"The {this.InterfaceType.FullName} interface defines method {proxyMethod.Name} which doesn't exist in the API."); - proxyMethodLoopContinue:; - } - - // save info - this.ProxyType = proxyBuilder.CreateType(); - } - - /// Get an existing or create a new instance of the proxy for a target instance. - /// The target instance. - /// The that requested to build a proxy. - public object ObtainInstance(object targetInstance, InterfaceProxyFactory factory) - { - if (this.ProxyCache.TryGetValue(targetInstance, out object proxyInstance)) - return proxyInstance; - - ConstructorInfo constructor = this.ProxyType.GetConstructor(new[] { this.TargetType, typeof(InterfaceProxyGlue) }); - if (constructor == null) - throw new InvalidOperationException($"Couldn't find the constructor for generated proxy type '{this.ProxyType.Name}'."); // should never happen - proxyInstance = constructor.Invoke(new[] { targetInstance, new InterfaceProxyGlue(factory) }); - this.ProxyCache.Add(targetInstance, proxyInstance); - return proxyInstance; - } - - - /********* - ** Private methods - *********/ - /// Define a method which proxies access to a method on the target. - /// The that requested to build a proxy. - /// The proxy type being generated. - /// The proxy method. - /// The target method. - /// The proxy field containing the API instance. - /// The proxy field containing an . - /// Parameter type positions (or null for the return type) for which types should also be proxied. - /// The unique ID of the mod consuming the API. - /// The unique ID of the mod providing the API. - private void ProxyMethod(InterfaceProxyFactory factory, TypeBuilder proxyBuilder, MethodInfo proxy, MethodInfo target, FieldBuilder instanceField, FieldBuilder glueField, ISet positionsToProxy, string sourceModID, string targetModID) - { - MethodBuilder methodBuilder = proxyBuilder.DefineMethod(proxy.Name, MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual); - - // set up generic arguments - Type[] proxyGenericArguments = proxy.GetGenericArguments(); - string[] genericArgNames = proxyGenericArguments.Select(a => a.Name).ToArray(); - GenericTypeParameterBuilder[] genericTypeParameterBuilders = proxyGenericArguments.Length == 0 ? null : methodBuilder.DefineGenericParameters(genericArgNames); - for (int i = 0; i < proxyGenericArguments.Length; i++) - genericTypeParameterBuilders[i].SetGenericParameterAttributes(proxyGenericArguments[i].GenericParameterAttributes); - - // set up return type - Type returnType = proxy.ReturnType.IsGenericMethodParameter ? genericTypeParameterBuilders[proxy.ReturnType.GenericParameterPosition] : proxy.ReturnType; - methodBuilder.SetReturnType(returnType); - - // set up parameters - var targetParameters = target.GetParameters(); - Type[] argTypes = proxy.GetParameters() - .Select(a => a.ParameterType) - .Select(t => t.IsGenericMethodParameter ? genericTypeParameterBuilders[t.GenericParameterPosition] : t) - .ToArray(); - - // proxy additional types - string returnValueProxyTypeName = null; - string[] parameterTargetToArgProxyTypeNames = new string[argTypes.Length]; - string[] parameterArgToTargetUnproxyTypeNames = new string[argTypes.Length]; - if (positionsToProxy.Count > 0) - { - foreach (int? position in positionsToProxy) - { - // we don't check for generics here, because earlier code does and generic positions won't end up here - if (position == null) // it's the return type - { - var builder = factory.ObtainBuilder(target.ReturnType, proxy.ReturnType, sourceModID, targetModID); - returnType = proxy.ReturnType; - returnValueProxyTypeName = builder.ProxyTypeName; - } - else // it's one of the parameters - { - bool isByRef = argTypes[position.Value].IsByRef; - var targetType = targetParameters[position.Value].ParameterType; - var argType = argTypes[position.Value]; - - var builder = factory.ObtainBuilder(targetType.GetNonRefType(), argType.GetNonRefType(), sourceModID, targetModID); - argTypes[position.Value] = argType; - parameterTargetToArgProxyTypeNames[position.Value] = builder.ProxyTypeName; - - if (!targetParameters[position.Value].IsOut) - { - var argToTargetBuilder = factory.ObtainBuilder(argType.GetNonRefType(), targetType.GetNonRefType(), sourceModID, targetModID); - parameterArgToTargetUnproxyTypeNames[position.Value] = argToTargetBuilder.ProxyTypeName; - } - } - } - - methodBuilder.SetReturnType(returnType); - } - - methodBuilder.SetParameters(argTypes); - for (int i = 0; i < argTypes.Length; i++) - methodBuilder.DefineParameter(i, targetParameters[i].Attributes, targetParameters[i].Name); - - // create method body - { - ILGenerator il = methodBuilder.GetILGenerator(); - LocalBuilder[] inputLocals = new LocalBuilder[argTypes.Length]; - LocalBuilder[] outputLocals = new LocalBuilder[argTypes.Length]; - - void ProxyIfNeededAndStore(LocalBuilder inputLocal, LocalBuilder outputLocal, string proxyTypeName, string unproxyTypeName) - { - if (proxyTypeName == null) - { - il.Emit(OpCodes.Ldloc, inputLocal); - il.Emit(OpCodes.Stloc, outputLocal); - return; - } - - var isNullLabel = il.DefineLabel(); - il.Emit(OpCodes.Ldloc, inputLocal); - il.Emit(OpCodes.Brfalse, isNullLabel); - - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, glueField); - if (unproxyTypeName == null) - { - il.Emit(OpCodes.Ldstr, proxyTypeName); - il.Emit(OpCodes.Ldloc, inputLocal); - il.Emit(OpCodes.Call, ObtainInstanceForProxyTypeNameMethod); - } - else - { - il.Emit(OpCodes.Ldstr, proxyTypeName); - il.Emit(OpCodes.Ldstr, unproxyTypeName); - il.Emit(OpCodes.Ldloc, inputLocal); - il.Emit(OpCodes.Call, UnproxyOrObtainInstanceForProxyTypeNameMethod); - } - il.Emit(OpCodes.Castclass, outputLocal.LocalType); - il.Emit(OpCodes.Stloc, outputLocal); - - il.MarkLabel(isNullLabel); - } - - // calling the proxied method - LocalBuilder resultInputLocal = target.ReturnType == typeof(void) ? null : il.DeclareLocal(target.ReturnType); - LocalBuilder resultOutputLocal = returnType == typeof(void) ? null : il.DeclareLocal(returnType); - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, instanceField); - for (int i = 0; i < argTypes.Length; i++) - { - if (targetParameters[i].IsOut && parameterTargetToArgProxyTypeNames[i] != null) // out parameter, proxy on the way back - { - inputLocals[i] = il.DeclareLocal(targetParameters[i].ParameterType.GetNonRefType()); - outputLocals[i] = il.DeclareLocal(argTypes[i].GetNonRefType()); - il.Emit(OpCodes.Ldloca, inputLocals[i]); - } - else if (parameterArgToTargetUnproxyTypeNames[i] != null) // normal parameter, proxy on the way in - { - inputLocals[i] = il.DeclareLocal(argTypes[i].GetNonRefType()); - outputLocals[i] = il.DeclareLocal(targetParameters[i].ParameterType.GetNonRefType()); - il.Emit(OpCodes.Ldarg, i + 1); - il.Emit(OpCodes.Stloc, inputLocals[i]); - ProxyIfNeededAndStore(inputLocals[i], outputLocals[i], parameterArgToTargetUnproxyTypeNames[i], parameterTargetToArgProxyTypeNames[i]); - il.Emit(OpCodes.Ldloc, outputLocals[i]); - } - else // normal parameter, no proxying - { - il.Emit(OpCodes.Ldarg, i + 1); - } - } - il.Emit(target.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, target); - if (target.ReturnType != typeof(void)) - il.Emit(OpCodes.Stloc, resultInputLocal); - - // proxying `out` parameters - for (int i = 0; i < argTypes.Length; i++) - { - if (parameterTargetToArgProxyTypeNames[i] == null) - continue; - if (!targetParameters[i].IsOut) - continue; - - ProxyIfNeededAndStore(inputLocals[i], outputLocals[i], parameterTargetToArgProxyTypeNames[i], null); - il.Emit(OpCodes.Ldarg, i + 1); - il.Emit(OpCodes.Ldloc, outputLocals[i]); - il.Emit(OpCodes.Stind_Ref); - } - - // proxying return value - if (target.ReturnType != typeof(void)) - ProxyIfNeededAndStore(resultInputLocal, resultOutputLocal, returnValueProxyTypeName, null); - - // return result - if (target.ReturnType != typeof(void)) - il.Emit(OpCodes.Ldloc, resultOutputLocal); - il.Emit(OpCodes.Ret); - } - } - - /// Try to get a target instance for a given proxy instance. - /// The proxy instance to look for. - /// The reference to store the found target instance in. - public bool TryUnproxy(object potentialProxyInstance, out object targetInstance) - { - foreach ((object cachedTargetInstance, object cachedProxyInstance) in this.ProxyCache) - { - if (object.ReferenceEquals(potentialProxyInstance, cachedProxyInstance)) - { - targetInstance = cachedTargetInstance; - return true; - } - } - targetInstance = null; - return false; - } - - /// The part of a method that is being matched. - private enum MethodTypeMatchingPart - { - ReturnType, Parameter - } - - /// The result of matching a target and a proxy type. - private enum MatchingTypesResult - { - False, IfProxied, True - } - } - - internal static class TypeExtensions - { - internal static Type GetNonRefType(this Type type) - { - return type.IsByRef ? type.GetElementType() : type; - } - } -} diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs deleted file mode 100644 index a6f38c3a..00000000 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Reflection.Emit; - -namespace StardewModdingAPI.Framework.Reflection -{ - /// Generates proxy classes to access mod APIs through an arbitrary interface. - internal class InterfaceProxyFactory - { - /********* - ** Fields - *********/ - /// The CLR module in which to create proxy classes. - private readonly ModuleBuilder ModuleBuilder; - - /// The generated proxy types. - private readonly IDictionary Builders = new Dictionary(); - - - /********* - ** Public methods - *********/ - /// Construct an instance. - public InterfaceProxyFactory() - { - AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run); - this.ModuleBuilder = assemblyBuilder.DefineDynamicModule("StardewModdingAPI.Proxies"); - } - - /// Create an API proxy. - /// The interface through which to access the API. - /// The API instance to access. - /// The unique ID of the mod consuming the API. - /// The unique ID of the mod providing the API. - public TInterface CreateProxy(object instance, string sourceModID, string targetModID) - where TInterface : class - { - // validate - if (instance == null) - throw new InvalidOperationException("Can't proxy access to a null API."); - - // create instance - InterfaceProxyBuilder builder = this.ObtainBuilder(instance.GetType(), typeof(TInterface), sourceModID, targetModID); - return (TInterface)builder.ObtainInstance(instance, this); - } - - internal InterfaceProxyBuilder ObtainBuilder(Type targetType, Type interfaceType, string sourceModID, string targetModID) - { - lock (this.Builders) - { - // validate - if (!interfaceType.IsInterface) - throw new InvalidOperationException("The proxy type must be an interface, not a class."); - - // get proxy type - string proxyTypeName = $"StardewModdingAPI.Proxies.From<{sourceModID}_{interfaceType.FullName}>_To<{targetModID}_{targetType.FullName}>"; - if (!this.Builders.TryGetValue(proxyTypeName, out InterfaceProxyBuilder builder)) - { - builder = new InterfaceProxyBuilder(targetType, interfaceType, proxyTypeName); - this.Builders[proxyTypeName] = builder; - try - { - builder.SetupProxyType(this, this.ModuleBuilder, sourceModID, targetModID); - } - catch - { - this.Builders.Remove(proxyTypeName); - throw; - } - } - return builder; - } - } - - internal InterfaceProxyBuilder GetBuilderByProxyTypeName(string proxyTypeName) - { - lock (this.Builders) - { - return this.Builders.TryGetValue(proxyTypeName, out InterfaceProxyBuilder builder) ? builder : null; - } - } - } -} diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs deleted file mode 100644 index 38569efa..00000000 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyGlue.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace StardewModdingAPI.Framework.Reflection -{ - /// Provides an interface for proxied types to create other proxied types. - public sealed class InterfaceProxyGlue - { - private readonly InterfaceProxyFactory Factory; - - internal InterfaceProxyGlue(InterfaceProxyFactory factory) - { - this.Factory = factory; - } - - /// Get an existing or create a new proxied instance by its type name. - /// The full name of the proxy type. - /// The target instance to proxy. - public object ObtainInstanceForProxyTypeName(string proxyTypeName, object toProxy) - { - var builder = this.Factory.GetBuilderByProxyTypeName(proxyTypeName); - return builder.ObtainInstance(toProxy, this.Factory); - } - - /// Try to unproxy, or get an existing, or create a new proxied instance by its type name. - /// The full name of the proxy type. - /// The full name of the reverse proxy type. - /// The target instance to proxy. - public object UnproxyOrObtainInstanceForProxyTypeName(string proxyTypeName, string unproxyTypeName, object toProxy) - { - var unproxyBuilder = this.Factory.GetBuilderByProxyTypeName(unproxyTypeName); - if (unproxyBuilder.TryUnproxy(toProxy, out object targetInstance)) - return targetInstance; - return this.ObtainInstanceForProxyTypeName(proxyTypeName, toProxy); - } - } -} diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 55a7f083..67c13ad0 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -47,6 +47,8 @@ using xTile.Display; using MiniMonoModHotfix = MonoMod.Utils.MiniMonoModHotfix; using PathUtilities = StardewModdingAPI.Toolkit.Utilities.PathUtilities; using SObject = StardewValley.Object; +using Nanoray.Pintail; +using System.Reflection.Emit; namespace StardewModdingAPI.Framework { @@ -1477,12 +1479,16 @@ namespace StardewModdingAPI.Framework { // init HashSet suppressUpdateChecks = new HashSet(this.Settings.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase); - InterfaceProxyFactory proxyFactory = new InterfaceProxyFactory(); + AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run); + ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("StardewModdingAPI.Proxies"); + IProxyManager proxyManager = new DefaultProxyManager(moduleBuilder, new DefaultProxyManagerConfiguration( + proxyObjectInterfaceMarking: ProxyObjectInterfaceMarking.Disabled + )); // load mods foreach (IModMetadata mod in mods) { - if (!this.TryLoadMod(mod, mods, modAssemblyLoader, proxyFactory, jsonHelper, contentCore, modDatabase, suppressUpdateChecks, out ModFailReason? failReason, out string errorPhrase, out string errorDetails)) + if (!this.TryLoadMod(mod, mods, modAssemblyLoader, proxyManager, jsonHelper, contentCore, modDatabase, suppressUpdateChecks, out ModFailReason? failReason, out string errorPhrase, out string errorDetails)) { failReason ??= ModFailReason.LoadFailed; mod.SetStatus(ModMetadataStatus.Failed, failReason.Value, errorPhrase, errorDetails); @@ -1585,7 +1591,7 @@ namespace StardewModdingAPI.Framework /// The mod to load. /// The mods being loaded. /// Preprocesses and loads mod assemblies. - /// Generates proxy classes to access mod APIs through an arbitrary interface. + /// Generates proxy classes to access mod APIs through an arbitrary interface. /// The JSON helper with which to read mods' JSON files. /// The content manager to use for mod content. /// Handles access to SMAPI's internal mod metadata list. @@ -1594,7 +1600,7 @@ namespace StardewModdingAPI.Framework /// The user-facing reason phrase explaining why the mod couldn't be loaded (if applicable). /// More detailed details about the error intended for developers (if any). /// Returns whether the mod was successfully loaded. - private bool TryLoadMod(IModMetadata mod, IModMetadata[] mods, AssemblyLoader assemblyLoader, InterfaceProxyFactory proxyFactory, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase, HashSet suppressUpdateChecks, out ModFailReason? failReason, out string errorReasonPhrase, out string errorDetails) + private bool TryLoadMod(IModMetadata mod, IModMetadata[] mods, AssemblyLoader assemblyLoader, IProxyManager proxyManager, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase, HashSet suppressUpdateChecks, out ModFailReason? failReason, out string errorReasonPhrase, out string errorDetails) { errorDetails = null; @@ -1737,7 +1743,7 @@ namespace StardewModdingAPI.Framework IContentPackHelper contentPackHelper = new ContentPackHelper(manifest.UniqueID, new Lazy(GetContentPacks), CreateFakeContentPack); IDataHelper dataHelper = new DataHelper(manifest.UniqueID, mod.DirectoryPath, jsonHelper); IReflectionHelper reflectionHelper = new ReflectionHelper(manifest.UniqueID, mod.DisplayName, this.Reflection); - IModRegistry modRegistryHelper = new ModRegistryHelper(manifest.UniqueID, this.ModRegistry, proxyFactory, monitor); + IModRegistry modRegistryHelper = new ModRegistryHelper(manifest.UniqueID, this.ModRegistry, proxyManager, monitor); IMultiplayerHelper multiplayerHelper = new MultiplayerHelper(manifest.UniqueID, this.Multiplayer); modHelper = new ModHelper(manifest.UniqueID, mod.DirectoryPath, () => this.GetCurrentGameInstance().Input, events, contentHelper, contentPackHelper, commandHelper, dataHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper); diff --git a/src/SMAPI/IModRegistry.cs b/src/SMAPI/IModRegistry.cs index 9b99e459..10b3121e 100644 --- a/src/SMAPI/IModRegistry.cs +++ b/src/SMAPI/IModRegistry.cs @@ -25,12 +25,5 @@ namespace StardewModdingAPI /// The interface which matches the properties and methods you intend to access. /// The mod's unique ID. TInterface GetApi(string uniqueID) where TInterface : class; - - /// Try to proxy (or unproxy back) the given object to a given interface provided by a mod. - /// The interface type to proxy (or unproxy) to. - /// The mod's unique ID. - /// The object to try to proxy (or unproxy back). - /// The reference to store the proxied (or unproxied) object back. - bool TryProxy(string uniqueID, object toProxy, out TInterface proxy) where TInterface : class; } } diff --git a/src/SMAPI/SMAPI.csproj b/src/SMAPI/SMAPI.csproj index f07ede87..1ea5ea78 100644 --- a/src/SMAPI/SMAPI.csproj +++ b/src/SMAPI/SMAPI.csproj @@ -28,6 +28,7 @@ + -- cgit From a2190df08cc3f1b4a8dcb394056d65921d10702e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 18 Feb 2022 15:39:49 -0500 Subject: add AssetName to encapsulate asset name handling (#766) --- docs/release-notes.md | 2 + src/SMAPI.Tests/Core/AssetNameTests.cs | 295 +++++++++++++++++++++ src/SMAPI/Framework/Content/AssetData.cs | 4 +- .../Framework/Content/AssetDataForDictionary.cs | 4 +- src/SMAPI/Framework/Content/AssetDataForImage.cs | 4 +- src/SMAPI/Framework/Content/AssetDataForMap.cs | 4 +- src/SMAPI/Framework/Content/AssetDataForObject.cs | 12 +- src/SMAPI/Framework/Content/AssetInfo.cs | 16 +- .../Framework/Content/AssetInterceptorChange.cs | 4 +- src/SMAPI/Framework/Content/AssetName.cs | 173 ++++++++++++ src/SMAPI/Framework/ContentCoordinator.cs | 45 ++-- .../ContentManagers/BaseContentManager.cs | 51 +--- .../ContentManagers/GameContentManager.cs | 94 +++---- .../Framework/ContentManagers/IContentManager.cs | 3 - .../Framework/ContentManagers/ModContentManager.cs | 26 +- src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 5 +- src/SMAPI/IAssetInfo.cs | 5 + src/SMAPI/IAssetName.cs | 44 +++ src/SMAPI/Metadata/CoreAssetPropagator.cs | 261 ++++++++---------- 19 files changed, 736 insertions(+), 316 deletions(-) create mode 100644 src/SMAPI.Tests/Core/AssetNameTests.cs create mode 100644 src/SMAPI/Framework/Content/AssetName.cs create mode 100644 src/SMAPI/IAssetName.cs (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/docs/release-notes.md b/docs/release-notes.md index d549b99c..b84f8a06 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -6,6 +6,8 @@ * Improved translations. Thanks to ChulkyBow (updated Ukrainian)! * For mod authors: + * Added `IAssetName` field to the asset info received by `IAssetEditor` and `IAssetLoader` methods. + _This provides utility methods for working with asset names, parsed locales, etc. The `asset.AssetNameEquals` method is now deprecated in favor of `asset.Name.IsEquivalentTo`_. * The `SDate` constructor is no longer case-sensitive for season names. * Fixed issue where suppressing `[Left|Right]Thumbstick[Down|Left]` keys would suppress the opposite direction instead. * Fixed support for using locale codes from custom languages in asset names (e.g. `Data/Achievements.eo-EU`). diff --git a/src/SMAPI.Tests/Core/AssetNameTests.cs b/src/SMAPI.Tests/Core/AssetNameTests.cs new file mode 100644 index 00000000..8785aab8 --- /dev/null +++ b/src/SMAPI.Tests/Core/AssetNameTests.cs @@ -0,0 +1,295 @@ +using System; +using System.Collections.Generic; +using FluentAssertions; +using NUnit.Framework; +using StardewModdingAPI; +using StardewModdingAPI.Framework.Content; +using StardewModdingAPI.Toolkit.Utilities; +using StardewValley; + +namespace SMAPI.Tests.Core +{ + /// Unit tests for . + [TestFixture] + internal class AssetNameTests + { + /********* + ** Unit tests + *********/ + /**** + ** Constructor + ****/ + [Test(Description = $"Assert that the {nameof(AssetName)} constructor creates an instance with the expected values.")] + [TestCase("SimpleName", "SimpleName", null, null)] + [TestCase("Data/Achievements", "Data/Achievements", null, null)] + [TestCase("Characters/Dialogue/Abigail", "Characters/Dialogue/Abigail", null, null)] + [TestCase("Characters/Dialogue/Abigail.fr-FR", "Characters/Dialogue/Abigail", "fr-FR", LocalizedContentManager.LanguageCode.fr)] + [TestCase("Characters/Dialogue\\Abigail.fr-FR", "Characters/Dialogue/Abigail.fr-FR", null, null)] + [TestCase("Characters/Dialogue/Abigail.fr-FR", "Characters/Dialogue/Abigail", "fr-FR", LocalizedContentManager.LanguageCode.fr)] + public void Constructor_Valid(string name, string expectedBaseName, string expectedLocale, LocalizedContentManager.LanguageCode? expectedLanguageCode) + { + // arrange + name = PathUtilities.NormalizeAssetName(name); + + // act + string calledWithLocale = null; + IAssetName assetName = AssetName.Parse(name, parseLocale: locale => expectedLanguageCode); + + // assert + assetName.Name.Should() + .NotBeNull() + .And.Be(name.Replace("\\", "/")); + assetName.BaseName.Should() + .NotBeNull() + .And.Be(expectedBaseName); + assetName.LocaleCode.Should() + .Be(expectedLocale); + assetName.LanguageCode.Should() + .Be(expectedLanguageCode); + } + + [Test(Description = $"Assert that the {nameof(AssetName)} constructor throws an exception if the value is invalid.")] + [TestCase(null)] + [TestCase("")] + [TestCase(" ")] + [TestCase("\t")] + [TestCase(" \t ")] + public void Constructor_NullOrWhitespace(string name) + { + // act + ArgumentException exception = Assert.Throws(() => _ = AssetName.Parse(name, null)); + + // assert + exception!.ParamName.Should().Be("rawName"); + exception.Message.Should().Be("The asset name can't be null or empty. (Parameter 'rawName')"); + } + + + /**** + ** IsEquivalentTo + ****/ + [Test(Description = $"Assert that {nameof(AssetName.IsEquivalentTo)} compares names as expected when the locale is included.")] + + // exact match (ignore case) + [TestCase("Data/Achievements", "Data/Achievements", ExpectedResult = true)] + [TestCase("DATA/achievements", "data/ACHIEVEMENTS", ExpectedResult = true)] + + // exact match (ignore formatting) + [TestCase("Data/Achievements", "Data\\Achievements", ExpectedResult = true)] + [TestCase("DATA\\achievements", "data/ACHIEVEMENTS", ExpectedResult = true)] + [TestCase("DATA\\\\achievements", "data////ACHIEVEMENTS", ExpectedResult = true)] + + // whitespace-sensitive + [TestCase("Data/Achievements", " Data/Achievements ", ExpectedResult = false)] + [TestCase(" Data/Achievements ", "Data/Achievements", ExpectedResult = false)] + + // other is null or whitespace + [TestCase("Data/Achievements", null, ExpectedResult = false)] + [TestCase("Data/Achievements", "", ExpectedResult = false)] + [TestCase("Data/Achievements", " ", ExpectedResult = false)] + + // with locale codes + [TestCase("Data/Achievements", "Data/Achievements.fr-FR", ExpectedResult = false)] + [TestCase("Data/Achievements.fr-FR", "Data/Achievements", ExpectedResult = false)] + [TestCase("Data/Achievements.fr-FR", "Data/Achievements.fr-FR", ExpectedResult = true)] + public bool IsEquivalentTo_Name(string mainAssetName, string otherAssetName) + { + // arrange + mainAssetName = PathUtilities.NormalizeAssetName(mainAssetName); + + // act + AssetName name = AssetName.Parse(mainAssetName, _ => LocalizedContentManager.LanguageCode.fr); + + // assert + return name.IsEquivalentTo(otherAssetName); + } + + [Test(Description = $"Assert that {nameof(AssetName.IsEquivalentTo)} compares names as expected when the locale is excluded.")] + + // a few samples from previous test to make sure + [TestCase("Data/Achievements", "Data/Achievements", ExpectedResult = true)] + [TestCase("DATA/achievements", "data/ACHIEVEMENTS", ExpectedResult = true)] + [TestCase("DATA\\\\achievements", "data////ACHIEVEMENTS", ExpectedResult = true)] + [TestCase(" Data/Achievements ", "Data/Achievements", ExpectedResult = false)] + [TestCase("Data/Achievements", " ", ExpectedResult = false)] + + // with locale codes + [TestCase("Data/Achievements", "Data/Achievements.fr-FR", ExpectedResult = false)] + [TestCase("Data/Achievements.fr-FR", "Data/Achievements", ExpectedResult = true)] + [TestCase("Data/Achievements.fr-FR", "Data/Achievements.fr-FR", ExpectedResult = false)] + public bool IsEquivalentTo_BaseName(string mainAssetName, string otherAssetName) + { + // arrange + mainAssetName = PathUtilities.NormalizeAssetName(mainAssetName); + + // act + AssetName name = AssetName.Parse(mainAssetName, _ => LocalizedContentManager.LanguageCode.fr); + + // assert + return name.IsEquivalentTo(otherAssetName, useBaseName: true); + } + + + /**** + ** StartsWith + ****/ + [Test(Description = $"Assert that {nameof(AssetName.StartsWith)} compares names as expected for inputs that aren't affected by the input options.")] + + // exact match (ignore case and formatting) + [TestCase("Data/Achievements", "Data/Achievements", ExpectedResult = true)] + [TestCase("DATA/achievements", "data/ACHIEVEMENTS", ExpectedResult = true)] + [TestCase("Data/Achievements", "Data\\Achievements", ExpectedResult = true)] + [TestCase("DATA\\achievements", "data/ACHIEVEMENTS", ExpectedResult = true)] + [TestCase("DATA\\\\achievements", "data////ACHIEVEMENTS", ExpectedResult = true)] + + // leading-whitespace-sensitive + [TestCase("Data/Achievements", " Data/Achievements", ExpectedResult = false)] + [TestCase(" Data/Achievements ", "Data/Achievements", ExpectedResult = false)] + + // invalid prefixes + [TestCase("Data/Achievements", null, ExpectedResult = false)] + [TestCase("Data/Achievements", " ", ExpectedResult = false)] + + // with locale codes + [TestCase("Data/Achievements.fr-FR", "Data/Achievements", ExpectedResult = true)] + public bool StartsWith_SimpleCases(string mainAssetName, string prefix) + { + // arrange + mainAssetName = PathUtilities.NormalizeAssetName(mainAssetName); + + // act + AssetName name = AssetName.Parse(mainAssetName, _ => null); + + // assert value is the same for any combination of options + bool result = name.StartsWith(prefix, true, true); + foreach (bool allowPartialWord in new[] { true, false }) + { + foreach (bool allowSubfolder in new[] { true, true }) + { + if (allowPartialWord && allowSubfolder) + continue; + + name.StartsWith(prefix, allowPartialWord, allowSubfolder) + .Should().Be(result, $"the value returned for options ({nameof(allowPartialWord)}: {allowPartialWord}, {nameof(allowSubfolder)}: {allowSubfolder}) should match the base case"); + } + } + + // assert value + return result; + } + + [Test(Description = $"Assert that {nameof(AssetName.StartsWith)} compares names as expected for the 'allowPartialWord' option.")] + [TestCase("Data/AchievementsToIgnore", "Data/Achievements", true, ExpectedResult = true)] + [TestCase("Data/AchievementsToIgnore", "Data/Achievements", false, ExpectedResult = false)] + [TestCase("Data/Achievements X", "Data/Achievements", true, ExpectedResult = true)] + [TestCase("Data/Achievements X", "Data/Achievements", false, ExpectedResult = true)] + [TestCase("Data/Achievements.X", "Data/Achievements", true, ExpectedResult = true)] + [TestCase("Data/Achievements.X", "Data/Achievements", false, ExpectedResult = true)] + + // with locale codes + [TestCase("Data/Achievements.fr-FR", "Data/Achievements", true, ExpectedResult = true)] + [TestCase("Data/Achievements.fr-FR", "Data/Achievements", false, ExpectedResult = true)] + public bool StartsWith_PartialWord(string mainAssetName, string prefix, bool allowPartialWord) + { + // arrange + mainAssetName = PathUtilities.NormalizeAssetName(mainAssetName); + + // act + AssetName name = AssetName.Parse(mainAssetName, _ => null); + + // assert value is the same for any combination of options + bool result = name.StartsWith(prefix, allowPartialWord: allowPartialWord, allowSubfolder: true); + name.StartsWith(prefix, allowPartialWord, allowSubfolder: false) + .Should().Be(result, "specifying allowSubfolder should have no effect for these inputs"); + + // assert value + return result; + } + + [Test(Description = $"Assert that {nameof(AssetName.StartsWith)} compares names as expected for the 'allowSubfolder' option.")] + + // simple cases + [TestCase("Data/Achievements/Path", "Data/Achievements", true, ExpectedResult = true)] + [TestCase("Data/Achievements/Path", "Data/Achievements", false, ExpectedResult = false)] + [TestCase("Data/Achievements/Path", "Data\\Achievements", true, ExpectedResult = true)] + [TestCase("Data/Achievements/Path", "Data\\Achievements", false, ExpectedResult = false)] + + // trailing slash + [TestCase("Data/Achievements/Path", "Data/", true, ExpectedResult = true)] + [TestCase("Data/Achievements/Path", "Data/", false, ExpectedResult = false)] + + // normalize slash style + [TestCase("Data/Achievements/Path", "Data\\", true, ExpectedResult = true)] + [TestCase("Data/Achievements/Path", "Data\\", false, ExpectedResult = false)] + [TestCase("Data/Achievements/Path", "Data/\\/", true, ExpectedResult = true)] + [TestCase("Data/Achievements/Path", "Data/\\/", false, ExpectedResult = false)] + + // with locale code + [TestCase("Data/Achievements/Path.fr-FR", "Data/Achievements", true, ExpectedResult = true)] + [TestCase("Data/Achievements/Path.fr-FR", "Data/Achievements", false, ExpectedResult = false)] + public bool StartsWith_Subfolder(string mainAssetName, string otherAssetName, bool allowSubfolder) + { + // arrange + mainAssetName = PathUtilities.NormalizeAssetName(mainAssetName); + + // act + AssetName name = AssetName.Parse(mainAssetName, _ => null); + + // assert value is the same for any combination of options + bool result = name.StartsWith(otherAssetName, allowPartialWord: true, allowSubfolder: allowSubfolder); + name.StartsWith(otherAssetName, allowPartialWord: false, allowSubfolder: allowSubfolder) + .Should().Be(result, "specifying allowPartialWord should have no effect for these inputs"); + + // assert value + return result; + } + + + /**** + ** GetHashCode + ****/ + [Test(Description = $"Assert that {nameof(AssetName.GetHashCode)} generates the same hash code for two asset names which differ only by capitalization.")] + public void GetHashCode_IsCaseInsensitive() + { + // arrange + string left = "data/ACHIEVEMENTS"; + string right = "DATA/achievements"; + + // act + int leftHash = AssetName.Parse(left, _ => null).GetHashCode(); + int rightHash = AssetName.Parse(right, _ => null).GetHashCode(); + + // assert + leftHash.Should().Be(rightHash, "two asset names which differ only by capitalization should produce the same hash code"); + } + + [Test(Description = $"Assert that {nameof(AssetName.GetHashCode)} generates few hash code collisions for an arbitrary set of asset names.")] + public void GetHashCode_HasFewCollisions() + { + // generate list of names + List names = new(); + { + Random random = new(); + string characters = "abcdefghijklmnopqrstuvwxyz1234567890/"; + + while (names.Count < 1000) + { + char[] name = new char[random.Next(5, 20)]; + for (int i = 0; i < name.Length; i++) + name[i] = characters[random.Next(0, characters.Length)]; + + names.Add(new string(name)); + } + } + + // get distinct hash codes + HashSet hashCodes = new(); + foreach (string name in names) + hashCodes.Add(AssetName.Parse(name, _ => null).GetHashCode()); + + // assert a collision frequency under 0.1% + float collisionFrequency = 1 - (hashCodes.Count / (names.Count * 1f)); + collisionFrequency.Should().BeLessOrEqualTo(0.001f, "hash codes should be relatively distinct with a collision rate under 0.1% for a small sample set"); + } + } +} diff --git a/src/SMAPI/Framework/Content/AssetData.cs b/src/SMAPI/Framework/Content/AssetData.cs index 5c90d83b..05be8a3b 100644 --- a/src/SMAPI/Framework/Content/AssetData.cs +++ b/src/SMAPI/Framework/Content/AssetData.cs @@ -25,11 +25,11 @@ namespace StardewModdingAPI.Framework.Content *********/ /// Construct an instance. /// The content's locale code, if the content is localized. - /// The normalized asset name being read. + /// The asset name being read. /// The content data being read. /// Normalizes an asset key to match the cache key. /// A callback to invoke when the data is replaced (if any). - public AssetData(string locale, string assetName, TValue data, Func getNormalizedPath, Action onDataReplaced) + public AssetData(string locale, IAssetName assetName, TValue data, Func getNormalizedPath, Action onDataReplaced) : base(locale, assetName, data.GetType(), getNormalizedPath) { this.Data = data; diff --git a/src/SMAPI/Framework/Content/AssetDataForDictionary.cs b/src/SMAPI/Framework/Content/AssetDataForDictionary.cs index 26cbff5a..735b651c 100644 --- a/src/SMAPI/Framework/Content/AssetDataForDictionary.cs +++ b/src/SMAPI/Framework/Content/AssetDataForDictionary.cs @@ -11,11 +11,11 @@ namespace StardewModdingAPI.Framework.Content *********/ /// Construct an instance. /// The content's locale code, if the content is localized. - /// The normalized asset name being read. + /// The asset name being read. /// The content data being read. /// Normalizes an asset key to match the cache key. /// A callback to invoke when the data is replaced (if any). - public AssetDataForDictionary(string locale, string assetName, IDictionary data, Func getNormalizedPath, Action> onDataReplaced) + public AssetDataForDictionary(string locale, IAssetName assetName, IDictionary data, Func getNormalizedPath, Action> onDataReplaced) : base(locale, assetName, data, getNormalizedPath, onDataReplaced) { } } } diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index c75514bc..b0f1b5c7 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -21,11 +21,11 @@ namespace StardewModdingAPI.Framework.Content *********/ /// Construct an instance. /// The content's locale code, if the content is localized. - /// The normalized asset name being read. + /// The asset name being read. /// The content data being read. /// Normalizes an asset key to match the cache key. /// A callback to invoke when the data is replaced (if any). - public AssetDataForImage(string locale, string assetName, Texture2D data, Func getNormalizedPath, Action onDataReplaced) + public AssetDataForImage(string locale, IAssetName assetName, Texture2D data, Func getNormalizedPath, Action onDataReplaced) : base(locale, assetName, data, getNormalizedPath, onDataReplaced) { } /// diff --git a/src/SMAPI/Framework/Content/AssetDataForMap.cs b/src/SMAPI/Framework/Content/AssetDataForMap.cs index 0a5fa7e7..26e4986e 100644 --- a/src/SMAPI/Framework/Content/AssetDataForMap.cs +++ b/src/SMAPI/Framework/Content/AssetDataForMap.cs @@ -18,11 +18,11 @@ namespace StardewModdingAPI.Framework.Content *********/ /// Construct an instance. /// The content's locale code, if the content is localized. - /// The normalized asset name being read. + /// The asset name being read. /// The content data being read. /// Normalizes an asset key to match the cache key. /// A callback to invoke when the data is replaced (if any). - public AssetDataForMap(string locale, string assetName, Map data, Func getNormalizedPath, Action onDataReplaced) + public AssetDataForMap(string locale, IAssetName assetName, Map data, Func getNormalizedPath, Action onDataReplaced) : base(locale, assetName, data, getNormalizedPath, onDataReplaced) { } /// diff --git a/src/SMAPI/Framework/Content/AssetDataForObject.cs b/src/SMAPI/Framework/Content/AssetDataForObject.cs index b7e8dfeb..d91873ae 100644 --- a/src/SMAPI/Framework/Content/AssetDataForObject.cs +++ b/src/SMAPI/Framework/Content/AssetDataForObject.cs @@ -13,10 +13,10 @@ namespace StardewModdingAPI.Framework.Content *********/ /// Construct an instance. /// The content's locale code, if the content is localized. - /// The normalized asset name being read. + /// The asset name being read. /// The content data being read. /// Normalizes an asset key to match the cache key. - public AssetDataForObject(string locale, string assetName, object data, Func getNormalizedPath) + public AssetDataForObject(string locale, IAssetName assetName, object data, Func getNormalizedPath) : base(locale, assetName, data, getNormalizedPath, onDataReplaced: null) { } /// Construct an instance. @@ -24,24 +24,24 @@ namespace StardewModdingAPI.Framework.Content /// The content data being read. /// Normalizes an asset key to match the cache key. public AssetDataForObject(IAssetInfo info, object data, Func getNormalizedPath) - : this(info.Locale, info.AssetName, data, getNormalizedPath) { } + : this(info.Locale, info.Name, data, getNormalizedPath) { } /// public IAssetDataForDictionary AsDictionary() { - return new AssetDataForDictionary(this.Locale, this.AssetName, this.GetData>(), this.GetNormalizedPath, this.ReplaceWith); + return new AssetDataForDictionary(this.Locale, this.Name, this.GetData>(), this.GetNormalizedPath, this.ReplaceWith); } /// public IAssetDataForImage AsImage() { - return new AssetDataForImage(this.Locale, this.AssetName, this.GetData(), this.GetNormalizedPath, this.ReplaceWith); + return new AssetDataForImage(this.Locale, this.Name, this.GetData(), this.GetNormalizedPath, this.ReplaceWith); } /// public IAssetDataForMap AsMap() { - return new AssetDataForMap(this.Locale, this.AssetName, this.GetData(), this.GetNormalizedPath, this.ReplaceWith); + return new AssetDataForMap(this.Locale, this.Name, this.GetData(), this.GetNormalizedPath, this.ReplaceWith); } /// diff --git a/src/SMAPI/Framework/Content/AssetInfo.cs b/src/SMAPI/Framework/Content/AssetInfo.cs index d8106439..6a5b4f31 100644 --- a/src/SMAPI/Framework/Content/AssetInfo.cs +++ b/src/SMAPI/Framework/Content/AssetInfo.cs @@ -20,7 +20,11 @@ namespace StardewModdingAPI.Framework.Content public string Locale { get; } /// - public string AssetName { get; } + public IAssetName Name { get; } + + /// + [Obsolete($"Use {nameof(Name)} instead.")] + public string AssetName => this.Name.Name; /// public Type DataType { get; } @@ -31,22 +35,22 @@ namespace StardewModdingAPI.Framework.Content *********/ /// Construct an instance. /// The content's locale code, if the content is localized. - /// The normalized asset name being read. + /// The asset name being read. /// The content type being read. /// Normalizes an asset key to match the cache key. - public AssetInfo(string locale, string assetName, Type type, Func getNormalizedPath) + public AssetInfo(string locale, IAssetName assetName, Type type, Func getNormalizedPath) { this.Locale = locale; - this.AssetName = assetName; + this.Name = assetName; this.DataType = type; this.GetNormalizedPath = getNormalizedPath; } /// + [Obsolete($"Use {nameof(Name)}.{nameof(IAssetName.IsEquivalentTo)} instead.")] public bool AssetNameEquals(string path) { - path = this.GetNormalizedPath(path); - return this.AssetName.Equals(path, StringComparison.OrdinalIgnoreCase); + return this.Name.IsEquivalentTo(path); } diff --git a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs index 10488b84..981eed40 100644 --- a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs +++ b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs @@ -70,7 +70,7 @@ namespace StardewModdingAPI.Framework.Content } catch (Exception ex) { - this.Mod.LogAsMod($"Mod failed when checking whether it could edit asset '{asset.AssetName}'. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); + this.Mod.LogAsMod($"Mod failed when checking whether it could edit asset '{asset.Name}'. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); } } @@ -84,7 +84,7 @@ namespace StardewModdingAPI.Framework.Content } catch (Exception ex) { - this.Mod.LogAsMod($"Mod failed when checking whether it could load asset '{asset.AssetName}'. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); + this.Mod.LogAsMod($"Mod failed when checking whether it could load asset '{asset.Name}'. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); } } diff --git a/src/SMAPI/Framework/Content/AssetName.cs b/src/SMAPI/Framework/Content/AssetName.cs new file mode 100644 index 00000000..992647f8 --- /dev/null +++ b/src/SMAPI/Framework/Content/AssetName.cs @@ -0,0 +1,173 @@ +using System; +using StardewModdingAPI.Toolkit.Utilities; +using StardewValley; + +namespace StardewModdingAPI.Framework.Content +{ + /// An asset name that can be loaded through the content pipeline. + internal class AssetName : IAssetName + { + /********* + ** Fields + *********/ + /// A lowercase version of used for consistent hash codes and equality checks. + private readonly string ComparableName; + + + /********* + ** Accessors + *********/ + /// + public string Name { get; } + + /// + public string BaseName { get; } + + /// + public string LocaleCode { get; } + + /// + public LocalizedContentManager.LanguageCode? LanguageCode { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The base asset name without the locale code. + /// The locale code specified in the , if it's a valid code recognized by the game content. + /// The language code matching the , if applicable. + public AssetName(string baseName, string localeCode, LocalizedContentManager.LanguageCode? languageCode) + { + // validate + if (string.IsNullOrWhiteSpace(baseName)) + throw new ArgumentException("The asset name can't be null or empty.", nameof(baseName)); + if (string.IsNullOrWhiteSpace(localeCode)) + localeCode = null; + + // set base values + this.BaseName = PathUtilities.NormalizeAssetName(baseName); + this.LocaleCode = localeCode; + this.LanguageCode = languageCode; + + // set derived values + this.Name = localeCode != null + ? string.Concat(this.BaseName, '.', this.LocaleCode) + : this.BaseName; + this.ComparableName = this.Name.ToLowerInvariant(); + } + + /// Parse a raw asset name into an instance. + /// The raw asset name to parse. + /// Get the language code for a given locale, if it's valid. + /// The is null or empty. + public static AssetName Parse(string rawName, Func parseLocale) + { + if (string.IsNullOrWhiteSpace(rawName)) + throw new ArgumentException("The asset name can't be null or empty.", nameof(rawName)); + + string baseName = rawName; + string localeCode = null; + LocalizedContentManager.LanguageCode? languageCode = null; + + int lastPeriodIndex = rawName.LastIndexOf('.'); + if (lastPeriodIndex > 0 && rawName.Length > lastPeriodIndex + 1) + { + string possibleLocaleCode = rawName[(lastPeriodIndex + 1)..]; + LocalizedContentManager.LanguageCode? possibleLanguageCode = parseLocale(possibleLocaleCode); + + if (possibleLanguageCode != null) + { + baseName = rawName[..lastPeriodIndex]; + localeCode = possibleLocaleCode; + languageCode = possibleLanguageCode; + } + } + + return new AssetName(baseName, localeCode, languageCode); + } + + /// + public bool IsEquivalentTo(string assetName, bool useBaseName = false) + { + // empty asset key is never equivalent + if (string.IsNullOrWhiteSpace(assetName)) + return false; + + assetName = PathUtilities.NormalizeAssetName(assetName); + + string compareTo = useBaseName ? this.BaseName : this.Name; + return compareTo.Equals(assetName, StringComparison.OrdinalIgnoreCase); + } + + /// + public bool StartsWith(string prefix, bool allowPartialWord = true, bool allowSubfolder = true) + { + // asset keys never start with null + if (prefix is null) + return false; + + // asset keys can't have a leading slash, but NormalizeAssetName will trim them + { + string trimmed = prefix.TrimStart(); + if (trimmed.StartsWith('/') || trimmed.StartsWith('\\')) + return false; + } + + // normalize prefix + { + string normalized = PathUtilities.NormalizeAssetName(prefix); + + string trimmed = prefix.TrimEnd(); + if (trimmed.EndsWith('/') || trimmed.EndsWith('\\')) + normalized += PathUtilities.PreferredAssetSeparator; + + prefix = normalized; + } + + // compare + return + this.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase) + && ( + allowPartialWord + || this.Name.Length == prefix.Length + || !char.IsLetterOrDigit(prefix[^1]) // last character in suffix is word separator + || !char.IsLetterOrDigit(this.Name[prefix.Length]) // or first character after it is + ) + && ( + allowSubfolder + || this.Name.Length == prefix.Length + || !this.Name[prefix.Length..].Contains(PathUtilities.PreferredAssetSeparator) + ); + } + + + public bool IsDirectlyUnderPath(string assetFolder) + { + return this.StartsWith(assetFolder + "/", allowPartialWord: false, allowSubfolder: false); + } + + /// + public bool Equals(IAssetName other) + { + return other switch + { + null => false, + AssetName otherImpl => this.ComparableName == otherImpl.ComparableName, + _ => StringComparer.OrdinalIgnoreCase.Equals(this.Name, other.Name) + }; + } + + /// + public override int GetHashCode() + { + return this.ComparableName.GetHashCode(); + } + + /// + public override string ToString() + { + return this.Name; + } + } +} diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 61cefd12..97a37b3f 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -248,6 +248,16 @@ namespace StardewModdingAPI.Framework this.InvalidateCache((contentManager, key, type) => contentManager is GameContentManager); } + /// Parse a raw asset name. + /// The raw asset name to parse. + /// The is null or empty. + public AssetName ParseAssetName(string rawName) + { + return !string.IsNullOrWhiteSpace(rawName) + ? AssetName.Parse(rawName, parseLocale: locale => this.LocaleCodes.Value.TryGetValue(locale, out LocalizedContentManager.LanguageCode langCode) ? langCode : null) + : throw new ArgumentException("The asset name can't be null or empty.", nameof(rawName)); + } + /// Get whether this asset is mapped to a mod folder. /// The asset key. public bool IsManagedAssetKey(string key) @@ -306,11 +316,12 @@ namespace StardewModdingAPI.Framework /// Matches the asset keys to invalidate. /// Whether to dispose invalidated assets. This should only be true when they're being invalidated as part of a dispose, to avoid crashing the game. /// Returns the invalidated asset keys. - public IEnumerable InvalidateCache(Func predicate, bool dispose = false) + public IEnumerable InvalidateCache(Func predicate, bool dispose = false) { string locale = this.GetLocale(); - return this.InvalidateCache((contentManager, assetName, type) => + return this.InvalidateCache((contentManager, rawName, type) => { + IAssetName assetName = this.ParseAssetName(rawName); IAssetInfo info = new AssetInfo(locale, assetName, type, this.MainContentManager.AssertAndNormalizeAssetName); return predicate(info); }, dispose); @@ -320,10 +331,10 @@ namespace StardewModdingAPI.Framework /// Matches the asset keys to invalidate. /// Whether to dispose invalidated assets. This should only be true when they're being invalidated as part of a dispose, to avoid crashing the game. /// Returns the invalidated asset names. - public IEnumerable InvalidateCache(Func predicate, bool dispose = false) + public IEnumerable InvalidateCache(Func predicate, bool dispose = false) { // invalidate cache & track removed assets - IDictionary removedAssets = new Dictionary(StringComparer.OrdinalIgnoreCase); + IDictionary removedAssets = new Dictionary(); this.ContentManagerLock.InReadLock(() => { // cached assets @@ -331,8 +342,9 @@ namespace StardewModdingAPI.Framework { foreach (var entry in contentManager.InvalidateCache((key, type) => predicate(contentManager, key, type), dispose)) { - if (!removedAssets.ContainsKey(entry.Key)) - removedAssets[entry.Key] = entry.Value.GetType(); + AssetName assetName = this.ParseAssetName(entry.Key); + if (!removedAssets.ContainsKey(assetName)) + removedAssets[assetName] = entry.Value.GetType(); } } @@ -346,8 +358,8 @@ namespace StardewModdingAPI.Framework continue; // get map path - string mapPath = this.MainContentManager.AssertAndNormalizeAssetName(location.mapPath.Value); - if (!removedAssets.ContainsKey(mapPath) && predicate(this.MainContentManager, mapPath, typeof(Map))) + AssetName mapPath = this.ParseAssetName(this.MainContentManager.AssertAndNormalizeAssetName(location.mapPath.Value)); + if (!removedAssets.ContainsKey(mapPath) && predicate(this.MainContentManager, mapPath.Name, typeof(Map))) removedAssets[mapPath] = typeof(Map); } } @@ -360,17 +372,17 @@ namespace StardewModdingAPI.Framework this.CoreAssets.Propagate( assets: removedAssets.ToDictionary(p => p.Key, p => p.Value), ignoreWorld: Context.IsWorldFullyUnloaded, - out IDictionary propagated, + out IDictionary propagated, out bool updatedNpcWarps ); // log summary StringBuilder report = new(); { - string[] invalidatedKeys = removedAssets.Keys.ToArray(); - string[] propagatedKeys = propagated.Where(p => p.Value).Select(p => p.Key).ToArray(); + IAssetName[] invalidatedKeys = removedAssets.Keys.ToArray(); + IAssetName[] propagatedKeys = propagated.Where(p => p.Value).Select(p => p.Key).ToArray(); - string FormatKeyList(IEnumerable keys) => string.Join(", ", keys.OrderBy(p => p, StringComparer.OrdinalIgnoreCase)); + string FormatKeyList(IEnumerable keys) => string.Join(", ", keys.Select(p => p.Name).OrderBy(p => p, StringComparer.OrdinalIgnoreCase)); report.AppendLine($"Invalidated {invalidatedKeys.Length} asset names ({FormatKeyList(invalidatedKeys)})."); report.AppendLine(propagated.Count > 0 @@ -422,15 +434,6 @@ namespace StardewModdingAPI.Framework return tilesheets ?? Array.Empty(); } - /// Get the language enum which corresponds to a locale code (e.g. given fr-FR). - /// The locale code to search. This must exactly match the language; no fallback is performed. - /// The matched language enum, if any. - /// Returns whether a valid language was found. - public bool TryGetLanguageEnum(string locale, out LocalizedContentManager.LanguageCode language) - { - return this.LocaleCodes.Value.TryGetValue(locale, out language); - } - /// Get the locale code which corresponds to a language enum (e.g. fr-FR given ). /// The language enum to search. public string GetLocaleCode(LocalizedContentManager.LanguageCode language) diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index 5645c0fa..be892b33 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -160,14 +160,6 @@ namespace StardewModdingAPI.Framework.ContentManagers return this.IsNormalizedKeyLoaded(assetName, language); } - /// - public IEnumerable GetAssetKeys() - { - return this.Cache.Keys - .Select(this.GetAssetName) - .Distinct(); - } - /**** ** Cache invalidation ****/ @@ -177,13 +169,13 @@ namespace StardewModdingAPI.Framework.ContentManagers IDictionary removeAssets = new Dictionary(StringComparer.OrdinalIgnoreCase); this.Cache.Remove((key, asset) => { - this.ParseCacheKey(key, out string assetName, out _); + string baseAssetName = this.Coordinator.ParseAssetName(key).BaseName; // check if asset should be removed - bool remove = removeAssets.ContainsKey(assetName); - if (!remove && predicate(assetName, asset.GetType())) + bool remove = removeAssets.ContainsKey(baseAssetName); + if (!remove && predicate(baseAssetName, asset.GetType())) { - removeAssets[assetName] = asset; + removeAssets[baseAssetName] = asset; remove = true; } @@ -275,44 +267,9 @@ namespace StardewModdingAPI.Framework.ContentManagers this.BaseDisposableReferences.Clear(); } - /// Parse a cache key into its component parts. - /// The input cache key. - /// The original asset name. - /// The asset locale code (or null if not localized). - protected void ParseCacheKey(string cacheKey, out string assetName, out string localeCode) - { - // handle localized key - if (!string.IsNullOrWhiteSpace(cacheKey)) - { - int lastSepIndex = cacheKey.LastIndexOf(".", StringComparison.Ordinal); - if (lastSepIndex >= 0) - { - string suffix = cacheKey.Substring(lastSepIndex + 1, cacheKey.Length - lastSepIndex - 1); - if (this.Coordinator.TryGetLanguageEnum(suffix, out _)) - { - assetName = cacheKey.Substring(0, lastSepIndex); - localeCode = cacheKey.Substring(lastSepIndex + 1, cacheKey.Length - lastSepIndex - 1); - return; - } - } - } - - // handle simple key - assetName = cacheKey; - localeCode = null; - } - /// Get whether an asset has already been loaded. /// The normalized asset name. /// The language to check. protected abstract bool IsNormalizedKeyLoaded(string normalizedAssetName, LanguageCode language); - - /// Get the asset name from a cache key. - /// The input cache key. - private string GetAssetName(string cacheKey) - { - this.ParseCacheKey(cacheKey, out string assetName, out string _); - return assetName; - } } } diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index ab198076..0ca9e277 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -73,46 +73,46 @@ namespace StardewModdingAPI.Framework.ContentManagers } // normalize asset name - assetName = this.AssertAndNormalizeAssetName(assetName); - if (this.TryParseExplicitLanguageAssetKey(assetName, out string newAssetName, out LanguageCode newLanguage)) - return this.Load(newAssetName, newLanguage, useCache); + IAssetName parsedName = this.Coordinator.ParseAssetName(assetName); + if (parsedName.LanguageCode.HasValue) + return this.Load(parsedName.BaseName, parsedName.LanguageCode.Value, useCache); // get from cache - if (useCache && this.IsLoaded(assetName, language)) - return this.RawLoad(assetName, language, useCache: true); + if (useCache && this.IsLoaded(parsedName.Name, language)) + return this.RawLoad(parsedName.Name, language, useCache: true); // get managed asset - if (this.Coordinator.TryParseManagedAssetKey(assetName, out string contentManagerID, out string relativePath)) + if (this.Coordinator.TryParseManagedAssetKey(parsedName.Name, out string contentManagerID, out string relativePath)) { T managedAsset = this.Coordinator.LoadManagedAsset(contentManagerID, relativePath); - this.TrackAsset(assetName, managedAsset, language, useCache); + this.TrackAsset(parsedName.Name, managedAsset, language, useCache); return managedAsset; } // load asset T data; - if (this.AssetsBeingLoaded.Contains(assetName)) + if (this.AssetsBeingLoaded.Contains(parsedName.Name)) { - this.Monitor.Log($"Broke loop while loading asset '{assetName}'.", LogLevel.Warn); + this.Monitor.Log($"Broke loop while loading asset '{parsedName.Name}'.", LogLevel.Warn); this.Monitor.Log($"Bypassing mod loaders for this asset. Stack trace:\n{Environment.StackTrace}"); - data = this.RawLoad(assetName, language, useCache); + data = this.RawLoad(parsedName.Name, language, useCache); } else { - data = this.AssetsBeingLoaded.Track(assetName, () => + data = this.AssetsBeingLoaded.Track(parsedName.Name, () => { string locale = this.GetLocale(language); - IAssetInfo info = new AssetInfo(locale, assetName, typeof(T), this.AssertAndNormalizeAssetName); + IAssetInfo info = new AssetInfo(locale, parsedName, typeof(T), this.AssertAndNormalizeAssetName); IAssetData asset = this.ApplyLoader(info) - ?? new AssetDataForObject(info, this.RawLoad(assetName, language, useCache), this.AssertAndNormalizeAssetName); + ?? new AssetDataForObject(info, this.RawLoad(parsedName.Name, language, useCache), this.AssertAndNormalizeAssetName); asset = this.ApplyEditors(info, asset); return (T)asset.Data; }); } // update cache & return data - this.TrackAsset(assetName, data, language, useCache); + this.TrackAsset(parsedName.Name, data, language, useCache); return data; } @@ -124,13 +124,16 @@ namespace StardewModdingAPI.Framework.ContentManagers // find assets for which a translatable version was loaded HashSet removeAssetNames = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (string key in this.LocalizedAssetNames.Where(p => p.Key != p.Value).Select(p => p.Key)) - removeAssetNames.Add(this.TryParseExplicitLanguageAssetKey(key, out string assetName, out _) ? assetName : key); + { + IAssetName assetName = this.Coordinator.ParseAssetName(key); + removeAssetNames.Add(assetName.BaseName); + } // invalidate translatable assets string[] invalidated = this .InvalidateCache((key, type) => removeAssetNames.Contains(key) - || (this.TryParseExplicitLanguageAssetKey(key, out string assetName, out _) && removeAssetNames.Contains(assetName)) + || removeAssetNames.Contains(this.Coordinator.ParseAssetName(key).BaseName) ) .Select(p => p.Key) .OrderBy(p => p, StringComparer.OrdinalIgnoreCase) @@ -168,9 +171,10 @@ namespace StardewModdingAPI.Framework.ContentManagers { // handle explicit language in asset name { - if (this.TryParseExplicitLanguageAssetKey(assetName, out string newAssetName, out LanguageCode newLanguage)) + IAssetName parsedName = this.Coordinator.ParseAssetName(assetName); + if (parsedName.LanguageCode.HasValue) { - this.TrackAsset(newAssetName, value, newLanguage, useCache); + this.TrackAsset(parsedName.BaseName, value, parsedName.LanguageCode.Value, useCache); return; } } @@ -238,30 +242,6 @@ namespace StardewModdingAPI.Framework.ContentManagers } } - /// Parse an asset key that contains an explicit language into its asset name and language, if applicable. - /// The asset key to parse. - /// The asset name without the language code. - /// The language code removed from the asset name. - /// Returns whether the asset key contains an explicit language and was successfully parsed. - private bool TryParseExplicitLanguageAssetKey(string rawAsset, out string assetName, out LanguageCode language) - { - if (string.IsNullOrWhiteSpace(rawAsset)) - throw new SContentLoadException("The asset key is empty."); - - // extract language code - int splitIndex = rawAsset.LastIndexOf('.'); - if (splitIndex != -1 && this.Coordinator.TryGetLanguageEnum(rawAsset.Substring(splitIndex + 1), out language)) - { - assetName = rawAsset.Substring(0, splitIndex); - return true; - } - - // no explicit language code found - assetName = rawAsset; - language = this.Language; - return false; - } - /// Load the initial asset from the registered . /// The basic asset metadata. /// Returns the loaded asset metadata, or null if no loader matched. @@ -277,7 +257,7 @@ namespace StardewModdingAPI.Framework.ContentManagers } catch (Exception ex) { - entry.Mod.LogAsMod($"Mod failed when checking whether it could load asset '{info.AssetName}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); + entry.Mod.LogAsMod($"Mod failed when checking whether it could load asset '{info.Name}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); return false; } }) @@ -289,7 +269,7 @@ namespace StardewModdingAPI.Framework.ContentManagers if (loaders.Length > 1) { string[] loaderNames = loaders.Select(p => p.Mod.DisplayName).ToArray(); - this.Monitor.Log($"Multiple mods want to provide the '{info.AssetName}' asset ({string.Join(", ", loaderNames)}), but an asset can't be loaded multiple times. SMAPI will use the default asset instead; uninstall one of the mods to fix this. (Message for modders: you should usually use {typeof(IAssetEditor)} instead to avoid conflicts.)", LogLevel.Warn); + this.Monitor.Log($"Multiple mods want to provide the '{info.Name}' asset ({string.Join(", ", loaderNames)}), but an asset can't be loaded multiple times. SMAPI will use the default asset instead; uninstall one of the mods to fix this. (Message for modders: you should usually use {typeof(IAssetEditor)} instead to avoid conflicts.)", LogLevel.Warn); return null; } @@ -300,11 +280,11 @@ namespace StardewModdingAPI.Framework.ContentManagers try { data = loader.Load(info); - this.Monitor.Log($"{mod.DisplayName} loaded asset '{info.AssetName}'.", LogLevel.Trace); + this.Monitor.Log($"{mod.DisplayName} loaded asset '{info.Name}'.", LogLevel.Trace); } catch (Exception ex) { - mod.LogAsMod($"Mod crashed when loading asset '{info.AssetName}'. SMAPI will use the default asset instead. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); + mod.LogAsMod($"Mod crashed when loading asset '{info.Name}'. SMAPI will use the default asset instead. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); return null; } @@ -349,7 +329,7 @@ namespace StardewModdingAPI.Framework.ContentManagers } catch (Exception ex) { - mod.LogAsMod($"Mod crashed when checking whether it could edit asset '{info.AssetName}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); + mod.LogAsMod($"Mod crashed when checking whether it could edit asset '{info.Name}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); continue; } @@ -358,22 +338,22 @@ namespace StardewModdingAPI.Framework.ContentManagers try { editor.Edit(asset); - this.Monitor.Log($"{mod.DisplayName} edited {info.AssetName}."); + this.Monitor.Log($"{mod.DisplayName} edited {info.Name}."); } catch (Exception ex) { - mod.LogAsMod($"Mod crashed when editing asset '{info.AssetName}', which may cause errors in-game. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); + mod.LogAsMod($"Mod crashed when editing asset '{info.Name}', which may cause errors in-game. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); } // validate edit if (asset.Data == null) { - mod.LogAsMod($"Mod incorrectly set asset '{info.AssetName}' to a null value; ignoring override.", LogLevel.Warn); + mod.LogAsMod($"Mod incorrectly set asset '{info.Name}' to a null value; ignoring override.", LogLevel.Warn); asset = GetNewData(prevAsset); } else if (!(asset.Data is T)) { - mod.LogAsMod($"Mod incorrectly set asset '{asset.AssetName}' to incompatible type '{asset.Data.GetType()}', expected '{typeof(T)}'; ignoring override.", LogLevel.Warn); + mod.LogAsMod($"Mod incorrectly set asset '{asset.Name}' to incompatible type '{asset.Data.GetType()}', expected '{typeof(T)}'; ignoring override.", LogLevel.Warn); asset = GetNewData(prevAsset); } } @@ -393,21 +373,21 @@ namespace StardewModdingAPI.Framework.ContentManagers // can't load a null asset if (data == null) { - mod.LogAsMod($"SMAPI blocked asset replacement for '{info.AssetName}': mod incorrectly set asset to a null value.", LogLevel.Error); + mod.LogAsMod($"SMAPI blocked asset replacement for '{info.Name}': mod incorrectly set asset to a null value.", LogLevel.Error); return false; } // when replacing a map, the vanilla tilesheets must have the same order and IDs if (data is Map loadedMap) { - TilesheetReference[] vanillaTilesheetRefs = this.Coordinator.GetVanillaTilesheetIds(info.AssetName); + TilesheetReference[] vanillaTilesheetRefs = this.Coordinator.GetVanillaTilesheetIds(info.Name.Name); foreach (TilesheetReference vanillaSheet in vanillaTilesheetRefs) { // add missing tilesheet if (loadedMap.GetTileSheet(vanillaSheet.Id) == null) { mod.Monitor.LogOnce("SMAPI fixed maps loaded by this mod to prevent errors. See the log file for details.", LogLevel.Warn); - this.Monitor.Log($"Fixed broken map replacement: {mod.DisplayName} loaded '{info.AssetName}' without a required tilesheet (id: {vanillaSheet.Id}, source: {vanillaSheet.ImageSource})."); + this.Monitor.Log($"Fixed broken map replacement: {mod.DisplayName} loaded '{info.Name}' without a required tilesheet (id: {vanillaSheet.Id}, source: {vanillaSheet.ImageSource})."); loadedMap.AddTileSheet(new TileSheet(vanillaSheet.Id, loadedMap, vanillaSheet.ImageSource, vanillaSheet.SheetSize, vanillaSheet.TileSize)); } @@ -417,17 +397,17 @@ namespace StardewModdingAPI.Framework.ContentManagers { // only show warning if not farm map // This is temporary: mods shouldn't do this for any vanilla map, but these are the ones we know will crash. Showing a warning for others instead gives modders time to update their mods, while still simplifying troubleshooting. - bool isFarmMap = info.AssetNameEquals("Maps/Farm") || info.AssetNameEquals("Maps/Farm_Combat") || info.AssetNameEquals("Maps/Farm_Fishing") || info.AssetNameEquals("Maps/Farm_Foraging") || info.AssetNameEquals("Maps/Farm_FourCorners") || info.AssetNameEquals("Maps/Farm_Island") || info.AssetNameEquals("Maps/Farm_Mining"); + bool isFarmMap = info.Name.IsEquivalentTo("Maps/Farm") || info.Name.IsEquivalentTo("Maps/Farm_Combat") || info.Name.IsEquivalentTo("Maps/Farm_Fishing") || info.Name.IsEquivalentTo("Maps/Farm_Foraging") || info.Name.IsEquivalentTo("Maps/Farm_FourCorners") || info.Name.IsEquivalentTo("Maps/Farm_Island") || info.Name.IsEquivalentTo("Maps/Farm_Mining"); string reason = $"mod reordered the original tilesheets, which {(isFarmMap ? "would cause a crash" : "often causes crashes")}.\nTechnical details for mod author: Expected order: {string.Join(", ", vanillaTilesheetRefs.Select(p => p.Id))}. See https://stardewvalleywiki.com/Modding:Maps#Tilesheet_order for help."; SCore.DeprecationManager.PlaceholderWarn("3.8.2", DeprecationLevel.PendingRemoval); if (isFarmMap) { - mod.LogAsMod($"SMAPI blocked '{info.AssetName}' map load: {reason}", LogLevel.Error); + mod.LogAsMod($"SMAPI blocked '{info.Name}' map load: {reason}", LogLevel.Error); return false; } - mod.LogAsMod($"SMAPI found an issue with '{info.AssetName}' map load: {reason}", LogLevel.Warn); + mod.LogAsMod($"SMAPI found an issue with '{info.Name}' map load: {reason}", LogLevel.Warn); } } } diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs index d7963305..ba7dbc06 100644 --- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs @@ -58,9 +58,6 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The language. bool IsLoaded(string assetName, LocalizedContentManager.LanguageCode language); - /// Get the cached asset keys. - IEnumerable GetAssetKeys(); - /// Purge matched assets from the cache. /// Matches the asset keys to invalidate. /// Whether to dispose invalidated assets. This should only be true when they're being invalidated as part of a dispose, to avoid crashing the game. diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index beb90a5d..21f88d47 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -80,7 +80,7 @@ namespace StardewModdingAPI.Framework.ContentManagers { // normalize key bool isXnbFile = Path.GetExtension(assetName).ToLower() == ".xnb"; - assetName = this.AssertAndNormalizeAssetName(assetName); + IAssetName parsedName = this.Coordinator.ParseAssetName(assetName); // disable caching // This is necessary to avoid assets being shared between content managers, which can @@ -97,21 +97,21 @@ namespace StardewModdingAPI.Framework.ContentManagers // resolve managed asset key { - if (this.Coordinator.TryParseManagedAssetKey(assetName, out string contentManagerID, out string relativePath)) + if (this.Coordinator.TryParseManagedAssetKey(parsedName.Name, out string contentManagerID, out string relativePath)) { if (contentManagerID != this.Name) - throw new SContentLoadException($"Can't load managed asset key '{assetName}' through content manager '{this.Name}' for a different mod."); - assetName = relativePath; + throw new SContentLoadException($"Can't load managed asset key '{parsedName}' through content manager '{this.Name}' for a different mod."); + parsedName = this.Coordinator.ParseAssetName(relativePath); } } // get local asset - SContentLoadException GetContentError(string reasonPhrase) => new SContentLoadException($"Failed loading asset '{assetName}' from {this.Name}: {reasonPhrase}"); + SContentLoadException GetContentError(string reasonPhrase) => new SContentLoadException($"Failed loading asset '{parsedName}' from {this.Name}: {reasonPhrase}"); T asset; try { // get file - FileInfo file = this.GetModFile(isXnbFile ? $"{assetName}.xnb" : assetName); // .xnb extension is stripped from asset names passed to the content manager + FileInfo file = this.GetModFile(isXnbFile ? $"{parsedName}.xnb" : parsedName.Name); // .xnb extension is stripped from asset names passed to the content manager if (!file.Exists) throw GetContentError("the specified path doesn't exist."); @@ -121,11 +121,11 @@ namespace StardewModdingAPI.Framework.ContentManagers // XNB file case ".xnb": { - asset = this.RawLoad(assetName, useCache: false); + asset = this.RawLoad(parsedName.Name, useCache: false); if (asset is Map map) { - map.assetPath = assetName; - this.FixTilesheetPaths(map, relativeMapPath: assetName, fixEagerPathPrefixes: true); + map.assetPath = parsedName.Name; + this.FixTilesheetPaths(map, relativeMapPath: parsedName.Name, fixEagerPathPrefixes: true); } } break; @@ -173,8 +173,8 @@ namespace StardewModdingAPI.Framework.ContentManagers // fetch & cache FormatManager formatManager = FormatManager.Instance; Map map = formatManager.LoadMap(file.FullName); - map.assetPath = assetName; - this.FixTilesheetPaths(map, relativeMapPath: assetName, fixEagerPathPrefixes: false); + map.assetPath = parsedName.Name; + this.FixTilesheetPaths(map, relativeMapPath: parsedName.Name, fixEagerPathPrefixes: false); asset = (T)(object)map; } break; @@ -185,11 +185,11 @@ namespace StardewModdingAPI.Framework.ContentManagers } catch (Exception ex) when (!(ex is SContentLoadException)) { - throw new SContentLoadException($"The content manager failed loading content asset '{assetName}' from {this.Name}.", ex); + throw new SContentLoadException($"The content manager failed loading content asset '{parsedName}' from {this.Name}.", ex); } // track & return asset - this.TrackAsset(assetName, asset, language, useCache); + this.TrackAsset(parsedName.Name, asset, language, useCache); return asset; } diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index bfca2264..a01248a8 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -129,7 +129,7 @@ namespace StardewModdingAPI.Framework.ModHelpers { string actualKey = this.GetActualAssetKey(key, ContentSource.GameContent); this.Monitor.Log($"Requested cache invalidation for '{actualKey}'.", LogLevel.Trace); - return this.ContentCore.InvalidateCache(asset => asset.AssetNameEquals(actualKey)).Any(); + return this.ContentCore.InvalidateCache(asset => asset.Name.IsEquivalentTo(actualKey)).Any(); } /// @@ -153,7 +153,8 @@ namespace StardewModdingAPI.Framework.ModHelpers throw new ArgumentNullException(nameof(data), "Can't get a patch helper for a null value."); assetName ??= $"temp/{Guid.NewGuid():N}"; - return new AssetDataForObject(this.CurrentLocale, assetName, data, this.NormalizeAssetName); + + return new AssetDataForObject(this.CurrentLocale, this.ContentCore.ParseAssetName(assetName), data, this.NormalizeAssetName); } diff --git a/src/SMAPI/IAssetInfo.cs b/src/SMAPI/IAssetInfo.cs index 6cdf01ee..6ac8358d 100644 --- a/src/SMAPI/IAssetInfo.cs +++ b/src/SMAPI/IAssetInfo.cs @@ -11,7 +11,11 @@ namespace StardewModdingAPI /// The content's locale code, if the content is localized. string Locale { get; } + /// The asset name being read. + public IAssetName Name { get; } + /// The normalized asset name being read. The format may change between platforms; see to compare with a known path. + [Obsolete($"Use {nameof(Name)} instead.")] string AssetName { get; } /// The content data type. @@ -23,6 +27,7 @@ namespace StardewModdingAPI *********/ /// Get whether the asset name being loaded matches a given name after normalization. /// The expected asset path, relative to the game's content folder and without the .xnb extension or locale suffix (like 'Data\ObjectInformation'). + [Obsolete($"Use {nameof(Name)}.{nameof(IAssetName.IsEquivalentTo)} instead.")] bool AssetNameEquals(string path); } } diff --git a/src/SMAPI/IAssetName.cs b/src/SMAPI/IAssetName.cs new file mode 100644 index 00000000..a5bfea93 --- /dev/null +++ b/src/SMAPI/IAssetName.cs @@ -0,0 +1,44 @@ +using System; +using StardewValley; + +namespace StardewModdingAPI +{ + /// The name for an asset loaded through the content pipeline. + public interface IAssetName : IEquatable + { + /********* + ** Accessors + *********/ + /// The full normalized asset name, including the locale if applicable (like Data/Achievements.fr-FR). + string Name { get; } + + /// The base asset name without the locale code. + string BaseName { get; } + + /// The locale code specified in the , if it's a valid code recognized by the game content. + string LocaleCode { get; } + + /// The language code matching the , if applicable. + LocalizedContentManager.LanguageCode? LanguageCode { get; } + + + /********* + ** Public methods + *********/ + /// Get whether the given asset name is equivalent, ignoring capitalization and formatting. + /// The asset name to compare this instance to. + /// Whether to compare the given name with the (if true) or (if false). This has no effect on any locale included in the given . + bool IsEquivalentTo(string assetName, bool useBaseName = false); + + /// Get whether the asset name starts with the given value, ignoring capitalization and formatting. This can be used with a trailing slash to test for an asset folder, like Data/. + /// The prefix to match. + /// Whether to match if the prefix occurs mid-word, so Data/AchievementsToIgnore matches prefix Data/Achievements. If this is false, the prefix only matches if the asset name starts with the prefix followed by a non-alphanumeric character (including ., /, or \\) or the end of string. + /// Whether to match the prefix if there's a subfolder path after it, so Data/Achievements/Example matches prefix Data/Achievements. If this is false, the prefix only matches if the asset name has no / or \\ characters after the prefix. + bool StartsWith(string prefix, bool allowPartialWord = true, bool allowSubfolder = true); + + /// Get whether the asset is directly within the given asset path. + /// For example, Characters/Dialogue/Abigail is directly under Characters/Dialogue but not Characters or Characters/Dialogue/Ab. To allow sub-paths, use instead. + /// The asset path to check. This doesn't need a trailing slash. + bool IsDirectlyUnderPath(string assetFolder); + } +} diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index a6c4bb24..e7fac578 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -87,22 +87,22 @@ namespace StardewModdingAPI.Metadata /// Whether the in-game world is fully unloaded (e.g. on the title screen), so there's no need to propagate changes into the world. /// A lookup of asset names to whether they've been propagated. /// Whether the NPC pathfinding cache was reloaded. - public void Propagate(IDictionary assets, bool ignoreWorld, out IDictionary propagatedAssets, out bool updatedNpcWarps) + public void Propagate(IDictionary assets, bool ignoreWorld, out IDictionary propagatedAssets, out bool updatedNpcWarps) { // group into optimized lists var buckets = assets.GroupBy(p => { - if (this.IsInFolder(p.Key, "Characters") || this.IsInFolder(p.Key, "Characters/Monsters")) + if (p.Key.IsDirectlyUnderPath("Characters") || p.Key.IsDirectlyUnderPath("Characters/Monsters")) return AssetBucket.Sprite; - if (this.IsInFolder(p.Key, "Portraits")) + if (p.Key.IsDirectlyUnderPath("Portraits")) return AssetBucket.Portrait; return AssetBucket.Other; }); // reload assets - propagatedAssets = assets.ToDictionary(p => p.Key, _ => false, StringComparer.OrdinalIgnoreCase); + propagatedAssets = assets.ToDictionary(p => p.Key, _ => false); updatedNpcWarps = false; foreach (var bucket in buckets) { @@ -149,16 +149,16 @@ namespace StardewModdingAPI.Metadata ** Private methods *********/ /// Reload one of the game's core assets (if applicable). - /// The asset key to reload. + /// The asset name to reload. /// The asset type to reload. /// Whether the in-game world is fully unloaded (e.g. on the title screen), so there's no need to propagate changes into the world. /// Whether any map warps were changed as part of this propagation. /// Returns whether an asset was loaded. The return value may be true or false, or a non-null value for true. [SuppressMessage("ReSharper", "StringLiteralTypo", Justification = "These deliberately match the asset names.")] - private bool PropagateOther(string key, Type type, bool ignoreWorld, out bool changedWarps) + private bool PropagateOther(IAssetName assetName, Type type, bool ignoreWorld, out bool changedWarps) { var content = this.MainContentManager; - key = this.AssertAndNormalizeAssetName(key); + string key = assetName.Name; changedWarps = false; /**** @@ -170,7 +170,7 @@ namespace StardewModdingAPI.Metadata { foreach (TileSheet tilesheet in Game1.currentLocation.map.TileSheets) { - if (this.IsSameAssetKey(tilesheet.ImageSource, key)) + if (assetName.IsEquivalentTo(tilesheet.ImageSource)) Game1.mapDisplayDevice.LoadTileSheet(tilesheet); } } @@ -188,7 +188,7 @@ namespace StardewModdingAPI.Metadata { GameLocation location = info.Location; - if (this.IsSameAssetKey(location.mapPath.Value, key)) + if (assetName.IsEquivalentTo(location.mapPath.Value)) { static ISet GetWarpSet(GameLocation location) { @@ -213,14 +213,13 @@ namespace StardewModdingAPI.Metadata /**** ** Propagate by key ****/ - Reflector reflection = this.Reflection; - switch (key.ToLower().Replace("\\", "/")) // normalized key so we can compare statically + switch (assetName.Name.ToLower().Replace("\\", "/")) // normalized key so we can compare statically { /**** ** Animals ****/ case "animals/horse": - return !ignoreWorld && this.ReloadPetOrHorseSprites(content, key); + return !ignoreWorld && this.ReloadPetOrHorseSprites(content, assetName); /**** ** Buildings @@ -231,7 +230,7 @@ namespace StardewModdingAPI.Metadata case "buildings/houses_paintmask": // Farm { - bool removedFromCache = this.RemoveFromPaintMaskCache(key); + bool removedFromCache = this.RemoveFromPaintMaskCache(assetName); Farm farm = Game1.getFarm(); farm?.ApplyHousePaint(); @@ -250,7 +249,7 @@ namespace StardewModdingAPI.Metadata case "characters/farmer/farmer_base_bald": case "characters/farmer/farmer_girl_base": case "characters/farmer/farmer_girl_base_bald": - return !ignoreWorld && this.ReloadPlayerSprites(key); + return !ignoreWorld && this.ReloadPlayerSprites(assetName); case "characters/farmer/hairstyles": // Game1.LoadContent FarmerRenderer.hairStylesTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.hairStylesTexture, key); @@ -313,7 +312,7 @@ namespace StardewModdingAPI.Metadata return true; case "data/npcdispositions": // NPC constructor - return !ignoreWorld && this.ReloadNpcDispositions(content, key); + return !ignoreWorld && this.ReloadNpcDispositions(content, assetName); case "data/npcgifttastes": // Game1.LoadContent Game1.NPCGiftTastes = content.Load>(key); @@ -393,7 +392,7 @@ namespace StardewModdingAPI.Metadata } if (!ignoreWorld) - this.ReloadDoorSprites(content, key); + this.ReloadDoorSprites(content, assetName); return true; case "loosesprites/cursors2": // Game1.LoadContent @@ -425,7 +424,7 @@ namespace StardewModdingAPI.Metadata return true; case "loosesprites/suspensionbridge": // SuspensionBridge constructor - return !ignoreWorld && this.ReloadSuspensionBridges(content, key); + return !ignoreWorld && this.ReloadSuspensionBridges(content, assetName); /**** ** Content\Maps @@ -456,7 +455,7 @@ namespace StardewModdingAPI.Metadata return false; case "minigames/titlebuttons": // TitleMenu - return this.ReloadTitleButtons(content, key); + return this.ReloadTitleButtons(content, assetName); /**** ** Content\Strings @@ -480,14 +479,14 @@ namespace StardewModdingAPI.Metadata return true; case "tilesheets/chairtiles": // Game1.LoadContent - return this.ReloadChairTiles(content, key, ignoreWorld); + return this.ReloadChairTiles(content, assetName, ignoreWorld); case "tilesheets/craftables": // Game1.LoadContent Game1.bigCraftableSpriteSheet = content.Load(key); return true; case "tilesheets/critters": // Critter constructor - return !ignoreWorld && this.ReloadCritterTextures(content, key) > 0; + return !ignoreWorld && this.ReloadCritterTextures(content, assetName) > 0; case "tilesheets/crops": // Game1.LoadContent Game1.cropSpriteSheet = content.Load(key); @@ -541,7 +540,7 @@ namespace StardewModdingAPI.Metadata return true; case "terrainfeatures/grass": // from Grass - return !ignoreWorld && this.ReloadGrassTextures(content, key); + return !ignoreWorld && this.ReloadGrassTextures(content, assetName); case "terrainfeatures/hoedirt": // from HoeDirt HoeDirt.lightTexture = content.Load(key); @@ -556,27 +555,27 @@ namespace StardewModdingAPI.Metadata return true; case "terrainfeatures/mushroom_tree": // from Tree - return !ignoreWorld && this.ReloadTreeTextures(content, key, Tree.mushroomTree); + return !ignoreWorld && this.ReloadTreeTextures(content, assetName, Tree.mushroomTree); case "terrainfeatures/tree_palm": // from Tree - return !ignoreWorld && this.ReloadTreeTextures(content, key, Tree.palmTree); + return !ignoreWorld && this.ReloadTreeTextures(content, assetName, Tree.palmTree); case "terrainfeatures/tree1_fall": // from Tree case "terrainfeatures/tree1_spring": // from Tree case "terrainfeatures/tree1_summer": // from Tree case "terrainfeatures/tree1_winter": // from Tree - return !ignoreWorld && this.ReloadTreeTextures(content, key, Tree.bushyTree); + return !ignoreWorld && this.ReloadTreeTextures(content, assetName, Tree.bushyTree); case "terrainfeatures/tree2_fall": // from Tree case "terrainfeatures/tree2_spring": // from Tree case "terrainfeatures/tree2_summer": // from Tree case "terrainfeatures/tree2_winter": // from Tree - return !ignoreWorld && this.ReloadTreeTextures(content, key, Tree.leafyTree); + return !ignoreWorld && this.ReloadTreeTextures(content, assetName, Tree.leafyTree); case "terrainfeatures/tree3_fall": // from Tree case "terrainfeatures/tree3_spring": // from Tree case "terrainfeatures/tree3_winter": // from Tree - return !ignoreWorld && this.ReloadTreeTextures(content, key, Tree.pineTree); + return !ignoreWorld && this.ReloadTreeTextures(content, assetName, Tree.pineTree); } /**** @@ -585,25 +584,25 @@ namespace StardewModdingAPI.Metadata if (!ignoreWorld) { // dynamic textures - if (this.KeyStartsWith(key, "animals/cat")) - return this.ReloadPetOrHorseSprites(content, key); - if (this.KeyStartsWith(key, "animals/dog")) - return this.ReloadPetOrHorseSprites(content, key); - if (this.IsInFolder(key, "Animals")) - return this.ReloadFarmAnimalSprites(content, key); + if (assetName.StartsWith("animals/cat")) + return this.ReloadPetOrHorseSprites(content, assetName); + if (assetName.StartsWith("animals/dog")) + return this.ReloadPetOrHorseSprites(content, assetName); + if (assetName.IsDirectlyUnderPath("Animals")) + return this.ReloadFarmAnimalSprites(content, assetName); - if (this.IsInFolder(key, "Buildings")) - return this.ReloadBuildings(key); + if (assetName.IsDirectlyUnderPath("Buildings")) + return this.ReloadBuildings(assetName); - if (this.KeyStartsWith(key, "LooseSprites/Fence")) - return this.ReloadFenceTextures(key); + if (assetName.StartsWith("LooseSprites/Fence")) + return this.ReloadFenceTextures(assetName); // dynamic data - if (this.IsInFolder(key, "Characters/Dialogue")) - return this.ReloadNpcDialogue(key); + if (assetName.IsDirectlyUnderPath("Characters/Dialogue")) + return this.ReloadNpcDialogue(assetName); - if (this.IsInFolder(key, "Characters/schedules")) - return this.ReloadNpcSchedules(key); + if (assetName.IsDirectlyUnderPath("Characters/schedules")) + return this.ReloadNpcSchedules(assetName); } return false; @@ -618,14 +617,14 @@ namespace StardewModdingAPI.Metadata ****/ /// Reload buttons on the title screen. /// The content manager through which to reload the asset. - /// The asset key to reload. + /// The asset name to reload. /// Returns whether any textures were reloaded. /// Derived from the constructor and . - private bool ReloadTitleButtons(LocalizedContentManager content, string key) + private bool ReloadTitleButtons(LocalizedContentManager content, IAssetName assetName) { if (Game1.activeClickableMenu is TitleMenu titleMenu) { - Texture2D texture = content.Load(key); + Texture2D texture = content.Load(assetName.Name); titleMenu.titleButtonsTexture = texture; titleMenu.backButton.texture = texture; @@ -645,21 +644,21 @@ namespace StardewModdingAPI.Metadata /// Reload the sprites for matching pets or horses. /// The animal type. /// The content manager through which to reload the asset. - /// The asset key to reload. + /// The asset name to reload. /// Returns whether any textures were reloaded. - private bool ReloadPetOrHorseSprites(LocalizedContentManager content, string key) + private bool ReloadPetOrHorseSprites(LocalizedContentManager content, IAssetName assetName) where TAnimal : NPC { // find matches TAnimal[] animals = this.GetCharacters() .OfType() - .Where(p => this.IsSameAssetKey(p.Sprite?.Texture?.Name, key)) + .Where(p => assetName.IsEquivalentTo(p.Sprite?.Texture?.Name)) .ToArray(); if (!animals.Any()) return false; // update sprites - Texture2D texture = content.Load(key); + Texture2D texture = content.Load(assetName.Name); foreach (TAnimal animal in animals) animal.Sprite.spriteTexture = texture; return true; @@ -667,10 +666,10 @@ namespace StardewModdingAPI.Metadata /// Reload the sprites for matching farm animals. /// The content manager through which to reload the asset. - /// The asset key to reload. + /// The asset name to reload. /// Returns whether any textures were reloaded. /// Derived from . - private bool ReloadFarmAnimalSprites(LocalizedContentManager content, string key) + private bool ReloadFarmAnimalSprites(LocalizedContentManager content, IAssetName assetName) { // find matches FarmAnimal[] animals = this.GetFarmAnimals().ToArray(); @@ -678,7 +677,7 @@ namespace StardewModdingAPI.Metadata return false; // update sprites - Lazy texture = new Lazy(() => content.Load(key)); + Lazy texture = new Lazy(() => content.Load(assetName.Name)); foreach (FarmAnimal animal in animals) { // get expected key @@ -690,23 +689,23 @@ namespace StardewModdingAPI.Metadata expectedKey = $"Animals/{expectedKey}"; // reload asset - if (this.IsSameAssetKey(expectedKey, key)) + if (assetName.IsEquivalentTo(expectedKey)) animal.Sprite.spriteTexture = texture.Value; } return texture.IsValueCreated; } /// Reload building textures. - /// The asset key to reload. + /// The asset name to reload. /// Returns whether any textures were reloaded. - private bool ReloadBuildings(string key) + private bool ReloadBuildings(IAssetName assetName) { // get paint mask info const string paintMaskSuffix = "_PaintMask"; - bool isPaintMask = key.EndsWith(paintMaskSuffix, StringComparison.OrdinalIgnoreCase); + bool isPaintMask = assetName.BaseName.EndsWith(paintMaskSuffix, StringComparison.OrdinalIgnoreCase); // get building type - string type = Path.GetFileName(key); + string type = Path.GetFileName(assetName.Name)!; if (isPaintMask) type = type.Substring(0, type.Length - paintMaskSuffix.Length); @@ -718,7 +717,7 @@ namespace StardewModdingAPI.Metadata .ToArray(); // remove from paint mask cache - bool removedFromCache = this.RemoveFromPaintMaskCache(key); + bool removedFromCache = this.RemoveFromPaintMaskCache(assetName); // reload textures if (buildings.Any()) @@ -734,12 +733,12 @@ namespace StardewModdingAPI.Metadata /// Reload map seat textures. /// The content manager through which to reload the asset. - /// The asset key to reload. + /// The asset name to reload. /// Whether the in-game world is fully unloaded (e.g. on the title screen), so there's no need to propagate changes into the world. /// Returns whether any textures were reloaded. - private bool ReloadChairTiles(LocalizedContentManager content, string key, bool ignoreWorld) + private bool ReloadChairTiles(LocalizedContentManager content, IAssetName assetName, bool ignoreWorld) { - MapSeat.mapChairTexture = content.Load(key); + MapSeat.mapChairTexture = content.Load(assetName.Name); if (!ignoreWorld) { @@ -747,7 +746,7 @@ namespace StardewModdingAPI.Metadata { foreach (MapSeat seat in location.mapSeats.Where(p => p != null)) { - if (this.IsSameAssetKey(seat._loadedTextureFile, key)) + if (assetName.IsEquivalentTo(seat._loadedTextureFile)) seat.overlayTexture = MapSeat.mapChairTexture; } } @@ -758,9 +757,9 @@ namespace StardewModdingAPI.Metadata /// Reload critter textures. /// The content manager through which to reload the asset. - /// The asset key to reload. + /// The asset name to reload. /// Returns the number of reloaded assets. - private int ReloadCritterTextures(LocalizedContentManager content, string key) + private int ReloadCritterTextures(LocalizedContentManager content, IAssetName assetName) { // get critters Critter[] critters = @@ -768,7 +767,7 @@ namespace StardewModdingAPI.Metadata from location in this.GetLocations() where location.critters != null from Critter critter in location.critters - where this.IsSameAssetKey(critter.sprite?.Texture?.Name, key) + where assetName.IsEquivalentTo(critter.sprite?.Texture?.Name) select critter ) .ToArray(); @@ -776,7 +775,7 @@ namespace StardewModdingAPI.Metadata return 0; // update sprites - Texture2D texture = content.Load(key); + Texture2D texture = content.Load(assetName.Name); foreach (var entry in critters) entry.sprite.spriteTexture = texture; @@ -785,11 +784,11 @@ namespace StardewModdingAPI.Metadata /// Reload the sprites for interior doors. /// The content manager through which to reload the asset. - /// The asset key to reload. + /// The asset name to reload. /// Returns whether any doors were affected. - private bool ReloadDoorSprites(LocalizedContentManager content, string key) + private bool ReloadDoorSprites(LocalizedContentManager content, IAssetName assetName) { - Lazy texture = new Lazy(() => content.Load(key)); + Lazy texture = new Lazy(() => content.Load(assetName.Name)); foreach (GameLocation location in this.GetLocations()) { @@ -803,7 +802,7 @@ namespace StardewModdingAPI.Metadata continue; string curKey = this.Reflection.GetField(door.Sprite, "textureName").GetValue(); - if (this.IsSameAssetKey(curKey, key)) + if (assetName.IsEquivalentTo(curKey)) door.Sprite.texture = texture.Value; } } @@ -827,12 +826,12 @@ namespace StardewModdingAPI.Metadata } /// Reload the sprites for a fence type. - /// The asset key to reload. + /// The asset name to reload. /// Returns whether any textures were reloaded. - private bool ReloadFenceTextures(string key) + private bool ReloadFenceTextures(IAssetName assetName) { - // get fence type - if (!int.TryParse(this.GetSegments(key)[1].Substring("Fence".Length), out int fenceType)) + // get fence type (e.g. LooseSprites/Fence3 => 3) + if (!int.TryParse(this.GetSegments(assetName.BaseName)[1].Substring("Fence".Length), out int fenceType)) return false; // get fences @@ -855,22 +854,22 @@ namespace StardewModdingAPI.Metadata /// Reload tree textures. /// The content manager through which to reload the asset. - /// The asset key to reload. + /// The asset name to reload. /// Returns whether any textures were reloaded. - private bool ReloadGrassTextures(LocalizedContentManager content, string key) + private bool ReloadGrassTextures(LocalizedContentManager content, IAssetName assetName) { Grass[] grasses = ( from location in this.GetLocations() from grass in location.terrainFeatures.Values.OfType() - where this.IsSameAssetKey(grass.textureName(), key) + where assetName.IsEquivalentTo(grass.textureName()) select grass ) .ToArray(); if (grasses.Any()) { - Lazy texture = new Lazy(() => content.Load(key)); + Lazy texture = new Lazy(() => content.Load(assetName.Name)); foreach (Grass grass in grasses) grass.texture = texture; return true; @@ -932,11 +931,11 @@ namespace StardewModdingAPI.Metadata /// Reload the disposition data for matching NPCs. /// The content manager through which to reload the asset. - /// The asset key to reload. + /// The asset name to reload. /// Returns whether any NPCs were affected. - private bool ReloadNpcDispositions(LocalizedContentManager content, string key) + private bool ReloadNpcDispositions(LocalizedContentManager content, IAssetName assetName) { - IDictionary data = content.Load>(key); + IDictionary data = content.Load>(assetName.Name); bool changed = false; foreach (NPC npc in this.GetCharacters()) { @@ -953,16 +952,16 @@ namespace StardewModdingAPI.Metadata /// Reload the sprites for matching NPCs. /// The asset keys to reload. /// The asset keys which have been propagated. - private void ReloadNpcSprites(IEnumerable keys, IDictionary propagated) + private void ReloadNpcSprites(IEnumerable keys, IDictionary propagated) { // get NPCs - HashSet lookup = new HashSet(keys, StringComparer.OrdinalIgnoreCase); + IDictionary lookup = keys.ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase); var characters = ( from npc in this.GetCharacters() let key = this.NormalizeAssetNameIgnoringEmpty(npc.Sprite?.Texture?.Name) - where key != null && lookup.Contains(key) - select new { Npc = npc, Key = key } + where key != null && lookup.ContainsKey(key) + select new { Npc = npc, AssetName = lookup[key] } ) .ToArray(); if (!characters.Any()) @@ -971,56 +970,56 @@ namespace StardewModdingAPI.Metadata // update sprite foreach (var target in characters) { - target.Npc.Sprite.spriteTexture = this.LoadAndDisposeIfNeeded(target.Npc.Sprite.spriteTexture, target.Key); - propagated[target.Key] = true; + target.Npc.Sprite.spriteTexture = this.LoadAndDisposeIfNeeded(target.Npc.Sprite.spriteTexture, target.AssetName.Name); + propagated[target.AssetName] = true; } } /// Reload the portraits for matching NPCs. /// The asset key to reload. /// The asset keys which have been propagated. - private void ReloadNpcPortraits(IEnumerable keys, IDictionary propagated) + private void ReloadNpcPortraits(IEnumerable keys, IDictionary propagated) { // get NPCs - HashSet lookup = new HashSet(keys, StringComparer.OrdinalIgnoreCase); + IDictionary lookup = keys.ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase); var characters = ( from npc in this.GetCharacters() where npc.isVillager() let key = this.NormalizeAssetNameIgnoringEmpty(npc.Portrait?.Name) - where key != null && lookup.Contains(key) - select new { Npc = npc, Key = key } + where key != null && lookup.ContainsKey(key) + select new { Npc = npc, AssetName = lookup[key] } ) .ToList(); // special case: Gil is a private NPC field on the AdventureGuild class (only used for the portrait) { string gilKey = this.NormalizeAssetNameIgnoringEmpty("Portraits/Gil"); - if (lookup.Contains(gilKey)) + if (lookup.TryGetValue(gilKey, out IAssetName assetName)) { GameLocation adventureGuild = Game1.getLocationFromName("AdventureGuild"); if (adventureGuild != null) - characters.Add(new { Npc = this.Reflection.GetField(adventureGuild, "Gil").GetValue(), Key = gilKey }); + characters.Add(new { Npc = this.Reflection.GetField(adventureGuild, "Gil").GetValue(), AssetName = assetName }); } } // update portrait foreach (var target in characters) { - target.Npc.Portrait = this.LoadAndDisposeIfNeeded(target.Npc.Portrait, target.Key); - propagated[target.Key] = true; + target.Npc.Portrait = this.LoadAndDisposeIfNeeded(target.Npc.Portrait, target.AssetName.Name); + propagated[target.AssetName] = true; } } /// Reload the sprites for matching players. - /// The asset key to reload. - private bool ReloadPlayerSprites(string key) + /// The asset name to reload. + private bool ReloadPlayerSprites(IAssetName assetName) { Farmer[] players = ( from player in Game1.getOnlineFarmers() - where this.IsSameAssetKey(player.getTexture(), key) + where assetName.IsEquivalentTo(player.getTexture()) select player ) .ToArray(); @@ -1036,11 +1035,11 @@ namespace StardewModdingAPI.Metadata /// Reload suspension bridge textures. /// The content manager through which to reload the asset. - /// The asset key to reload. + /// The asset name to reload. /// Returns whether any textures were reloaded. - private bool ReloadSuspensionBridges(LocalizedContentManager content, string key) + private bool ReloadSuspensionBridges(LocalizedContentManager content, IAssetName assetName) { - Lazy texture = new Lazy(() => content.Load(key)); + Lazy texture = new Lazy(() => content.Load(assetName.Name)); foreach (GameLocation location in this.GetLocations(buildingInteriors: false)) { @@ -1059,10 +1058,10 @@ namespace StardewModdingAPI.Metadata /// Reload tree textures. /// The content manager through which to reload the asset. - /// The asset key to reload. + /// The asset name to reload. /// The type to reload. /// Returns whether any textures were reloaded. - private bool ReloadTreeTextures(LocalizedContentManager content, string key, int type) + private bool ReloadTreeTextures(LocalizedContentManager content, IAssetName assetName, int type) { Tree[] trees = this.GetLocations() .SelectMany(p => p.terrainFeatures.Values.OfType()) @@ -1071,7 +1070,7 @@ namespace StardewModdingAPI.Metadata if (trees.Any()) { - Lazy texture = new Lazy(() => content.Load(key)); + Lazy texture = new Lazy(() => content.Load(assetName.Name)); foreach (Tree tree in trees) tree.texture = texture; return true; @@ -1084,12 +1083,12 @@ namespace StardewModdingAPI.Metadata ** Reload data methods ****/ /// Reload the dialogue data for matching NPCs. - /// The asset key to reload. + /// The asset name to reload. /// Returns whether any assets were reloaded. - private bool ReloadNpcDialogue(string key) + private bool ReloadNpcDialogue(IAssetName assetName) { // get NPCs - string name = Path.GetFileName(key); + string name = Path.GetFileName(assetName.Name); NPC[] villagers = this.GetCharacters().Where(npc => npc.Name == name && npc.isVillager()).ToArray(); if (!villagers.Any()) return false; @@ -1114,12 +1113,12 @@ namespace StardewModdingAPI.Metadata } /// Reload the schedules for matching NPCs. - /// The asset key to reload. + /// The asset name to reload. /// Returns whether any assets were reloaded. - private bool ReloadNpcSchedules(string key) + private bool ReloadNpcSchedules(IAssetName assetName) { // get NPCs - string name = Path.GetFileName(key); + string name = Path.GetFileName(assetName.Name); NPC[] villagers = this.GetCharacters().Where(npc => npc.Name == name && npc.isVillager()).ToArray(); if (!villagers.Any()) return false; @@ -1243,39 +1242,6 @@ namespace StardewModdingAPI.Metadata return this.AssertAndNormalizeAssetName(path); } - /// Get whether a given asset key is equivalent to a normalized asset key, ignoring unimportant differences like capitalization and formatting. - /// The actual key to check. - /// The key to match, already normalized via or . - private bool IsSameAssetKey(string actualKey, string normalizedKey) - { - if (actualKey is null || normalizedKey is null) - return false; - - return normalizedKey.Equals(PathUtilities.NormalizeAssetName(actualKey), StringComparison.OrdinalIgnoreCase); - } - - /// Get whether a key starts with a substring after the substring is normalized. - /// The key to check. - /// The substring to normalize and find. - private bool KeyStartsWith(string key, string rawSubstring) - { - if (string.IsNullOrWhiteSpace(key) || string.IsNullOrWhiteSpace(rawSubstring)) - return false; - - return key.StartsWith(this.NormalizeAssetNameIgnoringEmpty(rawSubstring), StringComparison.OrdinalIgnoreCase); - } - - /// Get whether a normalized asset key is in the given folder. - /// The normalized asset key (like Animals/cat). - /// The key folder (like Animals); doesn't need to be normalized. - /// Whether to return true if the key is inside a subfolder of the . - private bool IsInFolder(string key, string folder, bool allowSubfolders = false) - { - return - this.KeyStartsWith(key, $"{folder}/") - && (allowSubfolders || this.CountSegments(key) == this.CountSegments(folder) + 1); - } - /// Get the segments in a path (e.g. 'a/b' is 'a' and 'b'). /// The path to check. private string[] GetSegments(string path) @@ -1285,13 +1251,6 @@ namespace StardewModdingAPI.Metadata : Array.Empty(); } - /// Count the number of segments in a path (e.g. 'a/b' is 2). - /// The path to check. - private int CountSegments(string path) - { - return this.GetSegments(path).Length; - } - /// Load a texture, and dispose the old one if is enabled and it's different from the new instance. /// The previous texture to dispose. /// The asset key to load. @@ -1315,8 +1274,8 @@ namespace StardewModdingAPI.Metadata } /// Remove a case-insensitive key from the paint mask cache. - /// The paint mask asset key. - private bool RemoveFromPaintMaskCache(string key) + /// The paint mask asset name. + private bool RemoveFromPaintMaskCache(IAssetName assetName) { // make cache case-insensitive // This is needed for cache invalidation since mods may specify keys with a different capitalization @@ -1324,7 +1283,7 @@ namespace StardewModdingAPI.Metadata BuildingPainter.paintMaskLookup = new Dictionary>>(BuildingPainter.paintMaskLookup, StringComparer.OrdinalIgnoreCase); // remove key from cache - return BuildingPainter.paintMaskLookup.Remove(key); + return BuildingPainter.paintMaskLookup.Remove(assetName.Name); } /// Metadata about a location used in asset propagation. -- cgit From 0f987c0578b69d57c01502bc44b43b4e9619e658 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 25 Feb 2022 23:40:52 -0500 Subject: restore InterfaceProxyFactory to encapsulate Pintail (#830) --- .../Framework/ModHelpers/ModRegistryHelper.cs | 17 +++++---- .../Framework/Reflection/InterfaceProxyFactory.cs | 42 ++++++++++++++++++++++ src/SMAPI/Framework/SCore.cs | 17 +++------ 3 files changed, 55 insertions(+), 21 deletions(-) create mode 100644 src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 93ea6028..95eb03f3 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Nanoray.Pintail; using StardewModdingAPI.Framework.Reflection; namespace StardewModdingAPI.Framework.ModHelpers @@ -17,10 +16,10 @@ namespace StardewModdingAPI.Framework.ModHelpers private readonly IMonitor Monitor; /// The mod IDs for APIs accessed by this instanced. - private readonly HashSet AccessedModApis = new HashSet(); + private readonly HashSet AccessedModApis = new(); /// Generates proxy classes to access mod APIs through an arbitrary interface. - private readonly IProxyManager ProxyManager; + private readonly InterfaceProxyFactory ProxyFactory; /********* @@ -29,13 +28,13 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Construct an instance. /// The unique ID of the relevant mod. /// The underlying mod registry. - /// Generates proxy classes to access mod APIs through an arbitrary interface. + /// Generates proxy classes to access mod APIs through an arbitrary interface. /// Encapsulates monitoring and logging for the mod. - public ModRegistryHelper(string modID, ModRegistry registry, IProxyManager proxyManager, IMonitor monitor) + public ModRegistryHelper(string modID, ModRegistry registry, InterfaceProxyFactory proxyFactory, IMonitor monitor) : base(modID) { this.Registry = registry; - this.ProxyManager = proxyManager; + this.ProxyFactory = proxyFactory; this.Monitor = monitor; } @@ -95,9 +94,9 @@ namespace StardewModdingAPI.Framework.ModHelpers } // get API of type - if (api is TInterface castApi) - return castApi; - return this.ProxyManager.ObtainProxy(api, this.ModID, uniqueID); + return api is TInterface castApi + ? castApi + : this.ProxyFactory.CreateProxy(api, sourceModID: this.ModID, targetModID: uniqueID); } } } diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs new file mode 100644 index 00000000..40adde8e --- /dev/null +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs @@ -0,0 +1,42 @@ +using System.Reflection; +using System.Reflection.Emit; +using Nanoray.Pintail; + +namespace StardewModdingAPI.Framework.Reflection +{ + /// Generates proxy classes to access mod APIs through an arbitrary interface. + internal class InterfaceProxyFactory + { + /********* + ** Fields + *********/ + /// The underlying proxy type builder. + private readonly IProxyManager ProxyManager; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + public InterfaceProxyFactory() + { + AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run); + ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("StardewModdingAPI.Proxies"); + this.ProxyManager = new ProxyManager(moduleBuilder, new ProxyManagerConfiguration( + proxyPrepareBehavior: ProxyManagerProxyPrepareBehavior.Eager, + proxyObjectInterfaceMarking: ProxyObjectInterfaceMarking.Disabled + )); + } + + /// Create an API proxy. + /// The interface through which to access the API. + /// The API instance to access. + /// The unique ID of the mod consuming the API. + /// The unique ID of the mod providing the API. + public TInterface CreateProxy(object instance, string sourceModID, string targetModID) + where TInterface : class + { + return this.ProxyManager.ObtainProxy(instance, targetContext: targetModID, proxyContext: sourceModID); + } + } +} diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 8f810644..342d6415 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -48,8 +48,6 @@ using xTile.Display; using MiniMonoModHotfix = MonoMod.Utils.MiniMonoModHotfix; using PathUtilities = StardewModdingAPI.Toolkit.Utilities.PathUtilities; using SObject = StardewValley.Object; -using Nanoray.Pintail; -using System.Reflection.Emit; namespace StardewModdingAPI.Framework { @@ -1490,17 +1488,12 @@ namespace StardewModdingAPI.Framework { // init HashSet suppressUpdateChecks = new HashSet(this.Settings.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase); - AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run); - ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("StardewModdingAPI.Proxies"); - IProxyManager proxyManager = new ProxyManager(moduleBuilder, new ProxyManagerConfiguration( - proxyPrepareBehavior: ProxyManagerProxyPrepareBehavior.Eager, - proxyObjectInterfaceMarking: ProxyObjectInterfaceMarking.Disabled - )); + InterfaceProxyFactory proxyFactory = new(); // load mods foreach (IModMetadata mod in mods) { - if (!this.TryLoadMod(mod, mods, modAssemblyLoader, proxyManager, jsonHelper, contentCore, modDatabase, suppressUpdateChecks, out ModFailReason? failReason, out string errorPhrase, out string errorDetails)) + if (!this.TryLoadMod(mod, mods, modAssemblyLoader, proxyFactory, jsonHelper, contentCore, modDatabase, suppressUpdateChecks, out ModFailReason? failReason, out string errorPhrase, out string errorDetails)) { failReason ??= ModFailReason.LoadFailed; mod.SetStatus(ModMetadataStatus.Failed, failReason.Value, errorPhrase, errorDetails); @@ -1603,7 +1596,7 @@ namespace StardewModdingAPI.Framework /// The mod to load. /// The mods being loaded. /// Preprocesses and loads mod assemblies. - /// Generates proxy classes to access mod APIs through an arbitrary interface. + /// Generates proxy classes to access mod APIs through an arbitrary interface. /// The JSON helper with which to read mods' JSON files. /// The content manager to use for mod content. /// Handles access to SMAPI's internal mod metadata list. @@ -1612,7 +1605,7 @@ namespace StardewModdingAPI.Framework /// The user-facing reason phrase explaining why the mod couldn't be loaded (if applicable). /// More detailed details about the error intended for developers (if any). /// Returns whether the mod was successfully loaded. - private bool TryLoadMod(IModMetadata mod, IModMetadata[] mods, AssemblyLoader assemblyLoader, IProxyManager proxyManager, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase, HashSet suppressUpdateChecks, out ModFailReason? failReason, out string errorReasonPhrase, out string errorDetails) + private bool TryLoadMod(IModMetadata mod, IModMetadata[] mods, AssemblyLoader assemblyLoader, InterfaceProxyFactory proxyFactory, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase, HashSet suppressUpdateChecks, out ModFailReason? failReason, out string errorReasonPhrase, out string errorDetails) { errorDetails = null; @@ -1755,7 +1748,7 @@ namespace StardewModdingAPI.Framework IContentPackHelper contentPackHelper = new ContentPackHelper(manifest.UniqueID, new Lazy(GetContentPacks), CreateFakeContentPack); IDataHelper dataHelper = new DataHelper(manifest.UniqueID, mod.DirectoryPath, jsonHelper); IReflectionHelper reflectionHelper = new ReflectionHelper(manifest.UniqueID, mod.DisplayName, this.Reflection); - IModRegistry modRegistryHelper = new ModRegistryHelper(manifest.UniqueID, this.ModRegistry, proxyManager, monitor); + IModRegistry modRegistryHelper = new ModRegistryHelper(manifest.UniqueID, this.ModRegistry, proxyFactory, monitor); IMultiplayerHelper multiplayerHelper = new MultiplayerHelper(manifest.UniqueID, this.Multiplayer); modHelper = new ModHelper(manifest.UniqueID, mod.DirectoryPath, () => this.GetCurrentGameInstance().Input, events, contentHelper, contentPackHelper, commandHelper, dataHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper); -- cgit From b0d8b23c2c53ea3aafd60b0597a7562ac1708a42 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 5 Mar 2022 15:31:06 -0500 Subject: migrate more internal code to IAssetName (#766) --- src/SMAPI/Framework/ContentCoordinator.cs | 18 ++--- .../ContentManagers/BaseContentManager.cs | 54 ++++++------- .../ContentManagers/GameContentManager.cs | 88 +++++++++++----------- .../GameContentManagerForAssetPropagation.cs | 2 +- .../Framework/ContentManagers/IContentManager.cs | 12 +-- .../Framework/ContentManagers/ModContentManager.cs | 64 ++++++++-------- src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 8 +- src/SMAPI/Metadata/CoreAssetPropagator.cs | 34 ++++----- 8 files changed, 131 insertions(+), 149 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 0cdc0736..b255dae1 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -136,7 +136,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.CoreAssets = new CoreAssetPropagator(this.MainContentManager, contentManagerForAssetPropagation, this.Monitor, reflection, aggressiveMemoryOptimizations, this.ParseAssetName); this.LocaleCodes = new Lazy>(() => this.GetLocaleCodes(customLanguages: Enumerable.Empty())); } @@ -260,8 +260,8 @@ namespace StardewModdingAPI.Framework } /// Get whether this asset is mapped to a mod folder. - /// The asset key. - public bool IsManagedAssetKey(string key) + /// The asset name. + public bool IsManagedAssetKey(IAssetName key) { return key.StartsWith(this.ManagedPrefix); } @@ -269,9 +269,9 @@ namespace StardewModdingAPI.Framework /// Parse a managed SMAPI asset key which maps to a mod folder. /// The asset key. /// The unique name for the content manager which should load this asset. - /// The relative path within the mod folder. + /// The asset name within the mod folder. /// Returns whether the asset was parsed successfully. - public bool TryParseManagedAssetKey(string key, out string contentManagerID, out string relativePath) + public bool TryParseManagedAssetKey(string key, out string contentManagerID, out IAssetName relativePath) { contentManagerID = null; relativePath = null; @@ -285,7 +285,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 = parts[2]; + relativePath = this.ParseAssetName(parts[2]); return true; } @@ -299,8 +299,8 @@ namespace StardewModdingAPI.Framework /// Get a copy of an asset from a mod folder. /// The asset type. /// The unique name for the content manager which should load this asset. - /// The internal SMAPI asset key. - public T LoadManagedAsset(string contentManagerID, string relativePath) + /// The asset name within the mod folder. + public T LoadManagedAsset(string contentManagerID, IAssetName relativePath) { // get content manager IContentManager contentManager = this.ContentManagerLock.InReadLock(() => @@ -404,7 +404,7 @@ namespace StardewModdingAPI.Framework /// Get all loaded instances of an asset name. /// The asset name. [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "This method is provided for Content Patcher.")] - public IEnumerable GetLoadedValues(string assetName) + public IEnumerable GetLoadedValues(IAssetName assetName) { return this.ContentManagerLock.InReadLock(() => { diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index 26f0921d..3efc33bb 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -93,36 +93,30 @@ namespace StardewModdingAPI.Framework.ContentManagers } /// - public override T Load(string assetName) + [Obsolete("This method is implemented for the base game and should not be used directly. To load an asset from the underlying content manager directly, use " + nameof(BaseContentManager.RawLoad) + " instead.")] + public override T LoadBase(string assetName) { - return this.Load(assetName, this.Language, useCache: true); + return this.Load(assetName, LanguageCode.en); } /// - public override T Load(string assetName, LanguageCode language) + public override T Load(string assetName) { - return this.Load(assetName, language, useCache: true); + return this.Load(assetName, this.Language); } /// - public abstract T Load(string assetName, LocalizedContentManager.LanguageCode language, bool useCache); - - /// - [Obsolete("This method is implemented for the base game and should not be used directly. To load an asset from the underlying content manager directly, use " + nameof(BaseContentManager.RawLoad) + " instead.")] - public override T LoadBase(string assetName) + public override T Load(string assetName, LanguageCode language) { - return this.Load(assetName, LanguageCode.en, useCache: true); + IAssetName parsedName = this.Coordinator.ParseAssetName(assetName); + return this.Load(parsedName, language, useCache: true); } /// - public virtual void OnLocaleChanged() { } + public abstract T Load(IAssetName assetName, LanguageCode language, bool useCache); /// - [Pure] - public string NormalizePathSeparators(string path) - { - return this.Cache.NormalizePathSeparators(path); - } + public virtual void OnLocaleChanged() { } /// [SuppressMessage("ReSharper", "ParameterOnlyUsedForPreconditionCheck.Local", Justification = "Parameter is only used for assertion checks by design.")] @@ -154,11 +148,7 @@ namespace StardewModdingAPI.Framework.ContentManagers } /// - public bool IsLoaded(string assetName, LanguageCode language) - { - assetName = this.Cache.NormalizeKey(assetName); - return this.IsNormalizedKeyLoaded(assetName, language); - } + public abstract bool IsLoaded(IAssetName assetName, LanguageCode language); /**** ** Cache invalidation @@ -233,6 +223,14 @@ namespace StardewModdingAPI.Framework.ContentManagers /********* ** Private methods *********/ + /// Normalize path separators in a file path. For asset keys, see instead. + /// The file path to normalize. + [Pure] + protected string NormalizePathSeparators(string path) + { + return this.Cache.NormalizePathSeparators(path); + } + /// Load an asset file directly from the underlying content manager. /// The type of asset to load. /// The normalized asset key. @@ -250,26 +248,18 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The asset value. /// The language code for which to inject the asset. /// Whether to save the asset to the asset cache. - protected virtual void TrackAsset(string assetName, T value, LanguageCode language, bool useCache) + protected virtual void TrackAsset(IAssetName assetName, T value, LanguageCode language, bool useCache) { // track asset key if (value is Texture2D texture) - texture.Name = assetName; + texture.Name = assetName.Name; // cache asset if (useCache) - { - assetName = this.AssertAndNormalizeAssetName(assetName); - this.Cache[assetName] = value; - } + this.Cache[assetName.Name] = value; // avoid hard disposable references; see remarks on the field this.BaseDisposableReferences.Clear(); } - - /// Get whether an asset has already been loaded. - /// The normalized asset name. - /// The language to check. - protected abstract bool IsNormalizedKeyLoaded(string normalizedAssetName, LanguageCode language); } } diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index 0ca9e277..9f686f97 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -63,7 +63,7 @@ namespace StardewModdingAPI.Framework.ContentManagers } /// - public override T Load(string assetName, LocalizedContentManager.LanguageCode language, bool useCache) + public override T Load(IAssetName assetName, LanguageCode language, bool useCache) { // raise first-load callback if (GameContentManager.IsFirstLoad) @@ -73,49 +73,62 @@ namespace StardewModdingAPI.Framework.ContentManagers } // normalize asset name - IAssetName parsedName = this.Coordinator.ParseAssetName(assetName); - if (parsedName.LanguageCode.HasValue) - return this.Load(parsedName.BaseName, parsedName.LanguageCode.Value, useCache); + if (assetName.LanguageCode.HasValue) + return this.Load(this.Coordinator.ParseAssetName(assetName.BaseName), assetName.LanguageCode.Value, useCache); // get from cache - if (useCache && this.IsLoaded(parsedName.Name, language)) - return this.RawLoad(parsedName.Name, language, useCache: true); + if (useCache && this.IsLoaded(assetName, language)) + return this.RawLoad(assetName, language, useCache: true); // get managed asset - if (this.Coordinator.TryParseManagedAssetKey(parsedName.Name, out string contentManagerID, out string relativePath)) + if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string contentManagerID, out IAssetName relativePath)) { T managedAsset = this.Coordinator.LoadManagedAsset(contentManagerID, relativePath); - this.TrackAsset(parsedName.Name, managedAsset, language, useCache); + this.TrackAsset(assetName, managedAsset, language, useCache); return managedAsset; } // load asset T data; - if (this.AssetsBeingLoaded.Contains(parsedName.Name)) + if (this.AssetsBeingLoaded.Contains(assetName.Name)) { - this.Monitor.Log($"Broke loop while loading asset '{parsedName.Name}'.", LogLevel.Warn); + this.Monitor.Log($"Broke loop while loading asset '{assetName}'.", LogLevel.Warn); this.Monitor.Log($"Bypassing mod loaders for this asset. Stack trace:\n{Environment.StackTrace}"); - data = this.RawLoad(parsedName.Name, language, useCache); + data = this.RawLoad(assetName, language, useCache); } else { - data = this.AssetsBeingLoaded.Track(parsedName.Name, () => + data = this.AssetsBeingLoaded.Track(assetName.Name, () => { string locale = this.GetLocale(language); - IAssetInfo info = new AssetInfo(locale, parsedName, typeof(T), this.AssertAndNormalizeAssetName); + IAssetInfo info = new AssetInfo(locale, assetName, typeof(T), this.AssertAndNormalizeAssetName); IAssetData asset = this.ApplyLoader(info) - ?? new AssetDataForObject(info, this.RawLoad(parsedName.Name, language, useCache), this.AssertAndNormalizeAssetName); + ?? new AssetDataForObject(info, this.RawLoad(assetName, language, useCache), this.AssertAndNormalizeAssetName); asset = this.ApplyEditors(info, asset); return (T)asset.Data; }); } // update cache & return data - this.TrackAsset(parsedName.Name, data, language, useCache); + this.TrackAsset(assetName, data, language, useCache); return data; } + /// + public override bool IsLoaded(IAssetName assetName, LanguageCode language) + { + string cachedKey = null; + bool localized = + language != LanguageCode.en + && !this.Coordinator.IsManagedAssetKey(assetName) + && this.LocalizedAssetNames.TryGetValue(assetName.Name, out cachedKey); + + return localized + ? this.Cache.ContainsKey(cachedKey) + : this.Cache.ContainsKey(assetName.Name); + } + /// public override void OnLocaleChanged() { @@ -153,28 +166,13 @@ namespace StardewModdingAPI.Framework.ContentManagers ** Private methods *********/ /// - protected override bool IsNormalizedKeyLoaded(string normalizedAssetName, LanguageCode language) - { - string cachedKey = null; - bool localized = - language != LocalizedContentManager.LanguageCode.en - && !this.Coordinator.IsManagedAssetKey(normalizedAssetName) - && this.LocalizedAssetNames.TryGetValue(normalizedAssetName, out cachedKey); - - return localized - ? this.Cache.ContainsKey(cachedKey) - : this.Cache.ContainsKey(normalizedAssetName); - } - - /// - protected override void TrackAsset(string assetName, T value, LanguageCode language, bool useCache) + protected override void TrackAsset(IAssetName assetName, T value, LanguageCode language, bool useCache) { // handle explicit language in asset name { - IAssetName parsedName = this.Coordinator.ParseAssetName(assetName); - if (parsedName.LanguageCode.HasValue) + if (assetName.LanguageCode.HasValue) { - this.TrackAsset(parsedName.BaseName, value, parsedName.LanguageCode.Value, useCache); + this.TrackAsset(this.Coordinator.ParseAssetName(assetName.BaseName), value, assetName.LanguageCode.Value, useCache); return; } } @@ -188,16 +186,16 @@ namespace StardewModdingAPI.Framework.ContentManagers // doesn't change the instance stored in the cache, e.g. using `asset.ReplaceWith`. if (useCache) { - string translatedKey = $"{assetName}.{this.GetLocale(language)}"; + IAssetName translatedKey = new AssetName(assetName.Name, this.GetLocale(language), language); base.TrackAsset(assetName, value, language, useCache: true); - if (this.Cache.ContainsKey(translatedKey)) + if (this.Cache.ContainsKey(translatedKey.Name)) base.TrackAsset(translatedKey, value, language, useCache: true); // track whether the injected asset is translatable for is-loaded lookups - if (this.Cache.ContainsKey(translatedKey)) - this.LocalizedAssetNames[assetName] = translatedKey; - else if (this.Cache.ContainsKey(assetName)) - this.LocalizedAssetNames[assetName] = assetName; + if (this.Cache.ContainsKey(translatedKey.Name)) + this.LocalizedAssetNames[assetName.Name] = translatedKey.Name; + else if (this.Cache.ContainsKey(assetName.Name)) + this.LocalizedAssetNames[assetName.Name] = assetName.Name; else this.Monitor.Log($"Asset '{assetName}' could not be found in the cache immediately after injection.", LogLevel.Error); } @@ -209,32 +207,32 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The language code for which to load content. /// Whether to read/write the loaded asset to the asset cache. /// Derived from . - private T RawLoad(string assetName, LanguageCode language, bool useCache) + private T RawLoad(IAssetName assetName, LanguageCode language, bool useCache) { try { // use cached key - if (language == this.Language && this.LocalizedAssetNames.TryGetValue(assetName, out string cachedKey)) + if (language == this.Language && this.LocalizedAssetNames.TryGetValue(assetName.Name, out string cachedKey)) return base.RawLoad(cachedKey, useCache); // try translated key - if (language != LocalizedContentManager.LanguageCode.en) + if (language != LanguageCode.en) { string translatedKey = $"{assetName}.{this.GetLocale(language)}"; try { T obj = base.RawLoad(translatedKey, useCache); - this.LocalizedAssetNames[assetName] = translatedKey; + this.LocalizedAssetNames[assetName.Name] = translatedKey; return obj; } catch (ContentLoadException) { - this.LocalizedAssetNames[assetName] = assetName; + this.LocalizedAssetNames[assetName.Name] = assetName.Name; } } // try base asset - return base.RawLoad(assetName, useCache); + return base.RawLoad(assetName.Name, useCache); } catch (ContentLoadException ex) when (ex.InnerException is FileNotFoundException innerEx && innerEx.InnerException == null) { diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs index 61683ce6..206ece30 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs @@ -25,7 +25,7 @@ namespace StardewModdingAPI.Framework.ContentManagers : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, onLoadingFirstAsset, aggressiveMemoryOptimizations) { } /// - public override T Load(string assetName, LanguageCode language, bool useCache) + public override T Load(IAssetName assetName, LanguageCode language, bool useCache) { T data = base.Load(assetName, language, useCache); diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs index ba7dbc06..fe0519b6 100644 --- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics.Contracts; using Microsoft.Xna.Framework.Content; using StardewModdingAPI.Framework.Exceptions; using StardewValley; @@ -31,15 +30,10 @@ namespace StardewModdingAPI.Framework.ContentManagers *********/ /// Load an asset that has been processed by the content pipeline. /// The type of asset to load. - /// The asset path relative to the loader root directory, not including the .xnb extension. + /// The asset name relative to the loader root directory. /// The language code for which to load content. /// Whether to read/write the loaded asset to the asset cache. - T Load(string assetName, LocalizedContentManager.LanguageCode language, bool useCache); - - /// Normalize path separators in a file path. For asset keys, see instead. - /// The file path to normalize. - [Pure] - string NormalizePathSeparators(string path); + T Load(IAssetName assetName, LocalizedContentManager.LanguageCode language, bool useCache); /// Assert that the given key has a valid format and return a normalized form consistent with the underlying cache. /// The asset key to check. @@ -56,7 +50,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Get whether the content manager has already loaded and cached the given asset. /// The asset path relative to the loader root directory, not including the .xnb extension. /// The language. - bool IsLoaded(string assetName, LocalizedContentManager.LanguageCode language); + bool IsLoaded(IAssetName assetName, LocalizedContentManager.LanguageCode language); /// Purge matched assets from the cache. /// Matches the asset keys to invalidate. diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 2c47f14c..267146ad 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -66,21 +66,19 @@ namespace StardewModdingAPI.Framework.ContentManagers /// public override T Load(string assetName) { - return this.Load(assetName, this.DefaultLanguage, useCache: false); + return this.Load(assetName, this.DefaultLanguage); } /// public override T Load(string assetName, LanguageCode language) { - return this.Load(assetName, language, useCache: false); + IAssetName parsedName = this.Coordinator.ParseAssetName(assetName); + return this.Load(parsedName, language, useCache: false); } /// - public override T Load(string assetName, LanguageCode language, bool useCache) + public override T Load(IAssetName assetName, LanguageCode language, bool useCache) { - // normalize key - IAssetName parsedName = this.Coordinator.ParseAssetName(assetName); - // disable caching // This is necessary to avoid assets being shared between content managers, which can // cause changes to an asset through one content manager affecting the same asset in @@ -96,21 +94,21 @@ namespace StardewModdingAPI.Framework.ContentManagers // resolve managed asset key { - if (this.Coordinator.TryParseManagedAssetKey(parsedName.Name, out string contentManagerID, out string relativePath)) + 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 '{parsedName}' through content manager '{this.Name}' for a different mod."); - parsedName = this.Coordinator.ParseAssetName(relativePath); + throw new SContentLoadException($"Can't load managed asset key '{assetName}' through content manager '{this.Name}' for a different mod."); + assetName = relativePath; } } // get local asset - SContentLoadException GetContentError(string reasonPhrase) => new SContentLoadException($"Failed loading asset '{parsedName}' from {this.Name}: {reasonPhrase}"); + SContentLoadException GetContentError(string reasonPhrase) => new($"Failed loading asset '{assetName}' from {this.Name}: {reasonPhrase}"); T asset; try { // get file - FileInfo file = this.GetModFile(parsedName.Name); + FileInfo file = this.GetModFile(assetName.Name); if (!file.Exists) throw GetContentError("the specified path doesn't exist."); @@ -122,7 +120,7 @@ namespace StardewModdingAPI.Framework.ContentManagers { // 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. - string loadName = parsedName.Name[..^".xnb".Length]; + string loadName = assetName.Name[..^".xnb".Length]; // load asset asset = this.RawLoad(loadName, useCache: false); @@ -177,8 +175,8 @@ namespace StardewModdingAPI.Framework.ContentManagers // fetch & cache FormatManager formatManager = FormatManager.Instance; Map map = formatManager.LoadMap(file.FullName); - map.assetPath = parsedName.Name; - this.FixTilesheetPaths(map, relativeMapPath: parsedName.Name, fixEagerPathPrefixes: false); + map.assetPath = assetName.Name; + this.FixTilesheetPaths(map, relativeMapPath: assetName.Name, fixEagerPathPrefixes: false); asset = (T)(object)map; } break; @@ -189,14 +187,20 @@ namespace StardewModdingAPI.Framework.ContentManagers } catch (Exception ex) when (!(ex is SContentLoadException)) { - throw new SContentLoadException($"The content manager failed loading content asset '{parsedName}' from {this.Name}.", ex); + throw new SContentLoadException($"The content manager failed loading content asset '{assetName}' from {this.Name}.", ex); } // track & return asset - this.TrackAsset(parsedName.Name, asset, language, useCache); + this.TrackAsset(assetName, asset, language, useCache); return asset; } + /// + public override bool IsLoaded(IAssetName assetName, LanguageCode language) + { + return this.Cache.ContainsKey(assetName.Name); + } + /// public override LocalizedContentManager CreateTemporary() { @@ -206,23 +210,19 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Get the underlying key in the game's content cache for an asset. This does not validate whether the asset exists. /// The local path to a content file relative to the mod folder. /// The is empty or contains invalid characters. - public string GetInternalAssetKey(string key) + public IAssetName GetInternalAssetKey(string key) { FileInfo file = this.GetModFile(key); string relativePath = PathUtilities.GetRelativePath(this.RootDirectory, file.FullName); - return Path.Combine(this.Name, relativePath); + string internalKey = Path.Combine(this.Name, relativePath); + + return this.Coordinator.ParseAssetName(internalKey); } /********* ** Private methods *********/ - /// - protected override bool IsNormalizedKeyLoaded(string normalizedAssetName, LanguageCode language) - { - return this.Cache.ContainsKey(normalizedAssetName); - } - /// Get a file from the mod folder. /// The asset path relative to the content folder. private FileInfo GetModFile(string path) @@ -304,15 +304,15 @@ namespace StardewModdingAPI.Framework.ContentManagers // load best match try { - if (!this.TryGetTilesheetAssetName(relativeMapFolder, imageSource, out string assetName, out string error)) + if (!this.TryGetTilesheetAssetName(relativeMapFolder, imageSource, out IAssetName assetName, out string error)) throw new SContentLoadException($"{errorPrefix} {error}"); - if (assetName != tilesheet.ImageSource) + if (!assetName.IsEquivalentTo(tilesheet.ImageSource)) this.Monitor.VerboseLog($" Mapped tilesheet '{tilesheet.ImageSource}' to '{assetName}'."); - tilesheet.ImageSource = assetName; + tilesheet.ImageSource = assetName.Name; } - catch (Exception ex) when (!(ex is SContentLoadException)) + catch (Exception ex) when (ex is not SContentLoadException) { throw new SContentLoadException($"{errorPrefix} The tilesheet couldn't be loaded.", ex); } @@ -326,7 +326,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// A message indicating why the file couldn't be loaded. /// Returns whether the asset name was found. /// See remarks on . - private bool TryGetTilesheetAssetName(string modRelativeMapFolder, string relativePath, out string assetName, out string error) + private bool TryGetTilesheetAssetName(string modRelativeMapFolder, string relativePath, out IAssetName assetName, out string error) { assetName = null; error = null; @@ -334,7 +334,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // nothing to do if (string.IsNullOrWhiteSpace(relativePath)) { - assetName = relativePath; + assetName = null; return true; } @@ -358,7 +358,7 @@ namespace StardewModdingAPI.Framework.ContentManagers } // get from game assets - string contentKey = this.GetContentKeyForTilesheetImageSource(relativePath); + IAssetName contentKey = this.Coordinator.ParseAssetName(this.GetContentKeyForTilesheetImageSource(relativePath)); try { this.GameContentManager.Load(contentKey, this.Language, useCache: true); // no need to bypass cache here, since we're not storing the asset @@ -374,7 +374,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // if the content file doesn't exist, that doesn't mean the error here is a // content-not-found error. Unfortunately XNA doesn't provide a good way to // detect the error type. - if (this.GetContentFolderFileExists(contentKey)) + if (this.GetContentFolderFileExists(contentKey.Name)) throw; } diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index a01248a8..4e522c8d 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -80,16 +80,18 @@ namespace StardewModdingAPI.Framework.ModHelpers /// public T Load(string key, ContentSource source = ContentSource.ModFolder) { + IAssetName assetName = this.ContentCore.ParseAssetName(key); + try { this.AssertAndNormalizeAssetName(key); switch (source) { case ContentSource.GameContent: - return this.GameContentManager.Load(key, this.CurrentLocaleConstant, useCache: false); + return this.GameContentManager.Load(assetName, this.CurrentLocaleConstant, useCache: false); case ContentSource.ModFolder: - return this.ModContentManager.Load(key, Constants.DefaultLanguage, useCache: false); + return this.ModContentManager.Load(assetName, Constants.DefaultLanguage, useCache: false); default: throw new SContentLoadException($"{this.ModName} failed loading content asset '{key}' from {source}: unknown content source '{source}'."); @@ -117,7 +119,7 @@ namespace StardewModdingAPI.Framework.ModHelpers return this.GameContentManager.AssertAndNormalizeAssetName(key); case ContentSource.ModFolder: - return this.ModContentManager.GetInternalAssetKey(key); + return this.ModContentManager.GetInternalAssetKey(key).Name; default: throw new NotSupportedException($"Unknown content source '{source}'."); diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index e7fac578..f645470e 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -45,8 +45,8 @@ namespace StardewModdingAPI.Metadata /// Whether to enable more aggressive memory optimizations. private readonly bool AggressiveMemoryOptimizations; - /// Normalizes an asset key to match the cache key and assert that it's valid. - private readonly Func AssertAndNormalizeAssetName; + /// Parse a raw asset name. + private readonly Func ParseAssetName; /// Optimized bucket categories for batch reloading assets. private enum AssetBucket @@ -71,15 +71,15 @@ namespace StardewModdingAPI.Metadata /// Writes messages to the console. /// Simplifies access to private code. /// Whether to enable more aggressive memory optimizations. - public CoreAssetPropagator(LocalizedContentManager mainContent, GameContentManagerForAssetPropagation disposableContent, IMonitor monitor, Reflector reflection, bool aggressiveMemoryOptimizations) + /// Parse a raw asset name. + public CoreAssetPropagator(LocalizedContentManager mainContent, GameContentManagerForAssetPropagation disposableContent, IMonitor monitor, Reflector reflection, bool aggressiveMemoryOptimizations, Func parseAssetName) { this.MainContentManager = mainContent; this.DisposableContentManager = disposableContent; this.Monitor = monitor; this.Reflection = reflection; this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations; - - this.AssertAndNormalizeAssetName = disposableContent.AssertAndNormalizeAssetName; + this.ParseAssetName = parseAssetName; } /// Reload one of the game's core assets (if applicable). @@ -955,13 +955,12 @@ namespace StardewModdingAPI.Metadata private void ReloadNpcSprites(IEnumerable keys, IDictionary propagated) { // get NPCs - IDictionary lookup = keys.ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase); var characters = ( from npc in this.GetCharacters() - let key = this.NormalizeAssetNameIgnoringEmpty(npc.Sprite?.Texture?.Name) - where key != null && lookup.ContainsKey(key) - select new { Npc = npc, AssetName = lookup[key] } + let key = this.ParseAssetNameOrNull(npc.Sprite?.Texture?.Name) + where key != null && propagated.ContainsKey(key) + select new { Npc = npc, AssetName = key } ) .ToArray(); if (!characters.Any()) @@ -981,26 +980,25 @@ namespace StardewModdingAPI.Metadata private void ReloadNpcPortraits(IEnumerable keys, IDictionary propagated) { // get NPCs - IDictionary lookup = keys.ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase); var characters = ( from npc in this.GetCharacters() where npc.isVillager() - let key = this.NormalizeAssetNameIgnoringEmpty(npc.Portrait?.Name) - where key != null && lookup.ContainsKey(key) - select new { Npc = npc, AssetName = lookup[key] } + let key = this.ParseAssetNameOrNull(npc.Portrait?.Name) + where key != null && propagated.ContainsKey(key) + select new { Npc = npc, AssetName = key } ) .ToList(); // special case: Gil is a private NPC field on the AdventureGuild class (only used for the portrait) { - string gilKey = this.NormalizeAssetNameIgnoringEmpty("Portraits/Gil"); - if (lookup.TryGetValue(gilKey, out IAssetName assetName)) + IAssetName gilKey = this.ParseAssetName("Portraits/Gil"); + if (propagated.ContainsKey(gilKey)) { GameLocation adventureGuild = Game1.getLocationFromName("AdventureGuild"); if (adventureGuild != null) - characters.Add(new { Npc = this.Reflection.GetField(adventureGuild, "Gil").GetValue(), AssetName = assetName }); + characters.Add(new { Npc = this.Reflection.GetField(adventureGuild, "Gil").GetValue(), AssetName = gilKey }); } } @@ -1234,12 +1232,12 @@ namespace StardewModdingAPI.Metadata /// Normalize an asset key to match the cache key and assert that it's valid, but don't raise an error for null or empty values. /// The asset key to normalize. - private string NormalizeAssetNameIgnoringEmpty(string path) + private IAssetName ParseAssetNameOrNull(string path) { if (string.IsNullOrWhiteSpace(path)) return null; - return this.AssertAndNormalizeAssetName(path); + return this.ParseAssetName(path); } /// Get the segments in a path (e.g. 'a/b' is 'a' and 'b'). -- cgit From 4c64f9f644c2349d2ca2407ce3aff736ba3fc354 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 26 Mar 2022 14:07:16 -0400 Subject: rewrite content loading to allow handling locale variants (#766, #786, #812) The game's content pipeline automatically loads localized variants if present. For example, it will try to load "Maps/cave.fr-FR", then "Maps/cave_international", then "Maps/cave". The old content API obfuscates this logic and treats them as interchangeable, which causes edge cases like bundle corruption (#812). This commit rewrites the loading logic to match the game logic when using the new content events, while maintaining the legacy behavior for the old IAssetLoader/IAssetEditor interfaces that'll be removed in SMAPI 4.0.0. --- docs/release-notes.md | 1 + src/SMAPI/Framework/ContentCoordinator.cs | 89 +++++++++++++--- .../ContentManagers/BaseContentManager.cs | 71 +++++++++++-- .../ContentManagers/GameContentManager.cs | 114 ++------------------- .../GameContentManagerForAssetPropagation.cs | 4 +- .../Framework/ContentManagers/IContentManager.cs | 14 ++- .../Framework/ContentManagers/ModContentManager.cs | 32 ++---- src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 4 +- 8 files changed, 169 insertions(+), 160 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/docs/release-notes.md b/docs/release-notes.md index a8f8ccfd..98392c17 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -10,6 +10,7 @@ * Added `--use-current-shell` to avoid opening a separate terminal window. * Fixed `--no-terminal` still opening a terminal window, even if nothing is logged to it (thanks to Ryhon0!). * Fixed warning text when a mod causes an asset load conflict with itself. + * Fixed support for `_international` content assets (used in the movie theater). * For mod authors: * Added [content events](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events#Content), which will replace `IAssetEditor` and `IAssetLoader` in SMAPI 4.0.0. diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 8e7465de..a4d29068 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -61,9 +61,6 @@ namespace StardewModdingAPI.Framework /// The loaded content managers (including the ). private readonly List ContentManagers = new(); - /// The language code for language-agnostic mod assets. - private readonly LocalizedContentManager.LanguageCode DefaultLanguage = Constants.DefaultLanguage; - /// Whether the content coordinator has been disposed. private bool IsDisposed; @@ -350,7 +347,7 @@ namespace StardewModdingAPI.Framework throw new InvalidOperationException($"The '{contentManagerID}' prefix isn't handled by any mod."); // get fresh asset - return contentManager.Load(relativePath, this.DefaultLanguage, useCache: false); + return contentManager.LoadExact(relativePath, useCache: false); } /// Purge matched assets from the cache. @@ -467,9 +464,9 @@ namespace StardewModdingAPI.Framework return this.ContentManagerLock.InReadLock(() => { List values = new List(); - foreach (IContentManager content in this.ContentManagers.Where(p => !p.IsNamespaced && p.IsLoaded(assetName, p.Language))) + foreach (IContentManager content in this.ContentManagers.Where(p => !p.IsNamespaced && p.IsLoaded(assetName))) { - object value = content.Load(assetName, this.Language, useCache: true); + object value = content.LoadExact(assetName, useCache: true); values.Add(value); } return values; @@ -582,6 +579,8 @@ namespace StardewModdingAPI.Framework /// The asset info to load or edit. private IEnumerable GetAssetOperationsWithoutCache(IAssetInfo info) { + IAssetInfo legacyInfo = this.GetLegacyAssetInfo(info); + // new content API foreach (AssetOperationGroup group in this.RequestAssetOperations(info)) yield return group; @@ -592,12 +591,12 @@ namespace StardewModdingAPI.Framework // check if loader applies try { - if (!loader.Data.CanLoad(info)) + if (!loader.Data.CanLoad(legacyInfo)) continue; } catch (Exception ex) { - loader.Mod.LogAsMod($"Mod failed when checking whether it could load asset '{info.Name}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); + loader.Mod.LogAsMod($"Mod failed when checking whether it could load asset '{legacyInfo.Name}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); continue; } @@ -610,7 +609,9 @@ namespace StardewModdingAPI.Framework mod: loader.Mod, priority: AssetLoadPriority.Exclusive, onBehalfOf: null, - getData: assetInfo => loader.Data.Load(assetInfo) + getData: assetInfo => loader.Data.Load( + this.GetLegacyAssetInfo(assetInfo) + ) ) }, editOperations: Array.Empty() @@ -623,12 +624,12 @@ namespace StardewModdingAPI.Framework // check if editor applies try { - if (!editor.Data.CanEdit(info)) + if (!editor.Data.CanEdit(legacyInfo)) continue; } catch (Exception ex) { - editor.Mod.LogAsMod($"Mod crashed when checking whether it could edit asset '{info.Name}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); + editor.Mod.LogAsMod($"Mod crashed when checking whether it could edit asset '{legacyInfo.Name}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error); continue; } @@ -642,11 +643,75 @@ namespace StardewModdingAPI.Framework mod: editor.Mod, priority: AssetEditPriority.Default, onBehalfOf: null, - applyEdit: assetData => editor.Data.Edit(assetData) + applyEdit: assetData => editor.Data.Edit( + this.GetLegacyAssetData(assetData) + ) ) } ); } } + + /// Get an asset info compatible with legacy and instances, which always expect the base name. + /// The asset info. + private IAssetInfo GetLegacyAssetInfo(IAssetInfo asset) + { + if (!this.TryGetLegacyAssetName(asset.Name, out IAssetName legacyName)) + return asset; + + return new AssetInfo( + locale: null, + assetName: legacyName, + type: asset.DataType, + getNormalizedPath: this.MainContentManager.AssertAndNormalizeAssetName + ); + } + + /// Get an asset data compatible with legacy and instances, which always expect the base name. + /// The asset data. + private IAssetData GetLegacyAssetData(IAssetData asset) + { + if (!this.TryGetLegacyAssetName(asset.Name, out IAssetName legacyName)) + return asset; + + return asset.Name.LocaleCode == null + ? asset + : new AssetDataForObject( + locale: null, + assetName: legacyName, + data: asset.Data, + getNormalizedPath: this.MainContentManager.AssertAndNormalizeAssetName + ); + } + + /// Get an asset name compatible with legacy and instances, which always expect the base name. + /// The asset name to map. + /// The legacy asset name (or the if no change is needed). + /// Returns whether any change is needed for legacy compatibility. + private bool TryGetLegacyAssetName(IAssetName asset, out IAssetName newAsset) + { + // strip _international suffix + const string internationalSuffix = "_international"; + if (asset.Name.EndsWith(internationalSuffix)) + { + newAsset = new AssetName( + baseName: asset.Name[..^internationalSuffix.Length], + localeCode: null, + languageCode: null + ); + return true; + } + + // else strip locale + if (asset.LocaleCode != null) + { + newAsset = new AssetName(asset.BaseName, null, null); + return true; + } + + // else no change needed + newAsset = asset; + return false; + } } } diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index 030c60a7..b1ace259 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -5,6 +5,7 @@ using System.Diagnostics.Contracts; using System.Globalization; using System.IO; using System.Linq; +using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.Exceptions; @@ -32,6 +33,9 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Whether to enable more aggressive memory optimizations. protected readonly bool AggressiveMemoryOptimizations; + /// Whether to automatically try resolving keys to a localized form if available. + protected bool TryLocalizeKeys = true; + /// Whether the content coordinator has been disposed. private bool IsDisposed; @@ -39,7 +43,7 @@ namespace StardewModdingAPI.Framework.ContentManagers private readonly Action OnDisposing; /// A list of disposable assets. - private readonly List> Disposables = new List>(); + private readonly List> Disposables = new(); /// The disposable assets tracked by the base content manager. /// This should be kept empty to avoid keeping disposable assets referenced forever, which prevents garbage collection when they're unused. Disposable assets are tracked by instead, which avoids a hard reference. @@ -115,11 +119,51 @@ namespace StardewModdingAPI.Framework.ContentManagers public override T Load(string assetName, LanguageCode language) { IAssetName parsedName = this.Coordinator.ParseAssetName(assetName); - return this.Load(parsedName, language, useCache: true); + return this.LoadLocalized(parsedName, language, useCache: true); } /// - public abstract T Load(IAssetName assetName, LanguageCode language, bool useCache); + public T LoadLocalized(IAssetName assetName, LanguageCode language, bool useCache) + { + // ignore locale in English (or if disabled) + if (!this.TryLocalizeKeys || language == LocalizedContentManager.LanguageCode.en) + return this.LoadExact(assetName, useCache: useCache); + + // check for localized asset + if (!LocalizedContentManager.localizedAssetNames.TryGetValue(assetName.Name, out _)) + { + string localeCode = this.LanguageCodeString(language); + IAssetName localizedName = new AssetName(baseName: assetName.BaseName, localeCode: localeCode, languageCode: language); + + try + { + this.LoadExact(localizedName, useCache: useCache); + LocalizedContentManager.localizedAssetNames[assetName.Name] = localizedName.Name; + } + catch (ContentLoadException) + { + localizedName = new AssetName(assetName.BaseName + "_international", null, null); + try + { + this.LoadExact(localizedName, useCache: useCache); + LocalizedContentManager.localizedAssetNames[assetName.Name] = localizedName.Name; + } + catch (ContentLoadException) + { + LocalizedContentManager.localizedAssetNames[assetName.Name] = assetName.Name; + } + } + } + + // use cached key + string rawName = LocalizedContentManager.localizedAssetNames[assetName.Name]; + if (assetName.Name != rawName) + assetName = this.Coordinator.ParseAssetName(assetName.Name); + return this.LoadExact(assetName, useCache: useCache); + } + + /// + public abstract T LoadExact(IAssetName assetName, bool useCache); /// public virtual void OnLocaleChanged() { } @@ -154,7 +198,11 @@ namespace StardewModdingAPI.Framework.ContentManagers } /// - public abstract bool IsLoaded(IAssetName assetName, LanguageCode language); + public bool IsLoaded(IAssetName assetName) + { + return this.Cache.ContainsKey(assetName.Name); + } + /**** ** Cache invalidation @@ -241,26 +289,29 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The type of asset to load. /// The normalized asset key. /// Whether to read/write the loaded asset to the asset cache. - protected virtual T RawLoad(string assetName, bool useCache) + protected virtual T RawLoad(IAssetName assetName, bool useCache) { return useCache - ? base.LoadBase(assetName) - : base.ReadAsset(assetName, disposable => this.Disposables.Add(new WeakReference(disposable))); + ? base.LoadBase(assetName.Name) + : base.ReadAsset(assetName.Name, disposable => this.Disposables.Add(new WeakReference(disposable))); } /// Add tracking data to an asset and add it to the cache. /// The type of asset to inject. /// The asset path relative to the loader root directory, not including the .xnb extension. /// The asset value. - /// The language code for which to inject the asset. /// Whether to save the asset to the asset cache. - protected virtual void TrackAsset(IAssetName assetName, T value, LanguageCode language, bool useCache) + protected virtual void TrackAsset(IAssetName assetName, T value, bool useCache) { // track asset key if (value is Texture2D texture) texture.Name = assetName.Name; - // cache asset + // save to cache + // Note: even if the asset was loaded and cached right before this method was called, + // we need to fully re-inject it because a mod editor may have changed the asset in a + // way that doesn't change the instance stored in the cache, e.g. using + // `asset.ReplaceWith`. if (useCache) this.Cache[assetName.Name] = value; diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index a121f4c0..500c0191 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -4,11 +4,9 @@ using System.Globalization; using System.IO; using System.Linq; using System.Reflection; -using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Events; using StardewModdingAPI.Framework.Content; -using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Utilities; using StardewModdingAPI.Internal; @@ -91,7 +89,7 @@ namespace StardewModdingAPI.Framework.ContentManagers } /// - public override T Load(IAssetName assetName, LanguageCode language, bool useCache) + public override T LoadExact(IAssetName assetName, bool useCache) { // raise first-load callback if (GameContentManager.IsFirstLoad) @@ -100,19 +98,15 @@ namespace StardewModdingAPI.Framework.ContentManagers this.OnLoadingFirstAsset(); } - // normalize asset name - if (assetName.LanguageCode.HasValue) - return this.Load(this.Coordinator.ParseAssetName(assetName.BaseName), assetName.LanguageCode.Value, useCache); - // get from cache - if (useCache && this.IsLoaded(assetName, language)) - return this.RawLoad(assetName, language, useCache: true); + if (useCache && this.IsLoaded(assetName)) + return this.RawLoad(assetName, useCache: true); // get managed asset if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string contentManagerID, out IAssetName relativePath)) { T managedAsset = this.Coordinator.LoadManagedAsset(contentManagerID, relativePath); - this.TrackAsset(assetName, managedAsset, language, useCache); + this.TrackAsset(assetName, managedAsset, useCache); return managedAsset; } @@ -122,44 +116,29 @@ namespace StardewModdingAPI.Framework.ContentManagers { this.Monitor.Log($"Broke loop while loading asset '{assetName}'.", LogLevel.Warn); this.Monitor.Log($"Bypassing mod loaders for this asset. Stack trace:\n{Environment.StackTrace}"); - data = this.RawLoad(assetName, language, useCache); + data = this.RawLoad(assetName, useCache); } else { data = this.AssetsBeingLoaded.Track(assetName.Name, () => { - string locale = this.GetLocale(language); - IAssetInfo info = new AssetInfo(locale, assetName, typeof(T), this.AssertAndNormalizeAssetName); + IAssetInfo info = new AssetInfo(assetName.LocaleCode, assetName, typeof(T), this.AssertAndNormalizeAssetName); IAssetData asset = this.ApplyLoader(info) - ?? new AssetDataForObject(info, this.RawLoad(assetName, language, useCache), this.AssertAndNormalizeAssetName); + ?? new AssetDataForObject(info, this.RawLoad(assetName, useCache), this.AssertAndNormalizeAssetName); asset = this.ApplyEditors(info, asset); return (T)asset.Data; }); } // update cache - this.TrackAsset(assetName, data, language, useCache); + this.TrackAsset(assetName, data, useCache); // raise event & return data this.OnAssetLoaded(this, assetName); return data; } - /// - public override bool IsLoaded(IAssetName assetName, LanguageCode language) - { - string cachedKey = null; - bool localized = - language != LanguageCode.en - && !this.Coordinator.IsManagedAssetKey(assetName) - && this.LocalizedAssetNames.TryGetValue(assetName.Name, out cachedKey); - - return localized - ? this.Cache.ContainsKey(cachedKey) - : this.Cache.ContainsKey(assetName.Name); - } - /// public override void OnLocaleChanged() { @@ -175,7 +154,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // invalidate translatable assets string[] invalidated = this - .InvalidateCache((key, type) => + .InvalidateCache((key, _) => removeAssetNames.Contains(key) || removeAssetNames.Contains(this.Coordinator.ParseAssetName(key).BaseName) ) @@ -196,81 +175,6 @@ namespace StardewModdingAPI.Framework.ContentManagers /********* ** Private methods *********/ - /// - protected override void TrackAsset(IAssetName assetName, T value, LanguageCode language, bool useCache) - { - // handle explicit language in asset name - { - if (assetName.LanguageCode.HasValue) - { - this.TrackAsset(this.Coordinator.ParseAssetName(assetName.BaseName), value, assetName.LanguageCode.Value, useCache); - return; - } - } - - // save to cache - // Note: even if the asset was loaded and cached right before this method was called, - // we need to fully re-inject it here for two reasons: - // 1. So we can look up an asset by its base or localized key (the game/XNA logic - // only caches by the most specific key). - // 2. Because a mod asset loader/editor may have changed the asset in a way that - // doesn't change the instance stored in the cache, e.g. using `asset.ReplaceWith`. - if (useCache) - { - IAssetName translatedKey = new AssetName(assetName.Name, this.GetLocale(language), language); - base.TrackAsset(assetName, value, language, useCache: true); - if (this.Cache.ContainsKey(translatedKey.Name)) - base.TrackAsset(translatedKey, value, language, useCache: true); - - // track whether the injected asset is translatable for is-loaded lookups - if (this.Cache.ContainsKey(translatedKey.Name)) - this.LocalizedAssetNames[assetName.Name] = translatedKey.Name; - else if (this.Cache.ContainsKey(assetName.Name)) - this.LocalizedAssetNames[assetName.Name] = assetName.Name; - else - this.Monitor.Log($"Asset '{assetName}' could not be found in the cache immediately after injection.", LogLevel.Error); - } - } - - /// Load an asset file directly from the underlying content manager. - /// The type of asset to load. - /// The normalized asset key. - /// The language code for which to load content. - /// Whether to read/write the loaded asset to the asset cache. - /// Derived from . - private T RawLoad(IAssetName assetName, LanguageCode language, bool useCache) - { - try - { - // use cached key - if (language == this.Language && this.LocalizedAssetNames.TryGetValue(assetName.Name, out string cachedKey)) - return base.RawLoad(cachedKey, useCache); - - // try translated key - if (language != LanguageCode.en) - { - string translatedKey = $"{assetName}.{this.GetLocale(language)}"; - try - { - T obj = base.RawLoad(translatedKey, useCache); - this.LocalizedAssetNames[assetName.Name] = translatedKey; - return obj; - } - catch (ContentLoadException) - { - this.LocalizedAssetNames[assetName.Name] = assetName.Name; - } - } - - // try base asset - return base.RawLoad(assetName.Name, useCache); - } - catch (ContentLoadException ex) when (ex.InnerException is FileNotFoundException { InnerException: null }) - { - throw new SContentLoadException($"Error loading \"{assetName}\": it isn't in the Content folder and no mod provided it."); - } - } - /// Load the initial asset from the registered loaders. /// The basic asset metadata. /// Returns the loaded asset metadata, or null if no loader matched. diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs index 847e2ce1..3f7188da 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs @@ -25,9 +25,9 @@ namespace StardewModdingAPI.Framework.ContentManagers : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, onLoadingFirstAsset, onAssetLoaded, aggressiveMemoryOptimizations) { } /// - public override T Load(IAssetName assetName, LanguageCode language, bool useCache) + public override T LoadExact(IAssetName assetName, bool useCache) { - T data = base.Load(assetName, language, useCache); + T data = base.LoadExact(assetName, useCache); if (data is Texture2D texture) texture.Tag = this.Tag; diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs index 6d71472f..4de9a8c3 100644 --- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs @@ -32,12 +32,17 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The normalized asset name. bool DoesAssetExist(IAssetName assetName); - /// Load an asset that has been processed by the content pipeline. + /// Load an asset through the content pipeline, using a localized variant of the if available. /// The type of asset to load. /// The asset name relative to the loader root directory. - /// The language code for which to load content. /// Whether to read/write the loaded asset to the asset cache. - T Load(IAssetName assetName, LocalizedContentManager.LanguageCode language, bool useCache); + T LoadLocalized(IAssetName assetName, LocalizedContentManager.LanguageCode language, bool useCache); + + /// Load an asset through the content pipeline, using the exact asset name without checking for localized variants. + /// The type of asset to load. + /// The asset name relative to the loader root directory. + /// Whether to read/write the loaded asset to the asset cache. + T LoadExact(IAssetName assetName, bool useCache); /// Assert that the given key has a valid format and return a normalized form consistent with the underlying cache. /// The asset key to check. @@ -53,8 +58,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Get whether the content manager has already loaded and cached the given asset. /// The asset path relative to the loader root directory, not including the .xnb extension. - /// The language. - bool IsLoaded(IAssetName assetName, LocalizedContentManager.LanguageCode language); + bool IsLoaded(IAssetName assetName); /// Purge matched assets from the cache. /// Matches the asset keys to invalidate. diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 90836fcf..375b5e0e 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -61,6 +61,8 @@ namespace StardewModdingAPI.Framework.ContentManagers this.GameContentManager = gameContentManager; this.JsonHelper = jsonHelper; this.ModName = modName; + + this.TryLocalizeKeys = false; } /// @@ -80,14 +82,7 @@ namespace StardewModdingAPI.Framework.ContentManagers } /// - public override T Load(string assetName, LanguageCode language) - { - IAssetName parsedName = this.Coordinator.ParseAssetName(assetName); - return this.Load(parsedName, language, useCache: false); - } - - /// - public override T Load(IAssetName assetName, LanguageCode language, bool useCache) + public override T LoadExact(IAssetName assetName, bool useCache) { // disable caching // This is necessary to avoid assets being shared between content managers, which can @@ -97,11 +92,6 @@ namespace StardewModdingAPI.Framework.ContentManagers if (useCache) throw new InvalidOperationException("Mod content managers don't support asset caching."); - // disable language handling - // Mod files don't support automatic translation logic, so this should never happen. - if (language != this.DefaultLanguage) - throw new InvalidOperationException("Localized assets aren't supported by the mod content manager."); - // resolve managed asset key { if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string contentManagerID, out IAssetName relativePath)) @@ -130,14 +120,14 @@ namespace StardewModdingAPI.Framework.ContentManagers { // 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. - string loadName = assetName.Name[..^".xnb".Length]; + IAssetName loadName = this.Coordinator.ParseAssetName(assetName.Name[..^".xnb".Length]); // load asset asset = this.RawLoad(loadName, useCache: false); if (asset is Map map) { - map.assetPath = loadName; - this.FixTilesheetPaths(map, relativeMapPath: loadName, fixEagerPathPrefixes: true); + map.assetPath = loadName.Name; + this.FixTilesheetPaths(map, relativeMapPath: loadName.Name, fixEagerPathPrefixes: true); } } break; @@ -201,16 +191,10 @@ namespace StardewModdingAPI.Framework.ContentManagers } // track & return asset - this.TrackAsset(assetName, asset, language, useCache); + this.TrackAsset(assetName, asset, useCache); return asset; } - /// - public override bool IsLoaded(IAssetName assetName, LanguageCode language) - { - return this.Cache.ContainsKey(assetName.Name); - } - /// public override LocalizedContentManager CreateTemporary() { @@ -371,7 +355,7 @@ namespace StardewModdingAPI.Framework.ContentManagers IAssetName contentKey = this.Coordinator.ParseAssetName(this.GetContentKeyForTilesheetImageSource(relativePath)); try { - this.GameContentManager.Load(contentKey, this.Language, useCache: true); // no need to bypass cache here, since we're not storing the asset + this.GameContentManager.LoadLocalized(contentKey, this.GameContentManager.Language, useCache: true); // no need to bypass cache here, since we're not storing the asset assetName = contentKey; return true; } diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index 4e522c8d..3416c286 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -88,10 +88,10 @@ namespace StardewModdingAPI.Framework.ModHelpers switch (source) { case ContentSource.GameContent: - return this.GameContentManager.Load(assetName, this.CurrentLocaleConstant, useCache: false); + return this.GameContentManager.LoadLocalized(assetName, this.CurrentLocaleConstant, useCache: false); case ContentSource.ModFolder: - return this.ModContentManager.Load(assetName, Constants.DefaultLanguage, useCache: false); + return this.ModContentManager.LoadExact(assetName, useCache: false); default: throw new SContentLoadException($"{this.ModName} failed loading content asset '{key}' from {source}: unknown content source '{source}'."); -- cgit From 8d704153762fa73416a3ccb44ee71032952802eb Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 26 Mar 2022 15:02:11 -0400 Subject: add deprecation notices for SMAPI 4.0.0 (#766) --- docs/release-notes.md | 37 +++++++++++-------------- src/SMAPI/Constants.cs | 17 ++++++++++-- src/SMAPI/Framework/Content/AssetInfo.cs | 23 ++++++++++++++- src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 34 ++++++++++++++++++++--- src/SMAPI/Framework/SCore.cs | 19 +++++++++++++ src/SMAPI/IAssetInfo.cs | 1 + 6 files changed, 103 insertions(+), 28 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/docs/release-notes.md b/docs/release-notes.md index 98392c17..464049b9 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -3,37 +3,32 @@ # Release notes ## Upcoming release * For players: - * Improved translations. Thanks to ChulkyBow (updated Ukrainian)! - * Fixed `player_add` console command's handling of Journal Scraps and Secret Notes. - * Fixed `set_farm_type` console command not updating warps if they moved. - * Improved [command-line arguments](technical/smapi.md#command-line-arguments) on Linux/macOS: + * Fixed support for `_international` content assets (used in the movie theater). + * Fixed the warning text when a mod causes an asset load conflict with itself. + * Improved Linux/macOS [command-line arguments](technical/smapi.md#command-line-arguments): * Added `--use-current-shell` to avoid opening a separate terminal window. * Fixed `--no-terminal` still opening a terminal window, even if nothing is logged to it (thanks to Ryhon0!). - * Fixed warning text when a mod causes an asset load conflict with itself. - * Fixed support for `_international` content assets (used in the movie theater). + * Improved translations. Thanks to ChulkyBow (updated Ukrainian)! + +* For the Console Commands mod: + * Fixed `player_add` not handling journal scraps and secret notes correctly. + * Fixed `set_farm_type` not updating warps. * For mod authors: - * Added [content events](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events#Content), which will replace `IAssetEditor` and `IAssetLoader` in SMAPI 4.0.0. - _These include new features not supported by the old API like load conflict resolution, edit priority, and content pack labels. They also support new cases like easily detecting when an asset has been changed._ - * Overhauled [mod-provided APIs](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations#Mod-provided_APIs) (thanks to Shockah!). - _This adds support for many previously-unsupported cases: proxied interfaces in return values or input arguments, proxied enums if their values match, generic methods, and more._ + * **Added [content events](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events#Content), which will replace `IAssetEditor` and `IAssetLoader` in SMAPI 4.0.0.** + _These include new features not supported by the old API like load conflict resolution, edit priority, and content pack labels. They also support new cases like easily detecting when an asset has changed, and avoid data corruption issues in some edge cases._ + * **Overhauled [mod-provided API](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations#Mod-provided_APIs) proxying** (thanks to Shockah!). + _This adds support for many previously unsupported cases: proxied interfaces in return values or input arguments, proxied enums if their values match, generic methods, and more. Existing mod APIs should work fine as-is._ + * **Deprecation warning:** The upcoming SMAPI 4.0 will remove deprecated APIs and break mods which haven't updated yet. + _See [_Migrate to SMAPI 4.0_](https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_4.0) for help updating your mod code. You can update your mod code now, there's no need to wait for the 4.0.0 release (which will happen in at least three months, and possibly later if needed to update open-source mods)._ * Added `Constants.ContentPath`. - * Added `IAssetName Name` field to the info received by `IAssetEditor` and `IAssetLoader` methods. + * Added `IAssetName` fields to the info received by `IAssetEditor` and `IAssetLoader` methods. _This adds methods for working with asset names, parsed locales, etc._ - * If an asset is loaded multiple times in the same tick, `IAssetLoader.CanLoad` and `IAssetEditor.CanEdit` are now cached unless invalidated via `helper.Content.InvalidateCache`. + * If an asset is loaded multiple times in the same tick, `IAssetLoader.CanLoad` and `IAssetEditor.CanEdit` are now cached unless invalidated by `helper.Content.InvalidateCache`. * Fixed the `SDate` constructor being case-sensitive. * Fixed support for using locale codes from custom languages in asset names (e.g. `Data/Achievements.eo-EU`). * Fixed issue where suppressing `[Left|Right]Thumbstick[Down|Left]` keys would suppress the opposite direction instead. -* **Deprecation warning for mod authors:** - These APIs are now deprecated and will be removed in the upcoming SMAPI 4.0.0. - - API | how to update code - :-- | :----------------- - `Constants.ExecutionPath` | Use `Constants.GamePath` instead. - `IAssetInfo.AssetName`
`IAssetData.AssetName` | Use `Name` instead, which changes the type from `string` to the new `AssetName`. - `IAssetInfo.AssetNameEquals`
`IAssetData.AssetNameEquals` | Use `Name.IsEquivalentTo` instead. - * For the web UI: * Updated the JSON validator/schema for Content Patcher 1.25.0. * Added `data-*` attributes to log parser page for external tools. diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index b736ca59..3351e5c4 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -77,8 +77,21 @@ namespace StardewModdingAPI public static GameFramework GameFramework { get; } = EarlyConstants.GameFramework; /// The path to the game folder. - [Obsolete($"Use {nameof(GamePath)} instead.")] - public static string ExecutionPath => Constants.GamePath; + [Obsolete($"Use {nameof(Constants)}.{nameof(GamePath)} instead.")] + public static string ExecutionPath + { + get + { + SCore.DeprecationManager.Warn( + source: SCore.DeprecationManager.GetSourceNameFromStack(), + nounPhrase: $"{nameof(Constants)}.{nameof(Constants.ExecutionPath)}", + version: "3.14.0", + severity: DeprecationLevel.Notice + ); + + return Constants.GamePath; + } + } /// The path to the game folder. public static string GamePath { get; } = EarlyConstants.GamePath; diff --git a/src/SMAPI/Framework/Content/AssetInfo.cs b/src/SMAPI/Framework/Content/AssetInfo.cs index 556f1c2a..6e93c33c 100644 --- a/src/SMAPI/Framework/Content/AssetInfo.cs +++ b/src/SMAPI/Framework/Content/AssetInfo.cs @@ -27,7 +27,20 @@ namespace StardewModdingAPI.Framework.Content /// [Obsolete($"Use {nameof(Name)} or {nameof(NameWithoutLocale)} instead.")] - public string AssetName => this.NameWithoutLocale.Name; + public string AssetName + { + get + { + SCore.DeprecationManager.Warn( + source: SCore.DeprecationManager.GetSourceNameFromStack(), + nounPhrase: $"{nameof(IAssetInfo)}.{nameof(IAssetInfo.AssetName)}", + version: "3.14.0", + severity: DeprecationLevel.Notice + ); + + return this.NameWithoutLocale.Name; + } + } /// public Type DataType { get; } @@ -54,6 +67,14 @@ namespace StardewModdingAPI.Framework.Content [Obsolete($"Use {nameof(Name)}.{nameof(IAssetName.IsEquivalentTo)} or {nameof(NameWithoutLocale)}.{nameof(IAssetName.IsEquivalentTo)} instead.")] public bool AssetNameEquals(string path) { + SCore.DeprecationManager.Warn( + source: SCore.DeprecationManager.GetSourceNameFromStack(), + nounPhrase: $"{nameof(IAssetInfo)}.{nameof(IAssetInfo.AssetNameEquals)}", + version: "3.14.0", + severity: DeprecationLevel.Notice + ); + + return this.NameWithoutLocale.IsEquivalentTo(path); } diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index 3416c286..3727b909 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -44,16 +44,42 @@ namespace StardewModdingAPI.Framework.ModHelpers public LocalizedContentManager.LanguageCode CurrentLocaleConstant => this.GameContentManager.Language; /// The observable implementation of . - internal ObservableCollection ObservableAssetEditors { get; } = new ObservableCollection(); + internal ObservableCollection ObservableAssetEditors { get; } = new(); /// The observable implementation of . - internal ObservableCollection ObservableAssetLoaders { get; } = new ObservableCollection(); + internal ObservableCollection ObservableAssetLoaders { get; } = new(); /// - public IList AssetLoaders => this.ObservableAssetLoaders; + public IList AssetLoaders + { + get + { + SCore.DeprecationManager.Warn( + source: this.ModName, + nounPhrase: $"{nameof(IContentHelper)}.{nameof(IContentHelper.AssetLoaders)}", + version: "3.14.0", + severity: DeprecationLevel.Notice + ); + + return this.ObservableAssetLoaders; + } + } /// - public IList AssetEditors => this.ObservableAssetEditors; + public IList AssetEditors + { + get + { + SCore.DeprecationManager.Warn( + source: this.ModName, + nounPhrase: $"{nameof(IContentHelper)}.{nameof(IContentHelper.AssetEditors)}", + version: "3.14.0", + severity: DeprecationLevel.Notice + ); + + return this.ObservableAssetEditors; + } + } /********* diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index dd952dee..eab977ac 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1609,9 +1609,28 @@ namespace StardewModdingAPI.Framework { // ReSharper disable SuspiciousTypeConversion.Global if (metadata.Mod is IAssetEditor editor) + { + SCore.DeprecationManager.Warn( + source: metadata.DisplayName, + nounPhrase: $"{nameof(IAssetEditor)}", + version: "3.14.0", + severity: DeprecationLevel.Notice + ); + this.ContentCore.Editors.Add(new ModLinked(metadata, editor)); + } + if (metadata.Mod is IAssetLoader loader) + { + SCore.DeprecationManager.Warn( + source: metadata.DisplayName, + nounPhrase: $"{nameof(IAssetLoader)}", + version: "3.14.0", + severity: DeprecationLevel.Notice + ); + this.ContentCore.Loaders.Add(new ModLinked(metadata, loader)); + } // ReSharper restore SuspiciousTypeConversion.Global helper.ObservableAssetEditors.CollectionChanged += (sender, e) => this.OnAssetInterceptorsChanged(metadata, e.NewItems?.Cast(), e.OldItems?.Cast(), this.ContentCore.Editors); diff --git a/src/SMAPI/IAssetInfo.cs b/src/SMAPI/IAssetInfo.cs index c7b2ab62..c3753b97 100644 --- a/src/SMAPI/IAssetInfo.cs +++ b/src/SMAPI/IAssetInfo.cs @@ -12,6 +12,7 @@ namespace StardewModdingAPI string Locale { get; } /// The asset name being read. + /// NOTE: when reading this field from an or implementation, it's always equivalent to for backwards compatibility. public IAssetName Name { get; } /// The with any locale codes stripped. -- cgit From bacb851d7b186946ba1ead1caeab72e7604cfe6b Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 26 Mar 2022 17:44:48 -0400 Subject: add IContentHelper.ParseAssetName (#766) --- docs/release-notes.md | 1 + src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 6 ++++++ src/SMAPI/IContentHelper.cs | 6 ++++++ 3 files changed, 13 insertions(+) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/docs/release-notes.md b/docs/release-notes.md index 464049b9..99ac86df 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -24,6 +24,7 @@ * Added `Constants.ContentPath`. * Added `IAssetName` fields to the info received by `IAssetEditor` and `IAssetLoader` methods. _This adds methods for working with asset names, parsed locales, etc._ + * Added `helper.Content.ParseAssetName` to get an `IAssetName` for an arbitrary asset key. * If an asset is loaded multiple times in the same tick, `IAssetLoader.CanLoad` and `IAssetEditor.CanEdit` are now cached unless invalidated by `helper.Content.InvalidateCache`. * Fixed the `SDate` constructor being case-sensitive. * Fixed support for using locale codes from custom languages in asset names (e.g. `Data/Achievements.eo-EU`). diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index 3727b909..5d58ee26 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -103,6 +103,12 @@ namespace StardewModdingAPI.Framework.ModHelpers this.Monitor = monitor; } + /// + public IAssetName ParseAssetName(string rawName) + { + return this.ContentCore.ParseAssetName(rawName); + } + /// public T Load(string key, ContentSource source = ContentSource.ModFolder) { diff --git a/src/SMAPI/IContentHelper.cs b/src/SMAPI/IContentHelper.cs index 207b4a33..6fcd18cd 100644 --- a/src/SMAPI/IContentHelper.cs +++ b/src/SMAPI/IContentHelper.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.Contracts; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; +using StardewModdingAPI.Framework.Content; using StardewValley; using xTile; @@ -30,6 +31,11 @@ namespace StardewModdingAPI /********* ** Public methods *********/ + /// Parse a raw asset name. + /// The raw asset name to parse. + /// The is null or empty. + IAssetName ParseAssetName(string rawName); + /// Load content from the game folder or mod folder (if not already cached), and return it. When loading a .png file, this must be called outside the game's draw loop. /// The expected data type. The main supported types are , , dictionaries, and lists; other types may be supported by the game's content pipeline. /// The asset key to fetch (if the is ), or the local path to a content file relative to the mod folder. -- cgit From d864f2ed775dfd5843b9e1cdd1da96ade5dd1068 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 27 Mar 2022 12:16:28 -0400 Subject: add asset type to AssetRequested event (#766) --- src/SMAPI/Events/AssetRequestedEventArgs.cs | 7 ++++++- src/SMAPI/Framework/ContentCoordinator.cs | 5 +++-- src/SMAPI/Framework/ContentManagers/BaseContentManager.cs | 2 +- src/SMAPI/Framework/ContentManagers/GameContentManager.cs | 8 ++++---- src/SMAPI/Framework/ContentManagers/IContentManager.cs | 3 ++- src/SMAPI/Framework/ContentManagers/ModContentManager.cs | 4 ++-- src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 2 ++ src/SMAPI/Framework/SCore.cs | 2 +- src/SMAPI/IContentHelper.cs | 4 +++- 9 files changed, 24 insertions(+), 13 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Events/AssetRequestedEventArgs.cs b/src/SMAPI/Events/AssetRequestedEventArgs.cs index 4d9ee236..c0cbd8fb 100644 --- a/src/SMAPI/Events/AssetRequestedEventArgs.cs +++ b/src/SMAPI/Events/AssetRequestedEventArgs.cs @@ -30,6 +30,9 @@ namespace StardewModdingAPI.Events /// For example, if contains a locale like Data/Bundles.fr-FR, this will be the name without locale like Data/Bundles. If the name has no locale, this field is equivalent. public IAssetName NameWithoutLocale { get; } + /// The requested data type. + public Type DataType { get; } + /// The load operations requested by the event handler. internal IList LoadOperations { get; } = new List(); @@ -43,13 +46,15 @@ namespace StardewModdingAPI.Events /// Construct an instance. /// The mod handling the event. /// The name of the asset being requested. + /// The requested data type. /// The with any locale codes stripped. /// Get the mod metadata for a content pack, if it's a valid content pack for the mod. - internal AssetRequestedEventArgs(IModMetadata mod, IAssetName name, IAssetName nameWithoutLocale, Func getOnBehalfOf) + internal AssetRequestedEventArgs(IModMetadata mod, IAssetName name, IAssetName nameWithoutLocale, Type dataType, Func getOnBehalfOf) { this.Mod = mod; this.Name = name; this.NameWithoutLocale = nameWithoutLocale; + this.DataType = dataType; this.GetOnBehalfOf = getOnBehalfOf; } diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index ee8b6893..108257bf 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -315,9 +315,10 @@ namespace StardewModdingAPI.Framework } /// Get whether an asset from a mod folder exists. + /// The expected asset type. /// The unique name for the content manager which should load this asset. /// The asset name within the mod folder. - public bool DoesManagedAssetExist(string contentManagerID, IAssetName assetName) + public bool DoesManagedAssetExist(string contentManagerID, IAssetName assetName) { // get content manager IContentManager contentManager = this.ContentManagerLock.InReadLock(() => @@ -327,7 +328,7 @@ namespace StardewModdingAPI.Framework throw new InvalidOperationException($"The '{contentManagerID}' prefix isn't handled by any mod."); // get whether the asset exists - return contentManager.DoesAssetExist(assetName); + return contentManager.DoesAssetExist(assetName); } /// Get a copy of an asset from a mod folder. diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index 1ca84792..f41a9354 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -97,7 +97,7 @@ namespace StardewModdingAPI.Framework.ContentManagers } /// - public virtual bool DoesAssetExist(IAssetName assetName) + public virtual bool DoesAssetExist(IAssetName assetName) { return this.Cache.ContainsKey(assetName.Name); } diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index 0fcad30a..3d37e32a 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -58,9 +58,9 @@ namespace StardewModdingAPI.Framework.ContentManagers } /// - public override bool DoesAssetExist(IAssetName assetName) + public override bool DoesAssetExist(IAssetName assetName) { - if (base.DoesAssetExist(assetName)) + if (base.DoesAssetExist(assetName)) return true; // vanilla asset @@ -69,11 +69,11 @@ namespace StardewModdingAPI.Framework.ContentManagers // managed asset if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string contentManagerID, out IAssetName relativePath)) - return this.Coordinator.DoesManagedAssetExist(contentManagerID, relativePath); + return this.Coordinator.DoesManagedAssetExist(contentManagerID, relativePath); // custom asset from a loader string locale = this.GetLocale(); - IAssetInfo info = new AssetInfo(locale, assetName, typeof(object), this.AssertAndNormalizeAssetName); + IAssetInfo info = new AssetInfo(locale, assetName, typeof(T), this.AssertAndNormalizeAssetName); AssetLoadOperation[] loaders = this.GetLoaders(info).ToArray(); if (!this.AssertMaxOneRequiredLoader(info, loaders, out string error)) diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs index 774b20d9..90095492 100644 --- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs @@ -29,8 +29,9 @@ namespace StardewModdingAPI.Framework.ContentManagers ** Methods *********/ /// Get whether an asset exists and can be loaded. + /// The expected asset type. /// The normalized asset name. - bool DoesAssetExist(IAssetName assetName); + bool DoesAssetExist(IAssetName assetName); /// Load an asset through the content pipeline, using a localized variant of the if available. /// The type of asset to load. diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 375b5e0e..2a5a3f16 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -66,9 +66,9 @@ namespace StardewModdingAPI.Framework.ContentManagers } /// - public override bool DoesAssetExist(IAssetName assetName) + public override bool DoesAssetExist(IAssetName assetName) { - if (base.DoesAssetExist(assetName)) + if (base.DoesAssetExist(assetName)) return true; FileInfo file = this.GetModFile(assetName.Name); diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index 5d58ee26..3a5c8938 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -44,9 +44,11 @@ namespace StardewModdingAPI.Framework.ModHelpers public LocalizedContentManager.LanguageCode CurrentLocaleConstant => this.GameContentManager.Language; /// The observable implementation of . + [Obsolete] internal ObservableCollection ObservableAssetEditors { get; } = new(); /// The observable implementation of . + [Obsolete] internal ObservableCollection ObservableAssetLoaders { get; } = new(); /// diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 5deb177c..efdfabe7 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1151,7 +1151,7 @@ namespace StardewModdingAPI.Framework this.EventManager.AssetRequested.Raise( invoke: (mod, invoke) => { - AssetRequestedEventArgs args = new(mod, asset.Name, asset.NameWithoutLocale, this.GetOnBehalfOfContentPack); + AssetRequestedEventArgs args = new(mod, asset.Name, asset.NameWithoutLocale, asset.DataType, this.GetOnBehalfOfContentPack); invoke(args); diff --git a/src/SMAPI/IContentHelper.cs b/src/SMAPI/IContentHelper.cs index 6fcd18cd..1d36abff 100644 --- a/src/SMAPI/IContentHelper.cs +++ b/src/SMAPI/IContentHelper.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.Contracts; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; -using StardewModdingAPI.Framework.Content; +using StardewModdingAPI.Events; using StardewValley; using xTile; @@ -16,9 +16,11 @@ namespace StardewModdingAPI ** Accessors *********/ /// Interceptors which provide the initial versions of matching content assets. + [Obsolete($"Use {nameof(IMod.Helper)}.{nameof(IModHelper.Events)}.{nameof(IModEvents.Content)} instead. This interface will be removed in SMAPI 4.0.0.")] IList AssetLoaders { get; } /// Interceptors which edit matching content assets after they're loaded. + [Obsolete($"Use {nameof(IMod.Helper)}.{nameof(IModHelper.Events)}.{nameof(IModEvents.Content)} instead. This interface will be removed in SMAPI 4.0.0.")] IList AssetEditors { get; } /// The game's current locale code (like pt-BR). -- cgit From 1d3c99cc25f6c0d504fd5e43ea71ef327b6e9066 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 27 Mar 2022 13:42:14 -0400 Subject: split helper.Content API into game/mod content APIs --- docs/release-notes.md | 2 + src/SMAPI/Framework/Content/AssetName.cs | 12 ++ src/SMAPI/Framework/ContentPack.cs | 24 ++-- src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 9 +- .../Framework/ModHelpers/GameContentHelper.cs | 129 +++++++++++++++++++++ src/SMAPI/Framework/ModHelpers/ModContentHelper.cs | 75 ++++++++++++ src/SMAPI/Framework/ModHelpers/ModHelper.cs | 38 +++++- src/SMAPI/Framework/SCore.cs | 12 +- src/SMAPI/IAssetName.cs | 5 + src/SMAPI/IContentHelper.cs | 10 +- src/SMAPI/IContentPack.cs | 5 + src/SMAPI/IGameContentHelper.cs | 73 ++++++++++++ src/SMAPI/IModContentHelper.cs | 32 +++++ src/SMAPI/IModHelper.cs | 10 ++ 14 files changed, 400 insertions(+), 36 deletions(-) create mode 100644 src/SMAPI/Framework/ModHelpers/GameContentHelper.cs create mode 100644 src/SMAPI/Framework/ModHelpers/ModContentHelper.cs create mode 100644 src/SMAPI/IGameContentHelper.cs create mode 100644 src/SMAPI/IModContentHelper.cs (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/docs/release-notes.md b/docs/release-notes.md index 99ac86df..2e09240c 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -17,10 +17,12 @@ * For mod authors: * **Added [content events](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events#Content), which will replace `IAssetEditor` and `IAssetLoader` in SMAPI 4.0.0.** _These include new features not supported by the old API like load conflict resolution, edit priority, and content pack labels. They also support new cases like easily detecting when an asset has changed, and avoid data corruption issues in some edge cases._ + * **Added `helper.GameContent` and `helper.ModContent`, which will replace `helper.Content` in SMAPI 4.0.0.** * **Overhauled [mod-provided API](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations#Mod-provided_APIs) proxying** (thanks to Shockah!). _This adds support for many previously unsupported cases: proxied interfaces in return values or input arguments, proxied enums if their values match, generic methods, and more. Existing mod APIs should work fine as-is._ * **Deprecation warning:** The upcoming SMAPI 4.0 will remove deprecated APIs and break mods which haven't updated yet. _See [_Migrate to SMAPI 4.0_](https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_4.0) for help updating your mod code. You can update your mod code now, there's no need to wait for the 4.0.0 release (which will happen in at least three months, and possibly later if needed to update open-source mods)._ + * Added `IContentPack.ModContent` property. * Added `Constants.ContentPath`. * Added `IAssetName` fields to the info received by `IAssetEditor` and `IAssetLoader` methods. _This adds methods for working with asset names, parsed locales, etc._ diff --git a/src/SMAPI/Framework/Content/AssetName.cs b/src/SMAPI/Framework/Content/AssetName.cs index 7ce0f8ee..a1d37b0b 100644 --- a/src/SMAPI/Framework/Content/AssetName.cs +++ b/src/SMAPI/Framework/Content/AssetName.cs @@ -100,6 +100,18 @@ namespace StardewModdingAPI.Framework.Content return compareTo.Equals(assetName, StringComparison.OrdinalIgnoreCase); } + /// + public bool IsEquivalentTo(IAssetName assetName, bool useBaseName = false) + { + if (useBaseName) + return this.BaseName.Equals(assetName?.BaseName, StringComparison.OrdinalIgnoreCase); + + if (assetName is AssetName impl) + return this.ComparableName == impl?.ComparableName; + + return this.Name.Equals(assetName?.Name, StringComparison.OrdinalIgnoreCase); + } + /// public bool StartsWith(string prefix, bool allowPartialWord = true, bool allowSubfolder = true) { diff --git a/src/SMAPI/Framework/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs index b6add7b5..3920354e 100644 --- a/src/SMAPI/Framework/ContentPack.cs +++ b/src/SMAPI/Framework/ContentPack.cs @@ -13,9 +13,6 @@ namespace StardewModdingAPI.Framework /********* ** Fields *********/ - /// Provides an API for loading content assets. - private readonly IContentHelper Content; - /// Encapsulates SMAPI's JSON file parsing. private readonly JsonHelper JsonHelper; @@ -35,6 +32,9 @@ namespace StardewModdingAPI.Framework /// public ITranslationHelper Translation => this.TranslationImpl; + /// + public IModContentHelper ModContent { get; } + /// The underlying translation helper. internal TranslationHelper TranslationImpl { get; set; } @@ -45,14 +45,14 @@ namespace StardewModdingAPI.Framework /// Construct an instance. /// The full path to the content pack's folder. /// The content pack's manifest. - /// Provides an API for loading content assets. + /// Provides an API for loading content assets from the content pack's folder. /// Provides translations stored in the content pack's i18n folder. /// Encapsulates SMAPI's JSON file parsing. - public ContentPack(string directoryPath, IManifest manifest, IContentHelper content, TranslationHelper translation, JsonHelper jsonHelper) + public ContentPack(string directoryPath, IManifest manifest, IModContentHelper content, TranslationHelper translation, JsonHelper jsonHelper) { this.DirectoryPath = directoryPath; this.Manifest = manifest; - this.Content = content; + this.ModContent = content; this.TranslationImpl = translation; this.JsonHelper = jsonHelper; @@ -95,21 +95,17 @@ namespace StardewModdingAPI.Framework } /// + [Obsolete] public T LoadAsset(string key) { - key = PathUtilities.NormalizePath(key); - - key = this.GetCaseInsensitiveRelativePath(key); - return this.Content.Load(key, ContentSource.ModFolder); + return this.ModContent.Load(key); } /// + [Obsolete] public string GetActualAssetKey(string key) { - key = PathUtilities.NormalizePath(key); - - key = this.GetCaseInsensitiveRelativePath(key); - return this.Content.GetActualAssetKey(key, ContentSource.ModFolder); + return this.ModContent.GetInternalAssetName(key)?.Name; } diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index 3a5c8938..b0064532 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -13,6 +13,7 @@ using StardewValley; namespace StardewModdingAPI.Framework.ModHelpers { /// Provides an API for loading content assets. + [Obsolete] internal class ContentHelper : BaseHelper, IContentHelper { /********* @@ -44,11 +45,9 @@ namespace StardewModdingAPI.Framework.ModHelpers public LocalizedContentManager.LanguageCode CurrentLocaleConstant => this.GameContentManager.Language; /// The observable implementation of . - [Obsolete] internal ObservableCollection ObservableAssetEditors { get; } = new(); /// The observable implementation of . - [Obsolete] internal ObservableCollection ObservableAssetLoaders { get; } = new(); /// @@ -105,12 +104,6 @@ namespace StardewModdingAPI.Framework.ModHelpers this.Monitor = monitor; } - /// - public IAssetName ParseAssetName(string rawName) - { - return this.ContentCore.ParseAssetName(rawName); - } - /// public T Load(string key, ContentSource source = ContentSource.ModFolder) { diff --git a/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs new file mode 100644 index 00000000..42a4de20 --- /dev/null +++ b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs @@ -0,0 +1,129 @@ +using System; +using System.Linq; +using StardewModdingAPI.Framework.Content; +using StardewModdingAPI.Framework.ContentManagers; +using StardewModdingAPI.Framework.Exceptions; +using StardewValley; + +namespace StardewModdingAPI.Framework.ModHelpers +{ + /// + internal class GameContentHelper : BaseHelper, IGameContentHelper + { + /********* + ** Fields + *********/ + /// SMAPI's core content logic. + private readonly ContentCoordinator ContentCore; + + /// The underlying game content manager. + private readonly IContentManager GameContentManager; + + /// The friendly mod name for use in errors. + private readonly string ModName; + + /// Encapsulates monitoring and logging. + private readonly IMonitor Monitor; + + + /********* + ** Accessors + *********/ + /// + public string CurrentLocale => this.GameContentManager.GetLocale(); + + /// + public LocalizedContentManager.LanguageCode CurrentLocaleConstant => this.GameContentManager.Language; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// SMAPI's core content logic. + /// The unique ID of the relevant mod. + /// The friendly mod name for use in errors. + /// Encapsulates monitoring and logging. + public GameContentHelper(ContentCoordinator contentCore, string modID, string modName, IMonitor monitor) + : base(modID) + { + string managedAssetPrefix = contentCore.GetManagedAssetPrefix(modID); + + this.ContentCore = contentCore; + this.GameContentManager = contentCore.CreateGameContentManager(managedAssetPrefix + ".content"); + this.ModName = modName; + this.Monitor = monitor; + } + + /// + public IAssetName ParseAssetName(string rawName) + { + return this.ContentCore.ParseAssetName(rawName); + } + + /// + public T Load(string key) + { + IAssetName assetName = this.ContentCore.ParseAssetName(key); + return this.Load(assetName); + } + + /// + public T Load(IAssetName assetName) + { + try + { + return this.GameContentManager.LoadLocalized(assetName, this.CurrentLocaleConstant, useCache: false); + } + catch (Exception ex) when (ex is not SContentLoadException) + { + throw new SContentLoadException($"{this.ModName} failed loading content asset '{assetName}' from the game content.", ex); + } + } + + /// + public bool InvalidateCache(string key) + { + IAssetName assetName = this.ParseAssetName(key); + return this.InvalidateCache(assetName); + } + + /// + public bool InvalidateCache(IAssetName assetName) + { + this.Monitor.Log($"Requested cache invalidation for '{assetName}'."); + return this.ContentCore.InvalidateCache(asset => asset.Name.IsEquivalentTo(assetName)).Any(); + } + + /// + public bool InvalidateCache() + { + 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(); + } + + /// + public bool InvalidateCache(Func predicate) + { + this.Monitor.Log("Requested cache invalidation for all assets matching a predicate."); + return this.ContentCore.InvalidateCache(predicate).Any(); + } + + /// + public IAssetData GetPatchHelper(T data, string assetName = null) + { + if (data == null) + throw new ArgumentNullException(nameof(data), "Can't get a patch helper for a null value."); + + assetName ??= $"temp/{Guid.NewGuid():N}"; + + return new AssetDataForObject(this.CurrentLocale, this.ContentCore.ParseAssetName(assetName), data, key => this.ParseAssetName(key).Name); + } + + /// Get the underlying game content manager. + internal IContentManager GetUnderlyingContentManager() + { + return this.GameContentManager; + } + } +} diff --git a/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs new file mode 100644 index 00000000..45899dd7 --- /dev/null +++ b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs @@ -0,0 +1,75 @@ +using System; +using StardewModdingAPI.Framework.Content; +using StardewModdingAPI.Framework.ContentManagers; +using StardewModdingAPI.Framework.Exceptions; + +namespace StardewModdingAPI.Framework.ModHelpers +{ + /// + internal class ModContentHelper : BaseHelper, IModContentHelper + { + /********* + ** Fields + *********/ + /// SMAPI's core content logic. + private readonly ContentCoordinator ContentCore; + + /// A content manager for this mod which manages files from the mod's folder. + private readonly ModContentManager ModContentManager; + + /// The friendly mod name for use in errors. + private readonly string ModName; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// SMAPI's core content logic. + /// The absolute path to the mod folder. + /// The unique ID of the relevant mod. + /// The friendly mod name for use in errors. + /// The game content manager used for map tilesheets not provided by the mod. + public ModContentHelper(ContentCoordinator contentCore, string modFolderPath, string modID, string modName, IContentManager gameContentManager) + : base(modID) + { + string managedAssetPrefix = contentCore.GetManagedAssetPrefix(modID); + + this.ContentCore = contentCore; + this.ModContentManager = contentCore.CreateModContentManager(managedAssetPrefix, modName, modFolderPath, gameContentManager); + this.ModName = modName; + } + + /// + public T Load(string relativePath) + { + IAssetName assetName = this.ContentCore.ParseAssetName(relativePath); + + try + { + return this.ModContentManager.LoadExact(assetName, useCache: false); + } + catch (Exception ex) when (ex is not SContentLoadException) + { + throw new SContentLoadException($"{this.ModName} failed loading content asset '{relativePath}' from its mod folder.", ex); + } + } + + /// + public IAssetName GetInternalAssetName(string relativePath) + { + return this.ModContentManager.GetInternalAssetKey(relativePath); + } + + /// + public IAssetData GetPatchHelper(T data, string relativePath = null) + { + if (data == null) + throw new ArgumentNullException(nameof(data), "Can't get a patch helper for a null value."); + + relativePath ??= $"temp/{Guid.NewGuid():N}"; + + return new AssetDataForObject(this.ContentCore.GetLocale(), this.ContentCore.ParseAssetName(relativePath), data, key => this.ContentCore.ParseAssetName(key).Name); + } + } +} diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index 058bff83..d28faacc 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -8,6 +8,14 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Provides simplified APIs for writing mods. internal class ModHelper : BaseHelper, IModHelper, IDisposable { + /********* + ** Fields + *********/ + /// The backing field for . + [Obsolete] + private readonly IContentHelper ContentImpl; + + /********* ** Accessors *********/ @@ -18,7 +26,27 @@ namespace StardewModdingAPI.Framework.ModHelpers public IModEvents Events { get; } /// - public IContentHelper Content { get; } + [Obsolete] + public IContentHelper Content + { + get + { + SCore.DeprecationManager.Warn( + source: SCore.DeprecationManager.GetSourceName(this.ModID), + nounPhrase: $"{nameof(IModHelper)}.{nameof(IModHelper.Content)}", + version: "3.14.0", + severity: DeprecationLevel.Notice + ); + + return this.ContentImpl; + } + } + + /// + public IGameContentHelper GameContent { get; } + + /// + public IModContentHelper ModContent { get; } /// public IContentPackHelper ContentPacks { get; } @@ -54,6 +82,8 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Manages the game's input state for the current player instance. That may not be the main player in split-screen mode. /// Manages access to events raised by SMAPI. /// An API for loading content assets. + /// An API for loading content assets from the game's Content folder or via . + /// An API for loading content assets from your mod's files. /// An API for managing content packs. /// An API for managing console commands. /// An API for reading and writing persistent mod data. @@ -63,7 +93,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// An API for reading translations stored in the mod's i18n folder. /// An argument is null or empty. /// The path does not exist on disk. - public ModHelper(string modID, string modDirectory, Func currentInputState, IModEvents events, IContentHelper contentHelper, IContentPackHelper contentPackHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper) + public ModHelper(string modID, string modDirectory, Func currentInputState, IModEvents events, IContentHelper contentHelper, IGameContentHelper gameContentHelper, IModContentHelper modContentHelper, IContentPackHelper contentPackHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper) : base(modID) { // validate directory @@ -74,7 +104,9 @@ namespace StardewModdingAPI.Framework.ModHelpers // initialize this.DirectoryPath = modDirectory; - this.Content = contentHelper ?? throw new ArgumentNullException(nameof(contentHelper)); + this.ContentImpl = contentHelper ?? throw new ArgumentNullException(nameof(contentHelper)); + this.GameContent = gameContentHelper ?? throw new ArgumentNullException(nameof(gameContentHelper)); + this.ModContent = modContentHelper ?? throw new ArgumentNullException(nameof(modContentHelper)); this.ContentPacks = contentPackHelper ?? throw new ArgumentNullException(nameof(contentPackHelper)); this.Data = dataHelper ?? throw new ArgumentNullException(nameof(dataHelper)); this.Input = new InputHelper(modID, currentInputState); diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index efdfabe7..b4aa3595 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1775,9 +1775,10 @@ namespace StardewModdingAPI.Framework { IManifest manifest = mod.Manifest; IMonitor monitor = this.LogManager.GetMonitor(mod.DisplayName); - IContentHelper contentHelper = new ContentHelper(this.ContentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, monitor); + GameContentHelper gameContentHelper = new(this.ContentCore, manifest.UniqueID, mod.DisplayName, monitor); + IModContentHelper modContentHelper = new ModContentHelper(this.ContentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager()); TranslationHelper translationHelper = new(manifest.UniqueID, contentCore.GetLocale(), contentCore.Language); - IContentPack contentPack = new ContentPack(mod.DirectoryPath, manifest, contentHelper, translationHelper, jsonHelper); + IContentPack contentPack = new ContentPack(mod.DirectoryPath, manifest, modContentHelper, translationHelper, jsonHelper); mod.SetMod(contentPack, monitor, translationHelper); this.ModRegistry.Add(mod); @@ -1855,7 +1856,8 @@ namespace StardewModdingAPI.Framework IContentPack CreateFakeContentPack(string packDirPath, IManifest packManifest) { IMonitor packMonitor = this.LogManager.GetMonitor(packManifest.Name); - IContentHelper packContentHelper = new ContentHelper(contentCore, packDirPath, packManifest.UniqueID, packManifest.Name, packMonitor); + GameContentHelper gameContentHelper = new(contentCore, packManifest.UniqueID, packManifest.Name, packMonitor); + IModContentHelper packContentHelper = new ModContentHelper(contentCore, packDirPath, packManifest.UniqueID, packManifest.Name, gameContentHelper.GetUnderlyingContentManager()); TranslationHelper packTranslationHelper = new(packManifest.UniqueID, contentCore.GetLocale(), contentCore.Language); ContentPack contentPack = new(packDirPath, packManifest, packContentHelper, packTranslationHelper, this.Toolkit.JsonHelper); @@ -1867,13 +1869,15 @@ 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); + GameContentHelper gameContentHelper = new(contentCore, manifest.UniqueID, mod.DisplayName, monitor); + IModContentHelper modContentHelper = new ModContentHelper(contentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager()); IContentPackHelper contentPackHelper = new ContentPackHelper(manifest.UniqueID, new Lazy(GetContentPacks), CreateFakeContentPack); IDataHelper dataHelper = new DataHelper(manifest.UniqueID, mod.DirectoryPath, jsonHelper); IReflectionHelper reflectionHelper = new ReflectionHelper(manifest.UniqueID, mod.DisplayName, this.Reflection); IModRegistry modRegistryHelper = new ModRegistryHelper(manifest.UniqueID, this.ModRegistry, proxyFactory, monitor); IMultiplayerHelper multiplayerHelper = new MultiplayerHelper(manifest.UniqueID, this.Multiplayer); - modHelper = new ModHelper(manifest.UniqueID, mod.DirectoryPath, () => this.GetCurrentGameInstance().Input, events, contentHelper, contentPackHelper, commandHelper, dataHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper); + modHelper = new ModHelper(manifest.UniqueID, mod.DirectoryPath, () => this.GetCurrentGameInstance().Input, events, contentHelper, gameContentHelper, modContentHelper, contentPackHelper, commandHelper, dataHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper); } // init mod diff --git a/src/SMAPI/IAssetName.cs b/src/SMAPI/IAssetName.cs index 89f02adf..c91da266 100644 --- a/src/SMAPI/IAssetName.cs +++ b/src/SMAPI/IAssetName.cs @@ -30,6 +30,11 @@ namespace StardewModdingAPI /// Whether to compare the given name with the (if true) or (if false). This has no effect on any locale included in the given . bool IsEquivalentTo(string assetName, bool useBaseName = false); + /// Get whether the given asset name is equivalent, ignoring capitalization and formatting. + /// The asset name to compare this instance to. + /// Whether to compare the given name with the (if true) or (if false). + bool IsEquivalentTo(IAssetName assetName, bool useBaseName = false); + /// Get whether the asset name starts with the given value, ignoring capitalization and formatting. This can be used with a trailing slash to test for an asset folder, like Data/. /// The prefix to match. /// Whether to match if the prefix occurs mid-word, so Data/AchievementsToIgnore matches prefix Data/Achievements. If this is false, the prefix only matches if the asset name starts with the prefix followed by a non-alphanumeric character (including ., /, or \\) or the end of string. diff --git a/src/SMAPI/IContentHelper.cs b/src/SMAPI/IContentHelper.cs index 1d36abff..48f6bfd8 100644 --- a/src/SMAPI/IContentHelper.cs +++ b/src/SMAPI/IContentHelper.cs @@ -10,17 +10,18 @@ using xTile; namespace StardewModdingAPI { /// Provides an API for loading content assets. + [Obsolete($"Use {nameof(IMod.Helper)}.{nameof(IModHelper.GameContent)} or {nameof(IMod.Helper)}.{nameof(IModHelper.ModContent)} instead. This interface will be removed in SMAPI 4.0.0.")] public interface IContentHelper : IModLinked { /********* ** Accessors *********/ /// Interceptors which provide the initial versions of matching content assets. - [Obsolete($"Use {nameof(IMod.Helper)}.{nameof(IModHelper.Events)}.{nameof(IModEvents.Content)} instead. This interface will be removed in SMAPI 4.0.0.")] + [Obsolete($"Use {nameof(IMod.Helper)}.{nameof(IModHelper.Events)}.{nameof(IModEvents.Content)} instead. This property will be removed in SMAPI 4.0.0.")] IList AssetLoaders { get; } /// Interceptors which edit matching content assets after they're loaded. - [Obsolete($"Use {nameof(IMod.Helper)}.{nameof(IModHelper.Events)}.{nameof(IModEvents.Content)} instead. This interface will be removed in SMAPI 4.0.0.")] + [Obsolete($"Use {nameof(IMod.Helper)}.{nameof(IModHelper.Events)}.{nameof(IModEvents.Content)} instead. This property will be removed in SMAPI 4.0.0.")] IList AssetEditors { get; } /// The game's current locale code (like pt-BR). @@ -33,11 +34,6 @@ namespace StardewModdingAPI /********* ** Public methods *********/ - /// Parse a raw asset name. - /// The raw asset name to parse. - /// The is null or empty. - IAssetName ParseAssetName(string rawName); - /// Load content from the game folder or mod folder (if not already cached), and return it. When loading a .png file, this must be called outside the game's draw loop. /// The expected data type. The main supported types are , , dictionaries, and lists; other types may be supported by the game's content pipeline. /// The asset key to fetch (if the is ), or the local path to a content file relative to the mod folder. diff --git a/src/SMAPI/IContentPack.cs b/src/SMAPI/IContentPack.cs index 9cc64dcd..3c66faff 100644 --- a/src/SMAPI/IContentPack.cs +++ b/src/SMAPI/IContentPack.cs @@ -20,6 +20,9 @@ namespace StardewModdingAPI /// Provides translations stored in the content pack's i18n folder. See for more info. ITranslationHelper Translation { get; } + /// An API for loading content assets from the content pack's files. + IModContentHelper ModContent { get; } + /********* ** Public methods @@ -47,11 +50,13 @@ namespace StardewModdingAPI /// The relative file path within the content pack (case-insensitive). /// The is empty or contains invalid characters. /// The content asset couldn't be loaded (e.g. because it doesn't exist). + [Obsolete($"Use {nameof(IContentPack.ModContent)}.{nameof(IModContentHelper.Load)} instead. This method will be removed in SMAPI 4.0.0.")] T LoadAsset(string key); /// Get the underlying key in the game's content cache for an asset. This can be used to load custom map tilesheets, but should be avoided when you can use the content API instead. This does not validate whether the asset exists. /// The relative file path within the content pack (case-insensitive). /// The is empty or contains invalid characters. + [Obsolete($"Use {nameof(IContentPack.ModContent)}.{nameof(IModContentHelper.GetInternalAssetName)} instead. This method will be removed in SMAPI 4.0.0.")] string GetActualAssetKey(string key); } } diff --git a/src/SMAPI/IGameContentHelper.cs b/src/SMAPI/IGameContentHelper.cs new file mode 100644 index 00000000..86bc3e0e --- /dev/null +++ b/src/SMAPI/IGameContentHelper.cs @@ -0,0 +1,73 @@ +using System; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using StardewModdingAPI.Events; +using StardewValley; +using xTile; + +namespace StardewModdingAPI +{ + /// Provides an API for loading content assets from the game's Content folder or via . + public interface IGameContentHelper : IModLinked + { + /********* + ** Accessors + *********/ + /// The game's current locale code (like pt-BR). + string CurrentLocale { get; } + + /// The game's current locale as an enum value. + LocalizedContentManager.LanguageCode CurrentLocaleConstant { get; } + + + /********* + ** Public methods + *********/ + /// Parse a raw asset name. + /// The raw asset name to parse. + /// The is null or empty. + IAssetName ParseAssetName(string rawName); + + /// Load content from the game folder or mod folder (if not already cached), and return it. When loading a .png file, this must be called outside the game's draw loop. + /// The expected data type. The main supported types are , , dictionaries, and lists; other types may be supported by the game's content pipeline. + /// The asset name to load. + /// The is empty or contains invalid characters. + /// The content asset couldn't be loaded (e.g. because it doesn't exist). + T Load(string assetName); + + /// Load content from the game folder or mod folder (if not already cached), and return it. When loading a .png file, this must be called outside the game's draw loop. + /// The expected data type. The main supported types are , , dictionaries, and lists; other types may be supported by the game's content pipeline. + /// The asset name to load. + /// The is empty or contains invalid characters. + /// The content asset couldn't be loaded (e.g. because it doesn't exist). + T Load(IAssetName assetName); + + /// Remove an asset from the content cache so it's reloaded on the next request. This will reload core game assets if needed, but references to the former asset will still show the previous content. + /// The asset key to invalidate in the content folder. + /// The is empty or contains invalid characters. + /// Returns whether the given asset key was cached. + bool InvalidateCache(string assetName); + + /// Remove an asset from the content cache so it's reloaded on the next request. This will reload core game assets if needed, but references to the former asset will still show the previous content. + /// The asset key to invalidate in the content folder. + /// The is empty or contains invalid characters. + /// Returns whether the given asset key was cached. + bool InvalidateCache(IAssetName assetName); + + /// Remove all assets of the given type from the cache so they're reloaded on the next request. This can be a very expensive operation and should only be used in very specific cases. This will reload core game assets if needed, but references to the former assets will still show the previous content. + /// The asset type to remove from the cache. + /// Returns whether any assets were invalidated. + bool InvalidateCache(); + + /// Remove matching assets from the content cache so they're reloaded on the next request. This will reload core game assets if needed, but references to the former asset will still show the previous content. + /// A predicate matching the assets to invalidate. + /// Returns whether any cache entries were invalidated. + bool InvalidateCache(Func predicate); + + /// Get a patch helper for arbitrary data. + /// The data type. + /// The asset data. + /// The asset name. This is only used for tracking purposes and has no effect on the patch helper. + IAssetData GetPatchHelper(T data, string assetName = null); + } +} diff --git a/src/SMAPI/IModContentHelper.cs b/src/SMAPI/IModContentHelper.cs new file mode 100644 index 00000000..e3431365 --- /dev/null +++ b/src/SMAPI/IModContentHelper.cs @@ -0,0 +1,32 @@ +using System; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using xTile; + +namespace StardewModdingAPI +{ + /// Provides an API for loading content assets from the current mod's folder. + public interface IModContentHelper : IModLinked + { + /********* + ** Public methods + *********/ + /// Load content from the mod folder and return it. When loading a .png file, this must be called outside the game's draw loop. + /// The expected data type. The main supported types are , , dictionaries, and lists; other types may be supported by the game's content pipeline. + /// The local path to a content file relative to the mod folder. + /// The is empty or contains invalid characters. + /// The content asset couldn't be loaded (e.g. because it doesn't exist). + T Load(string relativePath); + + /// Get the internal asset name which allows loading a mod file through any of the game's content managers. This can be used when passing asset names directly to the game (e.g. for map tilesheets), but should be avoided if you can use instead. This does not validate whether the asset exists. + /// The local path to a content file relative to the mod folder. + /// The is empty or contains invalid characters. + IAssetName GetInternalAssetName(string relativePath); + + /// Get a patch helper for arbitrary data. + /// The data type. + /// The asset data. + /// The local path to the content file being edited relative to the mod folder. This is only used for tracking purposes and has no effect on the patch helper. + IAssetData GetPatchHelper(T data, string relativePath = null); + } +} diff --git a/src/SMAPI/IModHelper.cs b/src/SMAPI/IModHelper.cs index cd746e06..15e4ed8d 100644 --- a/src/SMAPI/IModHelper.cs +++ b/src/SMAPI/IModHelper.cs @@ -1,3 +1,4 @@ +using System; using StardewModdingAPI.Events; namespace StardewModdingAPI @@ -17,13 +18,22 @@ namespace StardewModdingAPI /// An API for managing console commands. ICommandHelper ConsoleCommands { get; } + /// An API for loading content assets from the game's Content folder or using the events. + IGameContentHelper GameContent { get; } + + /// An API for loading content assets from your mod's files. + /// This API is intended for reading content assets from the mod files (like game data, images, etc); see also which is intended for persisting internal mod data. + IModContentHelper ModContent { get; } + /// An API for loading content assets. + [Obsolete($"Use {nameof(IGameContentHelper)} or {nameof(IModContentHelper)} instead.")] IContentHelper Content { get; } /// An API for managing content packs. IContentPackHelper ContentPacks { get; } /// An API for reading and writing persistent mod data. + /// This API is intended for persisting internal mod data; see also which is intended for reading content assets (like game data, images, etc). IDataHelper Data { get; } /// An API for checking and changing input state. -- cgit From bac1f34f65412419656eea15ea81081f8a29867e Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 1 Apr 2022 22:59:50 -0400 Subject: fix local file path asset name parsing locale codes in rare cases (#766) Mod file paths can't be localized through the content pipeline. Normally the locale would be ignored anyway due to the file extension, but it'd be incorrectly parsed if the file name ended with a locale and no file extension (like "assets/example.fr-FR"). --- src/SMAPI/Framework/ContentCoordinator.cs | 20 +++++++++++++------- .../Framework/ContentManagers/BaseContentManager.cs | 6 +++--- .../Framework/ContentManagers/ModContentManager.cs | 6 +++--- src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 12 ++++++------ src/SMAPI/Framework/ModHelpers/GameContentHelper.cs | 6 +++--- src/SMAPI/Framework/ModHelpers/ModContentHelper.cs | 4 ++-- 6 files changed, 30 insertions(+), 24 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 108257bf..8483d45d 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -156,7 +156,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>(() => this.GetLocaleCodes(customLanguages: Enumerable.Empty())); } @@ -269,11 +269,17 @@ namespace StardewModdingAPI.Framework /// Parse a raw asset name. /// The raw asset name to parse. + /// Whether to parse locales in the . 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). /// The is null or empty. - 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 +309,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 +363,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 +384,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 +400,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); } diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index 2d921cc3..31199b3a 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -119,7 +119,7 @@ namespace StardewModdingAPI.Framework.ContentManagers public sealed override T Load(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(parsedName, language, useCache: true); } @@ -161,7 +161,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(assetName, useCache: useCache); } @@ -213,7 +213,7 @@ namespace StardewModdingAPI.Framework.ContentManagers IDictionary removeAssets = new Dictionary(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); diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 63b40d66..9ed989da 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -111,7 +111,7 @@ namespace StardewModdingAPI.Framework.ContentManagers { // 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]); + IAssetName loadName = this.Coordinator.ParseAssetName(assetName.Name[..^".xnb".Length], allowLocales: false); // load asset asset = this.RawLoad(loadName, useCache: false); @@ -201,7 +201,7 @@ namespace StardewModdingAPI.Framework.ContentManagers string relativePath = PathUtilities.GetRelativePath(this.RootDirectory, file.FullName); string internalKey = Path.Combine(this.Name, relativePath); - return this.Coordinator.ParseAssetName(internalKey); + return this.Coordinator.ParseAssetName(internalKey, allowLocales: false); } @@ -343,7 +343,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(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/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index b0064532..ae914c46 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -107,7 +107,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// public T Load(string key, ContentSource source = ContentSource.ModFolder) { - IAssetName assetName = this.ContentCore.ParseAssetName(key); + IAssetName assetName = this.ContentCore.ParseAssetName(key, allowLocales: source == ContentSource.GameContent); try { @@ -157,21 +157,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(); } /// public bool InvalidateCache() { - 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(); } /// public bool InvalidateCache(Func 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 +183,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/GameContentHelper.cs b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs index 42a4de20..0eb385d4 100644 --- a/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs @@ -58,13 +58,13 @@ namespace StardewModdingAPI.Framework.ModHelpers /// public IAssetName ParseAssetName(string rawName) { - return this.ContentCore.ParseAssetName(rawName); + return this.ContentCore.ParseAssetName(rawName, allowLocales: true); } /// public T Load(string key) { - IAssetName assetName = this.ContentCore.ParseAssetName(key); + IAssetName assetName = this.ContentCore.ParseAssetName(key, allowLocales: true); return this.Load(assetName); } @@ -117,7 +117,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); } /// Get the underlying game content manager. diff --git a/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs index 45899dd7..2379583c 100644 --- a/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs @@ -43,7 +43,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// public T Load(string relativePath) { - IAssetName assetName = this.ContentCore.ParseAssetName(relativePath); + IAssetName assetName = this.ContentCore.ParseAssetName(relativePath, allowLocales: false); try { @@ -69,7 +69,7 @@ namespace StardewModdingAPI.Framework.ModHelpers 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); } } } -- cgit From 4e2d7f2550b05e410735f51beac76ed040178cf4 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 1 Apr 2022 23:42:37 -0400 Subject: make mod file paths case-insensitive in all SMAPI APIs --- docs/release-notes.md | 15 ++- src/SMAPI/Framework/ContentCoordinator.cs | 22 +++- .../Framework/ContentManagers/ModContentManager.cs | 14 ++- src/SMAPI/Framework/ContentPack.cs | 38 ++----- src/SMAPI/Framework/ModHelpers/ModContentHelper.cs | 16 ++- src/SMAPI/Framework/SCore.cs | 15 ++- src/SMAPI/Utilities/CaseInsensitivePathCache.cs | 124 +++++++++++++++++++++ 7 files changed, 200 insertions(+), 44 deletions(-) create mode 100644 src/SMAPI/Utilities/CaseInsensitivePathCache.cs (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/docs/release-notes.md b/docs/release-notes.md index 2e09240c..6d505d40 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -8,6 +8,7 @@ * Improved Linux/macOS [command-line arguments](technical/smapi.md#command-line-arguments): * Added `--use-current-shell` to avoid opening a separate terminal window. * Fixed `--no-terminal` still opening a terminal window, even if nothing is logged to it (thanks to Ryhon0!). + * SMAPI now fixes many case-sensitive mod file path issues automatically. * Improved translations. Thanks to ChulkyBow (updated Ukrainian)! * For the Console Commands mod: @@ -15,12 +16,14 @@ * Fixed `set_farm_type` not updating warps. * For mod authors: - * **Added [content events](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events#Content), which will replace `IAssetEditor` and `IAssetLoader` in SMAPI 4.0.0.** - _These include new features not supported by the old API like load conflict resolution, edit priority, and content pack labels. They also support new cases like easily detecting when an asset has changed, and avoid data corruption issues in some edge cases._ - * **Added `helper.GameContent` and `helper.ModContent`, which will replace `helper.Content` in SMAPI 4.0.0.** - * **Overhauled [mod-provided API](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations#Mod-provided_APIs) proxying** (thanks to Shockah!). - _This adds support for many previously unsupported cases: proxied interfaces in return values or input arguments, proxied enums if their values match, generic methods, and more. Existing mod APIs should work fine as-is._ - * **Deprecation warning:** The upcoming SMAPI 4.0 will remove deprecated APIs and break mods which haven't updated yet. + * **Major changes as part of the upcoming SMAPI 4.0.0:** + * Added [content events](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Events#Content), which will replace `IAssetEditor` and `IAssetLoader` in SMAPI 4.0.0. + _These include new features not supported by the old API like load conflict resolution, edit priority, and content pack labels. They also support new cases like easily detecting when an asset has changed, and avoid data corruption issues in some edge cases._ + * Added `helper.GameContent` and `helper.ModContent`, which will replace `helper.Content` in SMAPI 4.0.0. + * Overhauled [mod-provided API](https://stardewvalleywiki.com/Modding:Modder_Guide/APIs/Integrations#Mod-provided_APIs) proxying (thanks to Shockah!). + _This adds support for many previously unsupported cases: proxied interfaces in return values or input arguments, proxied enums if their values match, generic methods, and more. Existing mod APIs should work fine as-is._ + * Mod files loaded through SMAPI APIs (including `helper.Content.Load`) are now case-insensitive, even on Linux. + * **Deprecation warning:** The upcoming SMAPI 4.0 will remove deprecated APIs and break mods which haven't updated yet. _See [_Migrate to SMAPI 4.0_](https://stardewvalleywiki.com/Modding:Migrate_to_SMAPI_4.0) for help updating your mod code. You can update your mod code now, there's no need to wait for the 4.0.0 release (which will happen in at least three months, and possibly later if needed to update open-source mods)._ * Added `IContentPack.ModContent` property. * Added `Constants.ContentPath`. diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 8483d45d..cd1de4a8 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -15,7 +15,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 +80,9 @@ namespace StardewModdingAPI.Framework /// The cached asset load/edit operations to apply, indexed by asset name. private readonly TickCacheDictionary AssetOperationsByKey = new(); + /// The previously created case-insensitive path caches by root path. + private readonly Dictionary CaseInsensitivePathCaches = new(StringComparer.OrdinalIgnoreCase); + /********* ** Accessors @@ -91,9 +94,11 @@ namespace StardewModdingAPI.Framework public LocalizedContentManager.LanguageCode Language => this.MainContentManager.Language; /// Interceptors which provide the initial versions of matching assets. + [Obsolete] public IList> Loaders { get; } = new List>(); /// Interceptors which edit matching assets after they're loaded. + [Obsolete] public IList> Editors { get; } = new List>(); /// The absolute path to the . @@ -205,7 +210,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; @@ -477,6 +483,18 @@ namespace StardewModdingAPI.Framework }); } + /// Get a dictionary of relative paths within a root path, for case-insensitive file lookups. + /// The root path to scan. + 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; + } + /// Get the tilesheet ID order used by the unmodified version of a map asset. /// The asset path relative to the loader root directory, not including the .xnb extension. public TilesheetReference[] GetVanillaTilesheetIds(string assetName) diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 9ed989da..a451fd7c 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -9,7 +9,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 +32,9 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The game content manager used for map tilesheets not provided by the mod. private readonly IContentManager GameContentManager; + /// A case-insensitive lookup of relative paths within the . + private readonly CaseInsensitivePathCache RelativePathCache; + /// If a map tilesheet's image source has no file extensions, the file extensions to check for in the local mod folder. private readonly string[] LocalTilesheetExtensions = { ".png", ".xnb" }; @@ -52,10 +55,12 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Encapsulates SMAPI's JSON file parsing. /// A callback to invoke when the content manager is being disposed. /// Whether to enable more aggressive memory optimizations. - public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onDisposing, bool aggressiveMemoryOptimizations) + /// A case-insensitive lookup of relative paths within the . + public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action 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; @@ -198,7 +203,7 @@ 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, allowLocales: false); @@ -212,6 +217,9 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The asset path relative to the content folder. 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)); diff --git a/src/SMAPI/Framework/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs index 3920354e..e02ef88b 100644 --- a/src/SMAPI/Framework/ContentPack.cs +++ b/src/SMAPI/Framework/ContentPack.cs @@ -1,9 +1,8 @@ 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 +15,8 @@ namespace StardewModdingAPI.Framework /// Encapsulates SMAPI's JSON file parsing. private readonly JsonHelper JsonHelper; - /// A cache of case-insensitive => exact relative paths within the content pack, for case-insensitive file lookups on Linux/macOS. - private readonly IDictionary RelativePaths = new Dictionary(StringComparer.OrdinalIgnoreCase); + /// A case-insensitive lookup of relative paths within the . + private readonly CaseInsensitivePathCache RelativePathCache; /********* @@ -48,19 +47,15 @@ namespace StardewModdingAPI.Framework /// Provides an API for loading content assets from the content pack's folder. /// Provides translations stored in the content pack's i18n folder. /// Encapsulates SMAPI's JSON file parsing. - public ContentPack(string directoryPath, IManifest manifest, IModContentHelper content, TranslationHelper translation, JsonHelper jsonHelper) + /// A case-insensitive lookup of relative paths within the . + 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; } /// @@ -90,8 +85,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); } /// @@ -112,18 +106,6 @@ namespace StardewModdingAPI.Framework /********* ** Private methods *********/ - /// Get the real relative path from a case-insensitive path. - /// The normalized relative path. - 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; - } - /// Get the underlying file info. /// The normalized file path relative to the content pack directory. private FileInfo GetFile(string relativePath) @@ -136,7 +118,11 @@ namespace StardewModdingAPI.Framework /// The relative path after case-insensitive matching. 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/ModHelpers/ModContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs index 2379583c..7468cda1 100644 --- a/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs @@ -1,7 +1,9 @@ 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 +22,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The friendly mod name for use in errors. private readonly string ModName; + /// A case-insensitive lookup of relative paths within the . + private readonly CaseInsensitivePathCache RelativePathCache; + /********* ** Public methods @@ -30,7 +35,8 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The unique ID of the relevant mod. /// The friendly mod name for use in errors. /// The game content manager used for map tilesheets not provided by the mod. - public ModContentHelper(ContentCoordinator contentCore, string modFolderPath, string modID, string modName, IContentManager gameContentManager) + /// A case-insensitive lookup of relative paths within the . + public ModContentHelper(ContentCoordinator contentCore, string modFolderPath, string modID, string modName, IContentManager gameContentManager, CaseInsensitivePathCache relativePathCache) : base(modID) { string managedAssetPrefix = contentCore.GetManagedAssetPrefix(modID); @@ -38,11 +44,14 @@ namespace StardewModdingAPI.Framework.ModHelpers this.ContentCore = contentCore; this.ModContentManager = contentCore.CreateModContentManager(managedAssetPrefix, modName, modFolderPath, gameContentManager); this.ModName = modName; + this.RelativePathCache = relativePathCache; } /// public T Load(string relativePath) { + relativePath = this.RelativePathCache.GetAssetName(relativePath); + IAssetName assetName = this.ContentCore.ParseAssetName(relativePath, allowLocales: false); try @@ -58,6 +67,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// public IAssetName GetInternalAssetName(string relativePath) { + relativePath = this.RelativePathCache.GetAssetName(relativePath); return this.ModContentManager.GetInternalAssetKey(relativePath); } @@ -67,7 +77,9 @@ 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, allowLocales: false), data, key => this.ContentCore.ParseAssetName(key, allowLocales: false).Name); } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index b4aa3595..7fd5bcd3 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1775,10 +1775,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); @@ -1856,11 +1857,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)); return contentPack; @@ -1868,9 +1872,10 @@ namespace StardewModdingAPI.Framework IModEvents events = new ModEvents(mod, this.EventManager); ICommandHelper commandHelper = new CommandHelper(mod, this.CommandManager); + CaseInsensitivePathCache relativePathCache = this.ContentCore.GetCaseInsensitivePathCache(mod.DirectoryPath); IContentHelper contentHelper = new ContentHelper(contentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, monitor); 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(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/Utilities/CaseInsensitivePathCache.cs b/src/SMAPI/Utilities/CaseInsensitivePathCache.cs new file mode 100644 index 00000000..1d947b53 --- /dev/null +++ b/src/SMAPI/Utilities/CaseInsensitivePathCache.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace StardewModdingAPI.Utilities +{ + /// Provides an API for case-insensitive relative path lookups within a root directory. + internal class CaseInsensitivePathCache + { + /********* + ** Fields + *********/ + /// The root directory path for relative paths. + private readonly string RootPath; + + /// A case-insensitive lookup of file paths within the . 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. + private readonly Lazy> RelativePathCache; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The root directory path for relative paths. + public CaseInsensitivePathCache(string rootPath) + { + this.RootPath = rootPath; + this.RelativePathCache = new(this.GetRelativePathCache); + } + + /// Get the exact capitalization for a given relative file path. + /// The relative path. + /// Returns the resolved path in file path format, else the normalized . + public string GetFilePath(string relativePath) + { + return this.GetImpl(PathUtilities.NormalizePath(relativePath)); + } + + /// Get the exact capitalization for a given asset name. + /// The relative path. + /// Returns the resolved path in asset name format, else the normalized . + public string GetAssetName(string relativePath) + { + return this.GetImpl(PathUtilities.NormalizeAssetName(relativePath)); + } + + /// Add a relative path that was just created by a SMAPI API. + /// The relative path. This must already be normalized in asset name or file path format. + 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 + *********/ + /// Get the exact capitalization for a given relative path. + /// The relative path. This must already be normalized into asset name or file path format (i.e. using or respectively). + /// Returns the resolved path in the same format if found, else returns the path as-is. + 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; + } + + /// Get a case-insensitive lookup of file paths (see ). + private Dictionary GetRelativePathCache() + { + Dictionary 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; + } + + /// Add a raw relative path to the cache. + /// The cache to update. + /// The relative path to cache, with its exact filesystem capitalization. + private void CacheRawPath(IDictionary cache, string relativePath) + { + string filePath = PathUtilities.NormalizePath(relativePath); + string assetName = PathUtilities.NormalizeAssetName(relativePath); + + cache[filePath] = filePath; + cache[assetName] = assetName; + } + } +} -- cgit From c9af1c452aeab89aa39e6e1521154a7fcc648f5a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 3 Apr 2022 15:44:26 -0400 Subject: fix incorrect deprecation warnings for helper.Content --- src/SMAPI/Framework/ModHelpers/ModHelper.cs | 11 +++++++++-- src/SMAPI/Framework/SCore.cs | 9 +++++---- 2 files changed, 14 insertions(+), 6 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index d28faacc..5b567ee0 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -13,7 +13,7 @@ namespace StardewModdingAPI.Framework.ModHelpers *********/ /// The backing field for . [Obsolete] - private readonly IContentHelper ContentImpl; + private readonly ContentHelper ContentImpl; /********* @@ -93,7 +93,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// An API for reading translations stored in the mod's i18n folder. /// An argument is null or empty. /// The path does not exist on disk. - public ModHelper(string modID, string modDirectory, Func 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 currentInputState, IModEvents events, ContentHelper contentHelper, IGameContentHelper gameContentHelper, IModContentHelper modContentHelper, IContentPackHelper contentPackHelper, ICommandHelper commandHelper, IDataHelper dataHelper, IModRegistry modRegistry, IReflectionHelper reflectionHelper, IMultiplayerHelper multiplayer, ITranslationHelper translationHelper) : base(modID) { // validate directory @@ -118,6 +118,13 @@ namespace StardewModdingAPI.Framework.ModHelpers this.Events = events; } + /// Get the underlying instance for . + [Obsolete] + public ContentHelper GetLegacyContentHelper() + { + return this.ContentImpl; + } + /**** ** Mod config file ****/ diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index d0f5ffb4..1cc6a5b8 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1629,7 +1629,7 @@ namespace StardewModdingAPI.Framework 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) @@ -1657,8 +1657,9 @@ namespace StardewModdingAPI.Framework } // ReSharper restore SuspiciousTypeConversion.Global - helper.ObservableAssetEditors.CollectionChanged += (sender, e) => this.OnAssetInterceptorsChanged(metadata, e.NewItems?.Cast(), e.OldItems?.Cast(), this.ContentCore.Editors); - helper.ObservableAssetLoaders.CollectionChanged += (sender, e) => this.OnAssetInterceptorsChanged(metadata, e.NewItems?.Cast(), e.OldItems?.Cast(), this.ContentCore.Loaders); + ContentHelper content = helper.GetLegacyContentHelper(); + content.ObservableAssetEditors.CollectionChanged += (sender, e) => this.OnAssetInterceptorsChanged(metadata, e.NewItems?.Cast(), e.OldItems?.Cast(), this.ContentCore.Editors); + content.ObservableAssetLoaders.CollectionChanged += (sender, e) => this.OnAssetInterceptorsChanged(metadata, e.NewItems?.Cast(), e.OldItems?.Cast(), this.ContentCore.Loaders); } // call entry method @@ -1879,7 +1880,7 @@ namespace StardewModdingAPI.Framework IModEvents events = new ModEvents(mod, this.EventManager); ICommandHelper commandHelper = new CommandHelper(mod, this.CommandManager); CaseInsensitivePathCache relativePathCache = this.ContentCore.GetCaseInsensitivePathCache(mod.DirectoryPath); - IContentHelper contentHelper = new ContentHelper(contentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, monitor); + ContentHelper contentHelper = new(contentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, monitor); GameContentHelper gameContentHelper = new(contentCore, manifest.UniqueID, mod.DisplayName, monitor); IModContentHelper modContentHelper = new ModContentHelper(contentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager(), relativePathCache); IContentPackHelper contentPackHelper = new ContentPackHelper(manifest.UniqueID, new Lazy(GetContentPacks), CreateFakeContentPack); -- cgit From b6c8cfc28b2c94e6dc3cb07d3058371dd6775e70 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 6 Apr 2022 18:24:59 -0400 Subject: simplify 'is not' patterns --- src/SMAPI.Installer/InteractiveInstaller.cs | 2 +- .../Framework/Commands/World/ClearCommand.cs | 4 ++-- src/SMAPI.Tests/Utilities/SemanticVersionTests.cs | 2 +- src/SMAPI/Context.cs | 4 ++-- src/SMAPI/Framework/Content/AssetDataForObject.cs | 2 +- src/SMAPI/Framework/Content/AssetInterceptorChange.cs | 2 +- src/SMAPI/Framework/ContentManagers/GameContentManager.cs | 2 +- src/SMAPI/Framework/Events/ManagedEventHandler.cs | 2 +- src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 2 +- src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs | 2 +- src/SMAPI/Framework/SCore.cs | 2 +- 11 files changed, 13 insertions(+), 13 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index a126c3b8..49ca80a5 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -808,7 +808,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.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs index ceaeb278..d388f4b0 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs @@ -92,7 +92,7 @@ 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" @@ -141,7 +141,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World 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.RemoveLargeTerrainFeatures(location, p => everything || p is not Bush bush || bush.isDestroyable(location, p.currentTileLocation)) + this.RemoveResourceClumps(location, p => true); monitor.Log($"Done! Removed {removed} entities from {location.Name}.", LogLevel.Info); break; diff --git a/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs b/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs index c8270373..142c9814 100644 --- a/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs +++ b/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs @@ -455,7 +455,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/Context.cs b/src/SMAPI/Context.cs index 4fbc9c42..98d60b78 100644 --- a/src/SMAPI/Context.cs +++ b/src/SMAPI/Context.cs @@ -20,7 +20,7 @@ namespace StardewModdingAPI private static readonly PerScreen LoadStageForScreen = new(); /// Whether a player save has been loaded. - internal static bool IsSaveLoaded => Game1.hasLoadedGame && !(Game1.activeClickableMenu is TitleMenu); + internal static bool IsSaveLoaded => Game1.hasLoadedGame && Game1.activeClickableMenu is not TitleMenu; /// Whether the game is currently writing to the save file. 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 @@ -86,7 +86,7 @@ namespace StardewModdingAPI public static bool HasRemotePlayers => Context.IsMultiplayer && !Game1.hasLocalClientsOnly; /// Whether the current player is the main player. This is always true in single-player, and true when hosting in multiplayer. - 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/Framework/Content/AssetDataForObject.cs b/src/SMAPI/Framework/Content/AssetDataForObject.cs index d91873ae..40f49190 100644 --- a/src/SMAPI/Framework/Content/AssetDataForObject.cs +++ b/src/SMAPI/Framework/Content/AssetDataForObject.cs @@ -47,7 +47,7 @@ namespace StardewModdingAPI.Framework.Content /// public TData GetData() { - if (!(this.Data is TData)) + if (this.Data is not TData) 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; } diff --git a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs index 981eed40..03d6da5a 100644 --- a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs +++ b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs @@ -36,7 +36,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/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index 3d37e32a..cad5f6db 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -235,7 +235,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/Events/ManagedEventHandler.cs b/src/SMAPI/Framework/Events/ManagedEventHandler.cs index 28e88be0..97040f76 100644 --- a/src/SMAPI/Framework/Events/ManagedEventHandler.cs +++ b/src/SMAPI/Framework/Events/ManagedEventHandler.cs @@ -42,7 +42,7 @@ namespace StardewModdingAPI.Framework.Events /// public int CompareTo(object obj) { - if (!(obj is ManagedEventHandler other)) + if (obj is not ManagedEventHandler 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/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index ae914c46..14aa74c2 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -124,7 +124,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); } diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs index 922d4bc4..f715fb6b 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs @@ -34,7 +34,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters public override bool Handle(ModuleDefinition module, TypeReference type, Action 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/SCore.cs b/src/SMAPI/Framework/SCore.cs index 1cc6a5b8..c8e0842e 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -580,7 +580,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; -- cgit From 077d8e4f401ad1806c6af0540f432366314a2300 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 6 Apr 2022 18:25:00 -0400 Subject: remove some unused/redundant code --- src/SMAPI.Installer/InteractiveInstaller.cs | 2 +- src/SMAPI.Internal/ExceptionHelper.cs | 11 +------ .../Framework/DiagnosticVerifier.cs | 2 +- .../NetFieldAnalyzer.cs | 3 -- .../ObsoleteFieldAnalyzer.cs | 2 +- .../Framework/Commands/ConsoleCommand.cs | 2 +- .../Commands/Other/ApplySaveFixCommand.cs | 2 ++ .../Framework/Commands/Other/DebugCommand.cs | 4 ++- .../Framework/Commands/Other/RegenerateBundles.cs | 2 ++ .../Commands/Other/ShowDataFilesCommand.cs | 4 ++- .../Commands/Other/ShowGameFilesCommand.cs | 2 ++ .../Framework/Commands/Other/TestInputCommand.cs | 6 ++-- .../Commands/Player/ListItemTypesCommand.cs | 4 ++- .../Framework/Commands/Player/ListItemsCommand.cs | 2 ++ .../Framework/Commands/Player/SetColorCommand.cs | 2 ++ .../Commands/Player/SetFarmTypeCommand.cs | 2 ++ .../Framework/Commands/Player/SetHealthCommand.cs | 2 ++ .../Commands/Player/SetImmunityCommand.cs | 4 ++- .../Commands/Player/SetMaxHealthCommand.cs | 4 ++- .../Commands/Player/SetMaxStaminaCommand.cs | 4 ++- .../Framework/Commands/Player/SetMoneyCommand.cs | 2 ++ .../Framework/Commands/Player/SetNameCommand.cs | 2 ++ .../Framework/Commands/Player/SetStaminaCommand.cs | 2 ++ .../Framework/Commands/Player/SetStyleCommand.cs | 2 ++ .../Framework/Commands/World/ClearCommand.cs | 12 ++++--- .../Commands/World/DownMineLevelCommand.cs | 2 ++ .../Framework/Commands/World/HurryAllCommand.cs | 2 ++ .../Framework/Commands/World/SetDayCommand.cs | 4 ++- .../Commands/World/SetMineLevelCommand.cs | 2 ++ .../Framework/Commands/World/SetSeasonCommand.cs | 2 ++ .../Framework/Commands/World/SetTimeCommand.cs | 2 ++ .../Framework/Commands/World/SetYearCommand.cs | 4 ++- .../Framework/ItemData/SearchableItem.cs | 10 ------ .../Framework/ItemRepository.cs | 2 +- .../Patches/SaveGamePatcher.cs | 2 +- src/SMAPI.Mods.SaveBackup/ModEntry.cs | 6 ++-- src/SMAPI.Tests.ModApiConsumer/ApiConsumer.cs | 4 +-- src/SMAPI.Tests/Core/AssetNameTests.cs | 4 +-- src/SMAPI.Tests/Core/ModResolverTests.cs | 14 ++++---- .../WikiClient/ChangeDescriptorTests.cs | 1 - .../Framework/Clients/Wiki/WikiClient.cs | 2 +- src/SMAPI.Toolkit/Framework/ModData/ModDatabase.cs | 2 +- .../Controllers/JsonValidatorController.cs | 11 +++---- .../Framework/Caching/Mods/IModCacheRepository.cs | 1 - .../Caching/Mods/ModCacheMemoryRepository.cs | 1 - src/SMAPI.Web/Framework/LogParsing/LogParser.cs | 2 +- src/SMAPI.Web/Framework/ModInfoModel.cs | 2 -- .../RedirectRules/RedirectHostsToUrlsRule.cs | 2 -- .../Framework/RedirectRules/RedirectToHttpsRule.cs | 2 +- src/SMAPI.Web/Startup.cs | 2 +- src/SMAPI.Web/Views/LogParser/Index.cshtml | 6 ++-- src/SMAPI.Web/wwwroot/Content/css/file-upload.css | 2 +- src/SMAPI/Constants.cs | 3 -- src/SMAPI/Framework/CommandManager.cs | 2 +- src/SMAPI/Framework/Content/AssetDataForMap.cs | 2 +- src/SMAPI/Framework/Content/AssetName.cs | 2 +- src/SMAPI/Framework/Content/TilesheetReference.cs | 1 - .../ContentManagers/BaseContentManager.cs | 2 +- .../Framework/ContentManagers/ModContentManager.cs | 2 +- src/SMAPI/Framework/Input/SInputState.cs | 2 +- src/SMAPI/Framework/InternalExtensions.cs | 1 - .../Framework/ModHelpers/ModRegistryHelper.cs | 2 +- src/SMAPI/Framework/ModLoading/AssemblyLoader.cs | 4 +-- src/SMAPI/Framework/ModLoading/ModResolver.cs | 2 +- src/SMAPI/Framework/ModRegistry.cs | 2 -- src/SMAPI/Framework/Monitor.cs | 2 +- src/SMAPI/Framework/SCore.cs | 16 ++++----- src/SMAPI/Framework/SMultiplayer.cs | 38 +++++++++++----------- .../Framework/Serialization/KeybindConverter.cs | 2 +- .../StateTracking/Snapshots/PlayerSnapshot.cs | 2 +- .../Framework/TemporaryHacks/MiniMonoModHotfix.cs | 2 +- src/SMAPI/Program.cs | 2 +- src/SMAPI/Translation.cs | 2 +- src/SMAPI/Utilities/PerScreen.cs | 2 +- 74 files changed, 143 insertions(+), 134 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI.Installer/InteractiveInstaller.cs b/src/SMAPI.Installer/InteractiveInstaller.cs index 49ca80a5..09183b5d 100644 --- a/src/SMAPI.Installer/InteractiveInstaller.cs +++ b/src/SMAPI.Installer/InteractiveInstaller.cs @@ -562,7 +562,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) diff --git a/src/SMAPI.Internal/ExceptionHelper.cs b/src/SMAPI.Internal/ExceptionHelper.cs index 03d48911..6bd1d579 100644 --- a/src/SMAPI.Internal/ExceptionHelper.cs +++ b/src/SMAPI.Internal/ExceptionHelper.cs @@ -25,7 +25,7 @@ namespace StardewModdingAPI.Internal case ReflectionTypeLoadException ex: string summary = ex.ToString(); - foreach (Exception childEx in ex.LoaderExceptions ?? Array.Empty()) + foreach (Exception childEx in ex.LoaderExceptions) summary += $"\n\n{childEx?.GetLogSummary()}"; message = summary; break; @@ -43,15 +43,6 @@ namespace StardewModdingAPI.Internal } } - /// Get the lowest exception in an exception stack. - /// The exception from which to search. - public static Exception GetInnermostException(this Exception exception) - { - while (exception.InnerException != null) - exception = exception.InnerException; - return exception; - } - /// Simplify common patterns in exception log messages that don't convey useful info. /// The log message to simplify. public static string SimplifyExtensionMessage(string message) diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.cs index edaaabd4..49697dfa 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.cs @@ -201,7 +201,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/NetFieldAnalyzer.cs b/src/SMAPI.ModBuildConfig.Analyzer/NetFieldAnalyzer.cs index 8478dc54..553aae99 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer/NetFieldAnalyzer.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer/NetFieldAnalyzer.cs @@ -227,10 +227,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 3184147a..ba089513 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer/ObsoleteFieldAnalyzer.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer/ObsoleteFieldAnalyzer.cs @@ -77,7 +77,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.Mods.ConsoleCommands/Framework/Commands/ConsoleCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ConsoleCommand.cs index 01cab92e..44b7824e 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ConsoleCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ConsoleCommand.cs @@ -100,7 +100,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands List lines = new List(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/Other/ApplySaveFixCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ApplySaveFixCommand.cs index 957b0e75..b02ba4b1 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ApplySaveFixCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ApplySaveFixCommand.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other { /// A command which runs one of the game's save migrations. + [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..cf1dcbce 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/DebugCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/DebugCommand.cs @@ -1,8 +1,10 @@ -using StardewValley; +using System.Diagnostics.CodeAnalysis; +using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other { /// A command which sends a debug command to the game. + [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..159d7c4a 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/RegenerateBundles.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/RegenerateBundles.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using Netcode; @@ -9,6 +10,7 @@ using StardewValley.Network; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other { /// A command which regenerates the game's bundles. + [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..a233d588 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowDataFilesCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowDataFilesCommand.cs @@ -1,8 +1,10 @@ -using System.Diagnostics; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other { /// A command which shows the data files. + [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..745b821b 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowGameFilesCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowGameFilesCommand.cs @@ -1,8 +1,10 @@ using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other { /// A command which shows the game files. + [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..8bf9f5db 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/TestInputCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/TestInputCommand.cs @@ -1,8 +1,10 @@ using System; +using System.Diagnostics.CodeAnalysis; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Other { /// A command which logs the keys being pressed for 30 seconds once enabled. + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class TestInputCommand : ConsoleCommand { /********* @@ -37,9 +39,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/ListItemTypesCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemTypesCommand.cs index af362bcd..ef35ad19 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemTypesCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemTypesCommand.cs @@ -1,9 +1,11 @@ -using System.Linq; +using System.Diagnostics.CodeAnalysis; +using System.Linq; using StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// A command which list item types. + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class ListItemTypesCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemsCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemsCommand.cs index 46fc1d9d..5cc464fe 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemsCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemsCommand.cs @@ -1,11 +1,13 @@ 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 { /// A command which list items available to spawn. + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class ListItemsCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetColorCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetColorCommand.cs index 7b7cbf83..af7f2d18 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetColorCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetColorCommand.cs @@ -1,9 +1,11 @@ +using System.Diagnostics.CodeAnalysis; using Microsoft.Xna.Framework; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// A command which edits the color of a player feature. + [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..2809df9c 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetFarmTypeCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetFarmTypeCommand.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Text; @@ -10,6 +11,7 @@ using StardewValley.GameData; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// A command which changes the player's farm type. + [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..f169159f 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetHealthCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetHealthCommand.cs @@ -1,9 +1,11 @@ +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// A command which edits the player's current health. + [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..1065bd21 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetImmunityCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetImmunityCommand.cs @@ -1,9 +1,11 @@ -using System.Linq; +using System.Diagnostics.CodeAnalysis; +using System.Linq; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// A command which edits the player's current immunity. + [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..c2c4931d 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxHealthCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxHealthCommand.cs @@ -1,9 +1,11 @@ -using System.Linq; +using System.Diagnostics.CodeAnalysis; +using System.Linq; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// A command which edits the player's maximum health. + [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..8c794e75 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxStaminaCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxStaminaCommand.cs @@ -1,9 +1,11 @@ -using System.Linq; +using System.Diagnostics.CodeAnalysis; +using System.Linq; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// A command which edits the player's maximum stamina. + [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..3afcc62b 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMoneyCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMoneyCommand.cs @@ -1,9 +1,11 @@ +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// A command which edits the player's current money. + [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..12d6b6e8 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetNameCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetNameCommand.cs @@ -1,8 +1,10 @@ +using System.Diagnostics.CodeAnalysis; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// A command which edits the player's name. + [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..24718ace 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStaminaCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStaminaCommand.cs @@ -1,9 +1,11 @@ +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// A command which edits the player's current stamina. + [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..558c327d 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStyleCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStyleCommand.cs @@ -1,8 +1,10 @@ +using System.Diagnostics.CodeAnalysis; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.Player { /// A command which edits a player style. + [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 7935b05f..eeb95553 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewValley; using StardewValley.Locations; @@ -9,6 +10,7 @@ using SObject = StardewValley.Object; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World { /// A command which clears in-game objects. + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class ClearCommand : ConsoleCommand { /********* @@ -113,7 +115,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; } @@ -137,11 +139,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.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, p => true); + + 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..5b1a4a13 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/DownMineLevelCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/DownMineLevelCommand.cs @@ -1,9 +1,11 @@ +using System.Diagnostics.CodeAnalysis; using StardewValley; using StardewValley.Locations; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World { /// A command which moves the player to the next mine level. + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "Loaded using reflection")] internal class DownMineLevelCommand : ConsoleCommand { /********* diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/HurryAllCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/HurryAllCommand.cs index 2deac5f8..09531720 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/HurryAllCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/HurryAllCommand.cs @@ -1,9 +1,11 @@ using System; +using System.Diagnostics.CodeAnalysis; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World { /// A command which immediately warps all NPCs to their scheduled positions. To hurry a single NPC, see debug hurry npc-name instead. + [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..399fd934 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetDayCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetDayCommand.cs @@ -1,10 +1,12 @@ -using System.Linq; +using System.Diagnostics.CodeAnalysis; +using System.Linq; using StardewModdingAPI.Utilities; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World { /// A command which sets the current day. + [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..f977fce3 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetMineLevelCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetMineLevelCommand.cs @@ -1,9 +1,11 @@ using System; +using System.Diagnostics.CodeAnalysis; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World { /// A command which moves the player to the given mine level. + [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..d839c037 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetSeasonCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetSeasonCommand.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewModdingAPI.Utilities; using StardewValley; @@ -5,6 +6,7 @@ using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World { /// A command which sets the current season. + [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..8c4458dd 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.Xna.Framework; using StardewValley; @@ -5,6 +6,7 @@ using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World { /// A command which sets the current time. + [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..a666a634 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetYearCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetYearCommand.cs @@ -1,10 +1,12 @@ -using System.Linq; +using System.Diagnostics.CodeAnalysis; +using System.Linq; using StardewModdingAPI.Utilities; using StardewValley; namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.Commands.World { /// A command which sets the current year. + [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..3675a963 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs @@ -43,16 +43,6 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData this.Item = createItem(this); } - /// Construct an instance. - /// The item metadata to copy. - public SearchableItem(SearchableItem item) - { - this.Type = item.Type; - this.ID = item.ID; - this.CreateItem = item.CreateItem; - this.Item = item.Item; - } - /// Get whether the item name contains a case-insensitive substring. /// The substring to find. public bool NameContains(string substring) diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs index ca313f41..3915db9a 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs @@ -107,7 +107,7 @@ namespace StardewModdingAPI.Mods.ConsoleCommands.Framework foreach (int id in this.TryLoad("Data\\weapons").Keys) { yield return this.TryCreate(ItemType.Weapon, id, p => p.ID is >= 32 and <= 34 - ? (Item)new Slingshot(p.ID) + ? new Slingshot(p.ID) : new MeleeWeapon(p.ID) ); } diff --git a/src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs b/src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs index 5f6dbcb3..01bfb888 100644 --- a/src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs +++ b/src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs @@ -77,7 +77,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); diff --git a/src/SMAPI.Mods.SaveBackup/ModEntry.cs b/src/SMAPI.Mods.SaveBackup/ModEntry.cs index f6925707..273b1434 100644 --- a/src/SMAPI.Mods.SaveBackup/ModEntry.cs +++ b/src/SMAPI.Mods.SaveBackup/ModEntry.cs @@ -44,7 +44,7 @@ namespace StardewModdingAPI.Mods.SaveBackup // 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) { @@ -170,8 +170,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."); diff --git a/src/SMAPI.Tests.ModApiConsumer/ApiConsumer.cs b/src/SMAPI.Tests.ModApiConsumer/ApiConsumer.cs index 2c7f9952..ac7bd338 100644 --- a/src/SMAPI.Tests.ModApiConsumer/ApiConsumer.cs +++ b/src/SMAPI.Tests.ModApiConsumer/ApiConsumer.cs @@ -17,7 +17,7 @@ namespace SMAPI.Tests.ModApiConsumer // act int calls = 0; int lastValue = -1; - api.OnEventRaised += (sender, value) => + api.OnEventRaised += (_, value) => { calls++; lastValue = value; @@ -34,7 +34,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/Core/AssetNameTests.cs b/src/SMAPI.Tests/Core/AssetNameTests.cs index 8785aab8..7b817d13 100644 --- a/src/SMAPI.Tests/Core/AssetNameTests.cs +++ b/src/SMAPI.Tests/Core/AssetNameTests.cs @@ -33,7 +33,7 @@ namespace SMAPI.Tests.Core // 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 +161,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/ModResolverTests.cs b/src/SMAPI.Tests/Core/ModResolverTests.cs index 1755f644..86c50606 100644 --- a/src/SMAPI.Tests/Core/ModResolverTests.cs +++ b/src/SMAPI.Tests/Core/ModResolverTests.cs @@ -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(), apiVersion: new SemanticVersion("1.0"), getUpdateUrl: key => null); + new ModResolver().ValidateManifests(Array.Empty(), 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(), It.IsAny(), It.IsAny()), 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(), It.IsAny(), It.IsAny()), 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(), It.IsAny(), It.IsAny()), 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(), It.IsAny(), It.IsAny()), Times.Once, "The validation did not fail the first mod with a unique ID."); @@ -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. diff --git a/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs b/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs index b896b09c..84cae8df 100644 --- a/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs +++ b/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using StardewModdingAPI; using StardewModdingAPI.Toolkit.Framework.Clients.Wiki; diff --git a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs index abbcdc81..c936bb3e 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs @@ -65,7 +65,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) diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDatabase.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDatabase.cs index 5b7e2a02..9bb3f558 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModDatabase.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModDatabase.cs @@ -22,7 +22,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData *********/ /// Construct an empty instance. public ModDatabase() - : this(Array.Empty(), key => null) { } + : this(Array.Empty(), _ => null) { } /// Construct an instance. /// The underlying mod data records indexed by default display name. diff --git a/src/SMAPI.Web/Controllers/JsonValidatorController.cs b/src/SMAPI.Web/Controllers/JsonValidatorController.cs index bcd4097a..985f91ae 100644 --- a/src/SMAPI.Web/Controllers/JsonValidatorController.cs +++ b/src/SMAPI.Web/Controllers/JsonValidatorController.cs @@ -161,7 +161,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 })); } @@ -317,13 +317,10 @@ namespace StardewModdingAPI.Web.Controllers /// The case-insensitive field key. private T GetExtensionField(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(); - } + if (curKey.Equals(key, StringComparison.OrdinalIgnoreCase)) + return value.ToObject(); } return default; diff --git a/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs b/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs index 0d912c7b..a16e6b73 100644 --- a/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs +++ b/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs @@ -1,6 +1,5 @@ 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..f871a9da 100644 --- a/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs +++ b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs @@ -2,7 +2,6 @@ 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/LogParsing/LogParser.cs b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs index 6a3ea222..864caef1 100644 --- a/src/SMAPI.Web/Framework/LogParsing/LogParser.cs +++ b/src/SMAPI.Web/Framework/LogParsing/LogParser.cs @@ -211,7 +211,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; diff --git a/src/SMAPI.Web/Framework/ModInfoModel.cs b/src/SMAPI.Web/Framework/ModInfoModel.cs index 7845b8c5..86f70788 100644 --- a/src/SMAPI.Web/Framework/ModInfoModel.cs +++ b/src/SMAPI.Web/Framework/ModInfoModel.cs @@ -1,5 +1,3 @@ -using StardewModdingAPI.Web.Framework.Clients; - namespace StardewModdingAPI.Web.Framework { /// Generic metadata about a mod. diff --git a/src/SMAPI.Web/Framework/RedirectRules/RedirectHostsToUrlsRule.cs b/src/SMAPI.Web/Framework/RedirectRules/RedirectHostsToUrlsRule.cs index d75ee791..d67b5156 100644 --- a/src/SMAPI.Web/Framework/RedirectRules/RedirectHostsToUrlsRule.cs +++ b/src/SMAPI.Web/Framework/RedirectRules/RedirectHostsToUrlsRule.cs @@ -37,8 +37,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/RedirectToHttpsRule.cs b/src/SMAPI.Web/Framework/RedirectRules/RedirectToHttpsRule.cs index 2a503ae3..265a605f 100644 --- a/src/SMAPI.Web/Framework/RedirectRules/RedirectToHttpsRule.cs +++ b/src/SMAPI.Web/Framework/RedirectRules/RedirectToHttpsRule.cs @@ -22,7 +22,7 @@ namespace StardewModdingAPI.Web.Framework.RedirectRules /// Matches requests which should be ignored. public RedirectToHttpsRule(Func except = null) { - this.Except = except ?? (req => false); + this.Except = except ?? (_ => false); this.StatusCode = HttpStatusCode.RedirectKeepVerb; } diff --git a/src/SMAPI.Web/Startup.cs b/src/SMAPI.Web/Startup.cs index d8561172..6d9591ee 100644 --- a/src/SMAPI.Web/Startup.cs +++ b/src/SMAPI.Web/Startup.cs @@ -81,7 +81,7 @@ namespace StardewModdingAPI.Web // init Hangfire services - .AddHangfire((serv, config) => + .AddHangfire((_, config) => { config .SetDataCompatibilityLevel(CompatibilityLevel.Version_170) diff --git a/src/SMAPI.Web/Views/LogParser/Index.cshtml b/src/SMAPI.Web/Views/LogParser/Index.cshtml index b54867b1..3ddc6303 100644 --- a/src/SMAPI.Web/Views/LogParser/Index.cshtml +++ b/src/SMAPI.Web/Views/LogParser/Index.cshtml @@ -40,8 +40,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 +207,7 @@ else if (log?.IsValid == true) @if (mod.HasUpdate) { - @(mod.Version == null ? @mod.UpdateVersion : $"{mod.Version} → {mod.UpdateVersion}") + @(mod.Version == null ? mod.UpdateVersion : $"{mod.Version} → {mod.UpdateVersion}") } else 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/Constants.cs b/src/SMAPI/Constants.cs index 76f4ef87..6b9e9b05 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -170,9 +170,6 @@ namespace StardewModdingAPI /// The target game platform as a SMAPI toolkit constant. internal static Platform Platform { get; } = (Platform)Constants.TargetPlatform; - /// The language code for non-translated mod assets. - internal static LocalizedContentManager.LanguageCode DefaultLanguage { get; } = LocalizedContentManager.LanguageCode.en; - /********* ** Internal methods diff --git a/src/SMAPI/Framework/CommandManager.cs b/src/SMAPI/Framework/CommandManager.cs index a7b64cb7..0c0f6685 100644 --- a/src/SMAPI/Framework/CommandManager.cs +++ b/src/SMAPI/Framework/CommandManager.cs @@ -65,7 +65,7 @@ namespace StardewModdingAPI.Framework /// There's already a command with that name. 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)); } /// Get a command by its unique name. diff --git a/src/SMAPI/Framework/Content/AssetDataForMap.cs b/src/SMAPI/Framework/Content/AssetDataForMap.cs index 0458f80a..5986e797 100644 --- a/src/SMAPI/Framework/Content/AssetDataForMap.cs +++ b/src/SMAPI/Framework/Content/AssetDataForMap.cs @@ -147,7 +147,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/AssetName.cs b/src/SMAPI/Framework/Content/AssetName.cs index a1d37b0b..4973b444 100644 --- a/src/SMAPI/Framework/Content/AssetName.cs +++ b/src/SMAPI/Framework/Content/AssetName.cs @@ -107,7 +107,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/TilesheetReference.cs b/src/SMAPI/Framework/Content/TilesheetReference.cs index 0919bb44..0339b802 100644 --- a/src/SMAPI/Framework/Content/TilesheetReference.cs +++ b/src/SMAPI/Framework/Content/TilesheetReference.cs @@ -1,4 +1,3 @@ -using System.Numerics; using xTile.Dimensions; namespace StardewModdingAPI.Framework.Content diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index 31199b3a..c803905a 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -308,7 +308,7 @@ namespace StardewModdingAPI.Framework.ContentManagers { return useCache ? base.LoadBase(assetName.Name) - : base.ReadAsset(assetName.Name, disposable => this.Disposables.Add(new WeakReference(disposable))); + : this.ReadAsset(assetName.Name, disposable => this.Disposables.Add(new WeakReference(disposable))); } /// Add tracking data to an asset and add it to the cache. diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 63070d85..e0c85265 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -124,7 +124,7 @@ namespace StardewModdingAPI.Framework.ContentManagers } // track & return asset - this.TrackAsset(assetName, asset, useCache); + this.TrackAsset(assetName, asset, useCache: false); return asset; } diff --git a/src/SMAPI/Framework/Input/SInputState.cs b/src/SMAPI/Framework/Input/SInputState.cs index d5feaf94..72adca02 100644 --- a/src/SMAPI/Framework/Input/SInputState.cs +++ b/src/SMAPI/Framework/Input/SInputState.cs @@ -73,7 +73,7 @@ namespace StardewModdingAPI.Framework.Input var keyboard = new KeyboardStateBuilder(base.GetKeyboardState()); var mouse = new MouseStateBuilder(base.GetMouseState()); Vector2 cursorAbsolutePos = new((mouse.X * zoomMultiplier) + Game1.viewport.X, (mouse.Y * zoomMultiplier) + Game1.viewport.Y); - Vector2? playerTilePos = Context.IsPlayerFree ? Game1.player.getTileLocation() : (Vector2?)null; + Vector2? playerTilePos = Context.IsPlayerFree ? Game1.player.getTileLocation() : null; HashSet reallyDown = new HashSet(this.GetPressedButtons(keyboard, mouse, controller)); // apply overrides diff --git a/src/SMAPI/Framework/InternalExtensions.cs b/src/SMAPI/Framework/InternalExtensions.cs index fe10b045..54aeffd7 100644 --- a/src/SMAPI/Framework/InternalExtensions.cs +++ b/src/SMAPI/Framework/InternalExtensions.cs @@ -1,7 +1,6 @@ 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/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 95eb03f3..09a392a6 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -69,7 +69,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/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index 4480f4e8..24214c96 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -138,7 +138,7 @@ 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(); @@ -150,7 +150,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); } diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index 21366bb4..d52cdbb4 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -237,7 +237,7 @@ namespace StardewModdingAPI.Framework.ModLoading // initialize metadata mods = mods.ToArray(); var sortedMods = new Stack(); - 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)) diff --git a/src/SMAPI/Framework/ModRegistry.cs b/src/SMAPI/Framework/ModRegistry.cs index 99548cf8..c0f8d537 100644 --- a/src/SMAPI/Framework/ModRegistry.cs +++ b/src/SMAPI/Framework/ModRegistry.cs @@ -96,8 +96,6 @@ namespace StardewModdingAPI.Framework // get stack frames 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/Monitor.cs b/src/SMAPI/Framework/Monitor.cs index ab76e7c0..6b53daff 100644 --- a/src/SMAPI/Framework/Monitor.cs +++ b/src/SMAPI/Framework/Monitor.cs @@ -92,7 +92,7 @@ namespace StardewModdingAPI.Framework public void VerboseLog(string message) { if (this.IsVerbose) - this.Log(message, LogLevel.Trace); + this.Log(message); } /// Write a newline to the console and log file. diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index c8e0842e..cc531de9 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -232,13 +232,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); @@ -1453,14 +1453,12 @@ namespace StardewModdingAPI.Framework // 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; + ISemanticVersion updateFound = response.SuggestedUpdate?.Version; + string updateUrl = response.SuggestedUpdate?.Url ?? Constants.HomePageUrl; // log message if (updateFound != null) @@ -1658,8 +1656,8 @@ namespace StardewModdingAPI.Framework // ReSharper restore SuspiciousTypeConversion.Global ContentHelper content = helper.GetLegacyContentHelper(); - content.ObservableAssetEditors.CollectionChanged += (sender, e) => this.OnAssetInterceptorsChanged(metadata, e.NewItems?.Cast(), e.OldItems?.Cast(), this.ContentCore.Editors); - content.ObservableAssetLoaders.CollectionChanged += (sender, e) => this.OnAssetInterceptorsChanged(metadata, e.NewItems?.Cast(), e.OldItems?.Cast(), this.ContentCore.Loaders); + content.ObservableAssetEditors.CollectionChanged += (_, e) => this.OnAssetInterceptorsChanged(metadata, e.NewItems?.Cast(), e.OldItems?.Cast(), this.ContentCore.Editors); + content.ObservableAssetLoaders.CollectionChanged += (_, e) => this.OnAssetInterceptorsChanged(metadata, e.NewItems?.Cast(), e.OldItems?.Cast(), this.ContentCore.Loaders); } // call entry method diff --git a/src/SMAPI/Framework/SMultiplayer.cs b/src/SMAPI/Framework/SMultiplayer.cs index 3981e4e5..bcf97006 100644 --- a/src/SMAPI/Framework/SMultiplayer.cs +++ b/src/SMAPI/Framework/SMultiplayer.cs @@ -111,20 +111,20 @@ namespace StardewModdingAPI.Framework { switch (client) { - case LidgrenClient _: + case LidgrenClient: { string address = this.Reflection.GetField(client, "address").GetValue(); return new SLidgrenClient(address, this.OnClientProcessingMessage, this.OnClientSendingMessage); } - case GalaxyNetClient _: + case GalaxyNetClient: { GalaxyID address = this.Reflection.GetField(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 +135,20 @@ namespace StardewModdingAPI.Framework { switch (server) { - case LidgrenServer _: + case LidgrenServer: { IGameServer gameServer = this.Reflection.GetField(server, "gameServer").GetValue(); return new SLidgrenServer(gameServer, this, this.OnServerProcessingMessage); } - case GalaxyNetServer _: + case GalaxyNetServer: { IGameServer gameServer = this.Reflection.GetField(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 +160,7 @@ namespace StardewModdingAPI.Framework protected void OnClientSendingMessage(OutgoingMessage message, Action 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 +184,7 @@ namespace StardewModdingAPI.Framework public void OnServerProcessingMessage(IncomingMessage message, Action 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,7 +193,7 @@ 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( @@ -243,7 +243,7 @@ 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); + this.Monitor.Log($"Received connection for vanilla player {message.FarmerID}."); MultiplayerPeer peer = new( playerID: message.FarmerID, screenID: this.GetScreenId(message.FarmerID), @@ -280,7 +280,7 @@ namespace StardewModdingAPI.Framework public void OnClientProcessingMessage(IncomingMessage message, Action 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,7 +289,7 @@ 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( @@ -314,7 +314,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 +341,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 +367,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)); } @@ -434,7 +434,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 +447,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 +455,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 +504,7 @@ namespace StardewModdingAPI.Framework ModMessageModel model = this.JsonHelper.Deserialize(json); HashSet playerIDs = new HashSet(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)) diff --git a/src/SMAPI/Framework/Serialization/KeybindConverter.cs b/src/SMAPI/Framework/Serialization/KeybindConverter.cs index 93a274a8..7c5db3ad 100644 --- a/src/SMAPI/Framework/Serialization/KeybindConverter.cs +++ b/src/SMAPI/Framework/Serialization/KeybindConverter.cs @@ -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: diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs index f3e42948..e113d27c 100644 --- a/src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs +++ b/src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs @@ -31,7 +31,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots Enum .GetValues(typeof(SkillType)) .Cast() - .ToDictionary(skill => skill, skill => new SnapshotDiff()); + .ToDictionary(skill => skill, _ => new SnapshotDiff()); /// Get a list of inventory changes. public SnapshotItemListDiff Inventory { get; private set; } diff --git a/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs index cab1a94c..c0f119f1 100644 --- a/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs +++ b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs @@ -126,7 +126,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) { diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 29f4be1b..1039cc9a 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -78,7 +78,7 @@ namespace StardewModdingAPI } catch { - continue; + // ignore invalid DLL } } } diff --git a/src/SMAPI/Translation.cs b/src/SMAPI/Translation.cs index 149f6728..5ab432f0 100644 --- a/src/SMAPI/Translation.cs +++ b/src/SMAPI/Translation.cs @@ -74,7 +74,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/PerScreen.cs b/src/SMAPI/Utilities/PerScreen.cs index 20b8fbce..6b7153ac 100644 --- a/src/SMAPI/Utilities/PerScreen.cs +++ b/src/SMAPI/Utilities/PerScreen.cs @@ -76,7 +76,7 @@ namespace StardewModdingAPI.Utilities /// Remove all active values. public void ResetAllScreens() { - this.RemoveScreens(p => true); + this.RemoveScreens(_ => true); } -- cgit From 2e7c233f6c9bf6430672b39f970a3324deba79dd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 6 Apr 2022 21:48:55 -0400 Subject: enable nullable annotations by default (#837) This adds `#nullable disable` to all existing code (except where null is impossible like enum files), so it can be migrated incrementally. --- build/common.targets | 1 + src/SMAPI.Installer/Framework/InstallerContext.cs | 2 ++ src/SMAPI.Installer/Framework/InstallerPaths.cs | 2 ++ src/SMAPI.Installer/InteractiveInstaller.cs | 2 ++ src/SMAPI.Installer/Program.cs | 2 ++ src/SMAPI.Internal.Patching/BasePatcher.cs | 2 ++ src/SMAPI.Internal.Patching/HarmonyPatcher.cs | 2 ++ src/SMAPI.Internal.Patching/IPatcher.cs | 2 ++ src/SMAPI.Internal.Patching/PatchHelper.cs | 2 ++ src/SMAPI.Internal/ConsoleWriting/ColorSchemeConfig.cs | 2 ++ src/SMAPI.Internal/ConsoleWriting/ColorfulConsoleWriter.cs | 2 ++ src/SMAPI.Internal/ConsoleWriting/IConsoleWriter.cs | 2 ++ src/SMAPI.Internal/ExceptionHelper.cs | 2 ++ .../Framework/DiagnosticResult.cs | 2 ++ .../Framework/DiagnosticVerifier.Helper.cs | 2 ++ .../Framework/DiagnosticVerifier.cs | 2 ++ .../Mock/Netcode/NetCollection.cs | 2 ++ .../Mock/Netcode/NetFieldBase.cs | 2 ++ src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetInt.cs | 2 ++ src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/Netcode/NetList.cs | 2 ++ .../Mock/Netcode/NetObjectList.cs | 2 ++ .../Mock/StardewValley/Farmer.cs | 2 ++ src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Item.cs | 2 ++ .../Mock/StardewValley/Object.cs | 2 ++ src/SMAPI.ModBuildConfig.Analyzer.Tests/NetFieldAnalyzerTests.cs | 2 ++ .../ObsoleteFieldAnalyzerTests.cs | 2 ++ src/SMAPI.ModBuildConfig.Analyzer/AnalyzerUtilities.cs | 2 ++ src/SMAPI.ModBuildConfig.Analyzer/NetFieldAnalyzer.cs | 2 ++ src/SMAPI.ModBuildConfig.Analyzer/ObsoleteFieldAnalyzer.cs | 2 ++ src/SMAPI.ModBuildConfig/DeployModTask.cs | 2 ++ src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs | 2 ++ src/SMAPI.ModBuildConfig/Framework/UserErrorException.cs | 2 ++ src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ArgumentParser.cs | 2 ++ src/SMAPI.Mods.ConsoleCommands/Framework/Commands/ConsoleCommand.cs | 2 ++ .../Framework/Commands/IConsoleCommand.cs | 2 ++ .../Framework/Commands/Other/ApplySaveFixCommand.cs | 2 ++ .../Framework/Commands/Other/DebugCommand.cs | 2 ++ .../Framework/Commands/Other/RegenerateBundles.cs | 2 ++ .../Framework/Commands/Other/ShowDataFilesCommand.cs | 2 ++ .../Framework/Commands/Other/ShowGameFilesCommand.cs | 2 ++ .../Framework/Commands/Other/TestInputCommand.cs | 2 ++ .../Framework/Commands/Player/AddCommand.cs | 2 ++ .../Framework/Commands/Player/ListItemTypesCommand.cs | 2 ++ .../Framework/Commands/Player/ListItemsCommand.cs | 2 ++ .../Framework/Commands/Player/SetColorCommand.cs | 2 ++ .../Framework/Commands/Player/SetFarmTypeCommand.cs | 2 ++ .../Framework/Commands/Player/SetHealthCommand.cs | 2 ++ .../Framework/Commands/Player/SetImmunityCommand.cs | 2 ++ .../Framework/Commands/Player/SetMaxHealthCommand.cs | 2 ++ .../Framework/Commands/Player/SetMaxStaminaCommand.cs | 2 ++ .../Framework/Commands/Player/SetMoneyCommand.cs | 2 ++ .../Framework/Commands/Player/SetNameCommand.cs | 2 ++ .../Framework/Commands/Player/SetStaminaCommand.cs | 2 ++ .../Framework/Commands/Player/SetStyleCommand.cs | 2 ++ .../Framework/Commands/World/ClearCommand.cs | 2 ++ .../Framework/Commands/World/DownMineLevelCommand.cs | 2 ++ .../Framework/Commands/World/FreezeTimeCommand.cs | 2 ++ .../Framework/Commands/World/HurryAllCommand.cs | 2 ++ .../Framework/Commands/World/SetDayCommand.cs | 2 ++ .../Framework/Commands/World/SetMineLevelCommand.cs | 2 ++ .../Framework/Commands/World/SetSeasonCommand.cs | 2 ++ .../Framework/Commands/World/SetTimeCommand.cs | 2 ++ .../Framework/Commands/World/SetYearCommand.cs | 2 ++ src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs | 2 ++ src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs | 2 ++ src/SMAPI.Mods.ConsoleCommands/ModEntry.cs | 2 ++ src/SMAPI.Mods.ErrorHandler/ModEntry.cs | 2 ++ src/SMAPI.Mods.ErrorHandler/Patches/DialoguePatcher.cs | 2 ++ src/SMAPI.Mods.ErrorHandler/Patches/EventPatcher.cs | 2 ++ src/SMAPI.Mods.ErrorHandler/Patches/GameLocationPatcher.cs | 2 ++ src/SMAPI.Mods.ErrorHandler/Patches/IClickableMenuPatcher.cs | 2 ++ src/SMAPI.Mods.ErrorHandler/Patches/NpcPatcher.cs | 2 ++ src/SMAPI.Mods.ErrorHandler/Patches/ObjectPatcher.cs | 2 ++ src/SMAPI.Mods.ErrorHandler/Patches/SaveGamePatcher.cs | 2 ++ src/SMAPI.Mods.ErrorHandler/Patches/SpriteBatchPatcher.cs | 2 ++ src/SMAPI.Mods.ErrorHandler/Patches/UtilityPatcher.cs | 2 ++ src/SMAPI.Mods.SaveBackup/ModEntry.cs | 2 ++ src/SMAPI.Tests.ModApiConsumer/ApiConsumer.cs | 2 ++ src/SMAPI.Tests.ModApiConsumer/Interfaces/ISimpleApi.cs | 2 ++ src/SMAPI.Tests.ModApiProvider/Framework/BaseApi.cs | 2 ++ src/SMAPI.Tests.ModApiProvider/Framework/SimpleApi.cs | 2 ++ src/SMAPI.Tests.ModApiProvider/ProviderMod.cs | 2 ++ src/SMAPI.Tests/Core/AssetNameTests.cs | 2 ++ src/SMAPI.Tests/Core/InterfaceProxyTests.cs | 2 ++ src/SMAPI.Tests/Core/ModResolverTests.cs | 2 ++ src/SMAPI.Tests/Core/TranslationTests.cs | 2 ++ src/SMAPI.Tests/Sample.cs | 2 ++ src/SMAPI.Tests/Utilities/KeybindListTests.cs | 2 ++ src/SMAPI.Tests/Utilities/PathUtilitiesTests.cs | 2 ++ src/SMAPI.Tests/Utilities/SDateTests.cs | 2 ++ src/SMAPI.Tests/Utilities/SemanticVersionTests.cs | 2 ++ src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs | 2 ++ src/SMAPI.Toolkit.CoreInterfaces/IManifest.cs | 2 ++ src/SMAPI.Toolkit.CoreInterfaces/IManifestContentPackFor.cs | 2 ++ src/SMAPI.Toolkit.CoreInterfaces/IManifestDependency.cs | 2 ++ src/SMAPI.Toolkit.CoreInterfaces/ISemanticVersion.cs | 2 ++ src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryModel.cs | 2 ++ src/SMAPI.Toolkit/Framework/Clients/WebApi/ModEntryVersionModel.cs | 2 ++ .../Framework/Clients/WebApi/ModExtendedMetadataModel.cs | 2 ++ src/SMAPI.Toolkit/Framework/Clients/WebApi/ModSearchEntryModel.cs | 2 ++ src/SMAPI.Toolkit/Framework/Clients/WebApi/ModSearchModel.cs | 2 ++ src/SMAPI.Toolkit/Framework/Clients/WebApi/WebApiClient.cs | 2 ++ src/SMAPI.Toolkit/Framework/Clients/Wiki/ChangeDescriptor.cs | 2 ++ src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiClient.cs | 2 ++ src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityInfo.cs | 2 ++ src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiCompatibilityStatus.cs | 2 ++ src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiDataOverrideEntry.cs | 2 -- src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModEntry.cs | 2 ++ src/SMAPI.Toolkit/Framework/Clients/Wiki/WikiModList.cs | 2 ++ src/SMAPI.Toolkit/Framework/Constants.cs | 2 ++ src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs | 2 ++ src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs | 2 ++ src/SMAPI.Toolkit/Framework/ModData/MetadataModel.cs | 2 ++ src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs | 2 ++ src/SMAPI.Toolkit/Framework/ModData/ModDataModel.cs | 2 ++ src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs | 2 ++ src/SMAPI.Toolkit/Framework/ModData/ModDataRecordVersionedFields.cs | 2 ++ src/SMAPI.Toolkit/Framework/ModData/ModDatabase.cs | 2 ++ src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs | 2 ++ src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs | 2 ++ src/SMAPI.Toolkit/Framework/SemanticVersionReader.cs | 2 ++ src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs | 2 ++ src/SMAPI.Toolkit/ModToolkit.cs | 2 ++ src/SMAPI.Toolkit/SemanticVersion.cs | 2 ++ src/SMAPI.Toolkit/SemanticVersionComparer.cs | 2 ++ .../Serialization/Converters/ManifestContentPackForConverter.cs | 2 ++ .../Serialization/Converters/ManifestDependencyArrayConverter.cs | 2 ++ .../Serialization/Converters/SemanticVersionConverter.cs | 2 ++ .../Serialization/Converters/SimpleReadOnlyConverter.cs | 2 ++ src/SMAPI.Toolkit/Serialization/InternalExtensions.cs | 2 ++ src/SMAPI.Toolkit/Serialization/JsonHelper.cs | 2 ++ src/SMAPI.Toolkit/Serialization/Models/Manifest.cs | 2 ++ src/SMAPI.Toolkit/Serialization/Models/ManifestContentPackFor.cs | 2 ++ src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs | 2 ++ src/SMAPI.Toolkit/Serialization/SParseException.cs | 2 ++ src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs | 2 ++ src/SMAPI.Toolkit/Utilities/FileUtilities.cs | 2 ++ src/SMAPI.Toolkit/Utilities/PathUtilities.cs | 2 ++ src/SMAPI.Web/BackgroundService.cs | 2 ++ src/SMAPI.Web/Controllers/IndexController.cs | 2 ++ src/SMAPI.Web/Controllers/JsonValidatorController.cs | 2 ++ src/SMAPI.Web/Controllers/LogParserController.cs | 2 ++ src/SMAPI.Web/Controllers/ModsApiController.cs | 2 ++ src/SMAPI.Web/Controllers/ModsController.cs | 2 ++ src/SMAPI.Web/Framework/AllowLargePostsAttribute.cs | 2 ++ src/SMAPI.Web/Framework/Caching/Cached.cs | 2 ++ src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs | 2 ++ src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs | 2 ++ src/SMAPI.Web/Framework/Caching/Wiki/IWikiCacheRepository.cs | 2 ++ src/SMAPI.Web/Framework/Caching/Wiki/WikiCacheMemoryRepository.cs | 2 ++ src/SMAPI.Web/Framework/Caching/Wiki/WikiMetadata.cs | 2 ++ src/SMAPI.Web/Framework/Clients/Chucklefish/ChucklefishClient.cs | 2 ++ src/SMAPI.Web/Framework/Clients/CurseForge/CurseForgeClient.cs | 2 ++ .../Framework/Clients/CurseForge/ResponseModels/ModFileModel.cs | 2 ++ .../Framework/Clients/CurseForge/ResponseModels/ModModel.cs | 2 ++ src/SMAPI.Web/Framework/Clients/GenericModDownload.cs | 2 ++ src/SMAPI.Web/Framework/Clients/GenericModPage.cs | 2 ++ src/SMAPI.Web/Framework/Clients/GitHub/GitAsset.cs | 2 ++ src/SMAPI.Web/Framework/Clients/GitHub/GitHubClient.cs | 2 ++ src/SMAPI.Web/Framework/Clients/GitHub/GitLicense.cs | 2 ++ src/SMAPI.Web/Framework/Clients/GitHub/GitRelease.cs | 2 ++ src/SMAPI.Web/Framework/Clients/GitHub/GitRepo.cs | 2 ++ src/SMAPI.Web/Framework/Clients/GitHub/IGitHubClient.cs | 2 ++ src/SMAPI.Web/Framework/Clients/IModSiteClient.cs | 2 ++ src/SMAPI.Web/Framework/Clients/ModDrop/ModDropClient.cs | 2 ++ .../Framework/Clients/ModDrop/ResponseModels/FileDataModel.cs | 2 ++ .../Framework/Clients/ModDrop/ResponseModels/ModDataModel.cs | 2 ++ .../Framework/Clients/ModDrop/ResponseModels/ModListModel.cs | 2 ++ src/SMAPI.Web/Framework/Clients/ModDrop/ResponseModels/ModModel.cs | 2 ++ src/SMAPI.Web/Framework/Clients/Nexus/NexusClient.cs | 2 ++ src/SMAPI.Web/Framework/Clients/Nexus/ResponseModels/NexusMod.cs | 2 ++ src/SMAPI.Web/Framework/Clients/Pastebin/IPastebinClient.cs | 2 ++ src/SMAPI.Web/Framework/Clients/Pastebin/PasteInfo.cs | 2 ++ src/SMAPI.Web/Framework/Clients/Pastebin/PastebinClient.cs | 2 ++ src/SMAPI.Web/Framework/Compression/GzipHelper.cs | 2 ++ src/SMAPI.Web/Framework/Compression/IGzipHelper.cs | 2 ++ src/SMAPI.Web/Framework/ConfigModels/ApiClientsConfig.cs | 2 ++ src/SMAPI.Web/Framework/ConfigModels/ModOverrideConfig.cs | 2 ++ src/SMAPI.Web/Framework/ConfigModels/ModUpdateCheckConfig.cs | 2 ++ src/SMAPI.Web/Framework/ConfigModels/SiteConfig.cs | 2 ++ src/SMAPI.Web/Framework/ConfigModels/SmapiInfoConfig.cs | 2 ++ src/SMAPI.Web/Framework/Extensions.cs | 2 ++ src/SMAPI.Web/Framework/IModDownload.cs | 2 ++ src/SMAPI.Web/Framework/IModPage.cs | 2 ++ src/SMAPI.Web/Framework/InternalControllerFeatureProvider.cs | 2 ++ src/SMAPI.Web/Framework/JobDashboardAuthorizationFilter.cs | 2 ++ src/SMAPI.Web/Framework/LogParsing/LogMessageBuilder.cs | 2 ++ src/SMAPI.Web/Framework/LogParsing/LogParseException.cs | 5 ++++- src/SMAPI.Web/Framework/LogParsing/LogParser.cs | 2 ++ src/SMAPI.Web/Framework/LogParsing/Models/LogMessage.cs | 2 ++ src/SMAPI.Web/Framework/LogParsing/Models/LogModInfo.cs | 2 ++ src/SMAPI.Web/Framework/LogParsing/Models/ParsedLog.cs | 2 ++ src/SMAPI.Web/Framework/ModInfoModel.cs | 2 ++ src/SMAPI.Web/Framework/ModSiteManager.cs | 2 ++ src/SMAPI.Web/Framework/RedirectRules/RedirectHostsToUrlsRule.cs | 2 ++ src/SMAPI.Web/Framework/RedirectRules/RedirectMatchRule.cs | 2 ++ src/SMAPI.Web/Framework/RedirectRules/RedirectPathsToUrlsRule.cs | 2 ++ src/SMAPI.Web/Framework/RedirectRules/RedirectToHttpsRule.cs | 2 ++ src/SMAPI.Web/Framework/Storage/IStorageProvider.cs | 2 ++ src/SMAPI.Web/Framework/Storage/StorageProvider.cs | 2 ++ src/SMAPI.Web/Framework/Storage/StoredFileInfo.cs | 2 ++ src/SMAPI.Web/Framework/Storage/UploadResult.cs | 2 ++ src/SMAPI.Web/Framework/VersionConstraint.cs | 2 ++ src/SMAPI.Web/Program.cs | 2 ++ src/SMAPI.Web/Startup.cs | 2 ++ src/SMAPI.Web/ViewModels/IndexModel.cs | 2 ++ src/SMAPI.Web/ViewModels/IndexVersionModel.cs | 2 ++ src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorErrorModel.cs | 2 ++ src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorModel.cs | 2 ++ src/SMAPI.Web/ViewModels/JsonValidator/JsonValidatorRequestModel.cs | 2 ++ src/SMAPI.Web/ViewModels/LogParserModel.cs | 2 ++ src/SMAPI.Web/ViewModels/ModCompatibilityModel.cs | 2 ++ src/SMAPI.Web/ViewModels/ModLinkModel.cs | 2 ++ src/SMAPI.Web/ViewModels/ModListModel.cs | 2 ++ src/SMAPI.Web/ViewModels/ModModel.cs | 2 ++ src/SMAPI.Web/Views/Index/Index.cshtml | 4 ++++ src/SMAPI.Web/Views/Index/Privacy.cshtml | 4 ++++ src/SMAPI.Web/Views/JsonValidator/Index.cshtml | 4 ++++ src/SMAPI.Web/Views/LogParser/Index.cshtml | 4 ++++ src/SMAPI.Web/Views/Mods/Index.cshtml | 4 ++++ src/SMAPI.Web/Views/Shared/_Layout.cshtml | 4 ++++ src/SMAPI.Web/Views/_ViewStart.cshtml | 6 +++++- src/SMAPI/Constants.cs | 2 ++ src/SMAPI/Context.cs | 2 ++ src/SMAPI/Events/AssetReadyEventArgs.cs | 2 ++ src/SMAPI/Events/AssetRequestedEventArgs.cs | 2 ++ src/SMAPI/Events/AssetsInvalidatedEventArgs.cs | 2 ++ src/SMAPI/Events/BuildingListChangedEventArgs.cs | 2 ++ src/SMAPI/Events/ButtonPressedEventArgs.cs | 2 ++ src/SMAPI/Events/ButtonReleasedEventArgs.cs | 2 ++ src/SMAPI/Events/ButtonsChangedEventArgs.cs | 2 ++ src/SMAPI/Events/ChestInventoryChangedEventArgs.cs | 2 ++ src/SMAPI/Events/CursorMovedEventArgs.cs | 2 ++ src/SMAPI/Events/DebrisListChangedEventArgs.cs | 2 ++ src/SMAPI/Events/FurnitureListChangedEventArgs.cs | 2 ++ src/SMAPI/Events/IContentEvents.cs | 2 ++ src/SMAPI/Events/IDisplayEvents.cs | 2 ++ src/SMAPI/Events/IGameLoopEvents.cs | 2 ++ src/SMAPI/Events/IInputEvents.cs | 2 ++ src/SMAPI/Events/IModEvents.cs | 2 ++ src/SMAPI/Events/IMultiplayerEvents.cs | 2 ++ src/SMAPI/Events/IPlayerEvents.cs | 2 ++ src/SMAPI/Events/ISpecialisedEvents.cs | 2 ++ src/SMAPI/Events/IWorldEvents.cs | 2 ++ src/SMAPI/Events/InventoryChangedEventArgs.cs | 2 ++ src/SMAPI/Events/ItemStackSizeChange.cs | 2 ++ src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs | 2 ++ src/SMAPI/Events/LevelChangedEventArgs.cs | 2 ++ src/SMAPI/Events/LocaleChangedEventArgs.cs | 2 ++ src/SMAPI/Events/LocationListChangedEventArgs.cs | 2 ++ src/SMAPI/Events/MenuChangedEventArgs.cs | 2 ++ src/SMAPI/Events/ModMessageReceivedEventArgs.cs | 2 ++ src/SMAPI/Events/NpcListChangedEventArgs.cs | 2 ++ src/SMAPI/Events/ObjectListChangedEventArgs.cs | 2 ++ src/SMAPI/Events/PeerConnectedEventArgs.cs | 2 ++ src/SMAPI/Events/PeerContextReceivedEventArgs.cs | 2 ++ src/SMAPI/Events/PeerDisconnectedEventArgs.cs | 2 ++ src/SMAPI/Events/RenderedActiveMenuEventArgs.cs | 2 ++ src/SMAPI/Events/RenderedEventArgs.cs | 2 ++ src/SMAPI/Events/RenderedHudEventArgs.cs | 2 ++ src/SMAPI/Events/RenderedWorldEventArgs.cs | 2 ++ src/SMAPI/Events/RenderingActiveMenuEventArgs.cs | 2 ++ src/SMAPI/Events/RenderingEventArgs.cs | 2 ++ src/SMAPI/Events/RenderingHudEventArgs.cs | 2 ++ src/SMAPI/Events/RenderingWorldEventArgs.cs | 2 ++ src/SMAPI/Events/TerrainFeatureListChangedEventArgs.cs | 2 ++ src/SMAPI/Events/WarpedEventArgs.cs | 2 ++ src/SMAPI/Framework/Command.cs | 2 ++ src/SMAPI/Framework/CommandManager.cs | 2 ++ src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs | 2 ++ src/SMAPI/Framework/Commands/HelpCommand.cs | 2 ++ src/SMAPI/Framework/Commands/IInternalCommand.cs | 2 ++ src/SMAPI/Framework/Commands/ReloadI18nCommand.cs | 2 ++ src/SMAPI/Framework/Content/AssetData.cs | 2 ++ src/SMAPI/Framework/Content/AssetDataForDictionary.cs | 2 ++ src/SMAPI/Framework/Content/AssetDataForImage.cs | 2 ++ src/SMAPI/Framework/Content/AssetDataForMap.cs | 2 ++ src/SMAPI/Framework/Content/AssetDataForObject.cs | 2 ++ src/SMAPI/Framework/Content/AssetEditOperation.cs | 2 ++ src/SMAPI/Framework/Content/AssetInfo.cs | 2 ++ src/SMAPI/Framework/Content/AssetInterceptorChange.cs | 2 ++ src/SMAPI/Framework/Content/AssetLoadOperation.cs | 2 ++ src/SMAPI/Framework/Content/AssetName.cs | 2 ++ src/SMAPI/Framework/Content/AssetOperationGroup.cs | 2 ++ src/SMAPI/Framework/Content/ContentCache.cs | 2 ++ src/SMAPI/Framework/Content/TilesheetReference.cs | 2 ++ src/SMAPI/Framework/ContentCoordinator.cs | 2 ++ src/SMAPI/Framework/ContentManagers/BaseContentManager.cs | 2 ++ src/SMAPI/Framework/ContentManagers/GameContentManager.cs | 2 ++ .../ContentManagers/GameContentManagerForAssetPropagation.cs | 2 ++ src/SMAPI/Framework/ContentManagers/IContentManager.cs | 2 ++ src/SMAPI/Framework/ContentManagers/ModContentManager.cs | 2 ++ src/SMAPI/Framework/ContentPack.cs | 2 ++ src/SMAPI/Framework/CursorPosition.cs | 2 ++ src/SMAPI/Framework/DeprecationManager.cs | 2 ++ src/SMAPI/Framework/DeprecationWarning.cs | 2 ++ src/SMAPI/Framework/Events/EventManager.cs | 2 ++ src/SMAPI/Framework/Events/IManagedEvent.cs | 2 ++ src/SMAPI/Framework/Events/ManagedEvent.cs | 2 ++ src/SMAPI/Framework/Events/ManagedEventHandler.cs | 2 ++ src/SMAPI/Framework/Events/ModContentEvents.cs | 2 ++ src/SMAPI/Framework/Events/ModDisplayEvents.cs | 2 ++ src/SMAPI/Framework/Events/ModEvents.cs | 2 ++ src/SMAPI/Framework/Events/ModEventsBase.cs | 2 ++ src/SMAPI/Framework/Events/ModGameLoopEvents.cs | 2 ++ src/SMAPI/Framework/Events/ModInputEvents.cs | 2 ++ src/SMAPI/Framework/Events/ModMultiplayerEvents.cs | 2 ++ src/SMAPI/Framework/Events/ModPlayerEvents.cs | 2 ++ src/SMAPI/Framework/Events/ModSpecialisedEvents.cs | 2 ++ src/SMAPI/Framework/Events/ModWorldEvents.cs | 2 ++ src/SMAPI/Framework/Exceptions/SAssemblyLoadFailedException.cs | 2 ++ src/SMAPI/Framework/Exceptions/SContentLoadException.cs | 4 +++- src/SMAPI/Framework/GameVersion.cs | 2 ++ src/SMAPI/Framework/IModMetadata.cs | 2 ++ src/SMAPI/Framework/Input/GamePadStateBuilder.cs | 2 ++ src/SMAPI/Framework/Input/IInputStateBuilder.cs | 2 ++ src/SMAPI/Framework/Input/KeyboardStateBuilder.cs | 2 ++ src/SMAPI/Framework/Input/MouseStateBuilder.cs | 2 ++ src/SMAPI/Framework/Input/SInputState.cs | 2 ++ src/SMAPI/Framework/InternalExtensions.cs | 2 ++ src/SMAPI/Framework/Logging/InterceptingTextWriter.cs | 2 ++ src/SMAPI/Framework/Logging/LogFileManager.cs | 2 ++ src/SMAPI/Framework/Logging/LogManager.cs | 2 ++ src/SMAPI/Framework/ModHelpers/BaseHelper.cs | 2 ++ src/SMAPI/Framework/ModHelpers/CommandHelper.cs | 2 ++ src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 2 ++ src/SMAPI/Framework/ModHelpers/ContentPackHelper.cs | 2 ++ src/SMAPI/Framework/ModHelpers/DataHelper.cs | 2 ++ src/SMAPI/Framework/ModHelpers/GameContentHelper.cs | 2 ++ src/SMAPI/Framework/ModHelpers/InputHelper.cs | 2 ++ src/SMAPI/Framework/ModHelpers/ModContentHelper.cs | 2 ++ src/SMAPI/Framework/ModHelpers/ModHelper.cs | 2 ++ src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs | 2 ++ src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs | 2 ++ src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs | 2 ++ src/SMAPI/Framework/ModHelpers/TranslationHelper.cs | 2 ++ src/SMAPI/Framework/ModLinked.cs | 2 ++ src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs | 2 ++ src/SMAPI/Framework/ModLoading/AssemblyLoadStatus.cs | 2 ++ src/SMAPI/Framework/ModLoading/AssemblyLoader.cs | 2 ++ src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs | 2 ++ src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs | 2 ++ src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs | 2 ++ src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs | 2 ++ src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs | 2 ++ .../ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs | 2 ++ .../Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs | 2 ++ src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs | 2 ++ src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs | 2 ++ src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs | 2 ++ src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs | 2 ++ src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs | 2 ++ src/SMAPI/Framework/ModLoading/IInstructionHandler.cs | 2 ++ src/SMAPI/Framework/ModLoading/IncompatibleInstructionException.cs | 2 ++ src/SMAPI/Framework/ModLoading/InvalidModStateException.cs | 2 ++ src/SMAPI/Framework/ModLoading/ModMetadata.cs | 2 ++ src/SMAPI/Framework/ModLoading/ModResolver.cs | 2 ++ src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs | 2 ++ src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs | 2 ++ .../Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs | 2 ++ .../Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs | 2 ++ src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs | 2 ++ .../Framework/ModLoading/Rewriters/ArchitectureAssemblyRewriter.cs | 2 ++ src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs | 2 ++ src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs | 2 ++ src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs | 2 ++ src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs | 2 ++ src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs | 2 ++ src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs | 2 ++ src/SMAPI/Framework/ModLoading/Symbols/SymbolReader.cs | 2 ++ src/SMAPI/Framework/ModLoading/Symbols/SymbolReaderProvider.cs | 2 ++ src/SMAPI/Framework/ModLoading/Symbols/SymbolWriterProvider.cs | 2 ++ src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs | 2 ++ src/SMAPI/Framework/ModRegistry.cs | 2 ++ src/SMAPI/Framework/Models/SConfig.cs | 2 ++ src/SMAPI/Framework/Monitor.cs | 2 ++ src/SMAPI/Framework/Networking/ModMessageModel.cs | 2 ++ src/SMAPI/Framework/Networking/MultiplayerPeer.cs | 2 ++ src/SMAPI/Framework/Networking/MultiplayerPeerMod.cs | 2 ++ src/SMAPI/Framework/Networking/RemoteContextModModel.cs | 2 ++ src/SMAPI/Framework/Networking/RemoteContextModel.cs | 2 ++ src/SMAPI/Framework/Networking/SGalaxyNetClient.cs | 2 ++ src/SMAPI/Framework/Networking/SGalaxyNetServer.cs | 2 ++ src/SMAPI/Framework/Networking/SLidgrenClient.cs | 2 ++ src/SMAPI/Framework/Networking/SLidgrenServer.cs | 2 ++ src/SMAPI/Framework/Reflection/CacheEntry.cs | 2 ++ src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs | 2 ++ src/SMAPI/Framework/Reflection/ReflectedField.cs | 2 ++ src/SMAPI/Framework/Reflection/ReflectedMethod.cs | 2 ++ src/SMAPI/Framework/Reflection/ReflectedProperty.cs | 2 ++ src/SMAPI/Framework/Reflection/Reflector.cs | 2 ++ src/SMAPI/Framework/Rendering/SDisplayDevice.cs | 2 ++ src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs | 2 ++ src/SMAPI/Framework/RequestExitDelegate.cs | 2 ++ src/SMAPI/Framework/SChatBox.cs | 2 ++ src/SMAPI/Framework/SCore.cs | 2 ++ src/SMAPI/Framework/SGame.cs | 2 ++ src/SMAPI/Framework/SGameRunner.cs | 2 ++ src/SMAPI/Framework/SModHooks.cs | 2 ++ src/SMAPI/Framework/SMultiplayer.cs | 2 ++ src/SMAPI/Framework/Serialization/ColorConverter.cs | 2 ++ src/SMAPI/Framework/Serialization/KeybindConverter.cs | 2 ++ src/SMAPI/Framework/Serialization/PointConverter.cs | 2 ++ src/SMAPI/Framework/Serialization/RectangleConverter.cs | 2 ++ src/SMAPI/Framework/Serialization/Vector2Converter.cs | 2 ++ src/SMAPI/Framework/Singleton.cs | 2 ++ src/SMAPI/Framework/SnapshotDiff.cs | 2 ++ src/SMAPI/Framework/SnapshotItemListDiff.cs | 2 ++ src/SMAPI/Framework/SnapshotListDiff.cs | 2 ++ src/SMAPI/Framework/StateTracking/ChestTracker.cs | 2 ++ src/SMAPI/Framework/StateTracking/Comparers/EquatableComparer.cs | 2 ++ .../Framework/StateTracking/Comparers/GenericEqualsComparer.cs | 2 ++ .../Framework/StateTracking/Comparers/ObjectReferenceComparer.cs | 2 ++ .../Framework/StateTracking/FieldWatchers/BaseDisposableWatcher.cs | 2 ++ .../Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs | 2 ++ .../Framework/StateTracking/FieldWatchers/ComparableWatcher.cs | 2 ++ .../StateTracking/FieldWatchers/ImmutableCollectionWatcher.cs | 2 ++ .../Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs | 2 ++ .../Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs | 2 ++ src/SMAPI/Framework/StateTracking/FieldWatchers/NetListWatcher.cs | 2 ++ src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs | 2 ++ .../StateTracking/FieldWatchers/ObservableCollectionWatcher.cs | 2 ++ src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs | 2 ++ src/SMAPI/Framework/StateTracking/ICollectionWatcher.cs | 2 ++ src/SMAPI/Framework/StateTracking/IDictionaryWatcher.cs | 2 ++ src/SMAPI/Framework/StateTracking/IValueWatcher.cs | 2 ++ src/SMAPI/Framework/StateTracking/IWatcher.cs | 2 ++ src/SMAPI/Framework/StateTracking/LocationTracker.cs | 2 ++ src/SMAPI/Framework/StateTracking/PlayerTracker.cs | 2 ++ src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs | 2 ++ src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs | 2 ++ src/SMAPI/Framework/StateTracking/Snapshots/WatcherSnapshot.cs | 2 ++ .../Framework/StateTracking/Snapshots/WorldLocationsSnapshot.cs | 2 ++ src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs | 2 ++ src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs | 2 ++ src/SMAPI/Framework/Translator.cs | 2 ++ src/SMAPI/Framework/Utilities/ContextHash.cs | 2 ++ src/SMAPI/Framework/Utilities/Countdown.cs | 2 +- src/SMAPI/Framework/Utilities/TickCacheDictionary.cs | 2 ++ src/SMAPI/Framework/WatcherCore.cs | 2 ++ src/SMAPI/GamePlatform.cs | 2 ++ src/SMAPI/IAssetData.cs | 2 ++ src/SMAPI/IAssetDataForDictionary.cs | 2 ++ src/SMAPI/IAssetDataForImage.cs | 2 ++ src/SMAPI/IAssetDataForMap.cs | 2 ++ src/SMAPI/IAssetEditor.cs | 2 ++ src/SMAPI/IAssetInfo.cs | 2 ++ src/SMAPI/IAssetLoader.cs | 2 ++ src/SMAPI/IAssetName.cs | 2 ++ src/SMAPI/ICommandHelper.cs | 2 ++ src/SMAPI/IContentHelper.cs | 2 ++ src/SMAPI/IContentPack.cs | 2 ++ src/SMAPI/IContentPackHelper.cs | 2 ++ src/SMAPI/ICursorPosition.cs | 2 ++ src/SMAPI/IDataHelper.cs | 2 ++ src/SMAPI/IGameContentHelper.cs | 2 ++ src/SMAPI/IInputHelper.cs | 2 ++ src/SMAPI/IMod.cs | 2 ++ src/SMAPI/IModContentHelper.cs | 2 ++ src/SMAPI/IModHelper.cs | 2 ++ src/SMAPI/IModInfo.cs | 2 ++ src/SMAPI/IModLinked.cs | 2 ++ src/SMAPI/IModRegistry.cs | 2 ++ src/SMAPI/IMonitor.cs | 2 ++ src/SMAPI/IMultiplayerHelper.cs | 2 ++ src/SMAPI/IMultiplayerPeer.cs | 2 ++ src/SMAPI/IMultiplayerPeerMod.cs | 2 ++ src/SMAPI/IReflectedField.cs | 4 +++- src/SMAPI/IReflectedMethod.cs | 4 +++- src/SMAPI/IReflectedProperty.cs | 2 ++ src/SMAPI/IReflectionHelper.cs | 2 ++ src/SMAPI/ITranslationHelper.cs | 2 ++ src/SMAPI/Metadata/CoreAssetPropagator.cs | 2 ++ src/SMAPI/Metadata/InstructionMetadata.cs | 2 ++ src/SMAPI/Mod.cs | 2 ++ src/SMAPI/Patches/Game1Patcher.cs | 2 ++ src/SMAPI/Patches/TitleMenuPatcher.cs | 2 ++ src/SMAPI/Program.cs | 2 ++ src/SMAPI/SemanticVersion.cs | 2 ++ src/SMAPI/Translation.cs | 2 ++ src/SMAPI/Utilities/CaseInsensitivePathCache.cs | 2 ++ src/SMAPI/Utilities/Keybind.cs | 2 ++ src/SMAPI/Utilities/KeybindList.cs | 2 ++ src/SMAPI/Utilities/PathUtilities.cs | 2 ++ src/SMAPI/Utilities/PerScreen.cs | 2 ++ src/SMAPI/Utilities/SDate.cs | 2 ++ 486 files changed, 988 insertions(+), 8 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/build/common.targets b/build/common.targets index bcb0e9e1..258b48f2 100644 --- a/build/common.targets +++ b/build/common.targets @@ -5,6 +5,7 @@ SMAPI latest $(AssemblySearchPaths);{GAC} + enable $(DefineConstants);SMAPI_FOR_WINDOWS diff --git a/src/SMAPI.Installer/Framework/InstallerContext.cs b/src/SMAPI.Installer/Framework/InstallerContext.cs index abc7adde..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; 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 09183b5d..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; diff --git a/src/SMAPI.Installer/Program.cs b/src/SMAPI.Installer/Program.cs index 2c9b2c0a..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; 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 6f30c241..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; 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 c9758616..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; 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 { /// Writes text to the console. diff --git a/src/SMAPI.Internal/ExceptionHelper.cs b/src/SMAPI.Internal/ExceptionHelper.cs index 6bd1d579..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; 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 + // 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..8d72fea1 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 + // using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.cs index 49697dfa..09d3a3f8 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 + // using System.Collections.Generic; using System.Linq; 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 e8da92fa..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; diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Object.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Mock/StardewValley/Object.cs index 151010a7..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; diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/NetFieldAnalyzerTests.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/NetFieldAnalyzerTests.cs index f11a59d3..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; diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/ObsoleteFieldAnalyzerTests.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/ObsoleteFieldAnalyzerTests.cs index 76607b8e..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; diff --git a/src/SMAPI.ModBuildConfig.Analyzer/AnalyzerUtilities.cs b/src/SMAPI.ModBuildConfig.Analyzer/AnalyzerUtilities.cs index a1ad8aa4..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; diff --git a/src/SMAPI.ModBuildConfig.Analyzer/NetFieldAnalyzer.cs b/src/SMAPI.ModBuildConfig.Analyzer/NetFieldAnalyzer.cs index 553aae99..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; diff --git a/src/SMAPI.ModBuildConfig.Analyzer/ObsoleteFieldAnalyzer.cs b/src/SMAPI.ModBuildConfig.Analyzer/ObsoleteFieldAnalyzer.cs index ba089513..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; diff --git a/src/SMAPI.ModBuildConfig/DeployModTask.cs b/src/SMAPI.ModBuildConfig/DeployModTask.cs index c7026ee1..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; diff --git a/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs b/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs index 80955f67..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; 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.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 44b7824e..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; 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 { /// A console command to register. diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ApplySaveFixCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ApplySaveFixCommand.cs index b02ba4b1..f31457ed 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ApplySaveFixCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ApplySaveFixCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/DebugCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/DebugCommand.cs index cf1dcbce..f289c669 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/DebugCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/DebugCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Diagnostics.CodeAnalysis; using StardewValley; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/RegenerateBundles.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/RegenerateBundles.cs index 159d7c4a..81a8c570 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/RegenerateBundles.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/RegenerateBundles.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowDataFilesCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowDataFilesCommand.cs index a233d588..d762d8bf 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowDataFilesCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowDataFilesCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Diagnostics; using System.Diagnostics.CodeAnalysis; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowGameFilesCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowGameFilesCommand.cs index 745b821b..b5733eb9 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowGameFilesCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/ShowGameFilesCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Diagnostics; using System.Diagnostics.CodeAnalysis; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/TestInputCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/TestInputCommand.cs index 8bf9f5db..5484fc7c 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/TestInputCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Other/TestInputCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics.CodeAnalysis; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/AddCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/AddCommand.cs index fae31c23..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; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemTypesCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemTypesCommand.cs index ef35ad19..e57d4065 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemTypesCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemTypesCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewModdingAPI.Mods.ConsoleCommands.Framework.ItemData; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemsCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemsCommand.cs index 5cc464fe..5a21b459 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemsCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/ListItemsCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetColorCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetColorCommand.cs index af7f2d18..e8605163 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetColorCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetColorCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Diagnostics.CodeAnalysis; using Microsoft.Xna.Framework; using StardewValley; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetFarmTypeCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetFarmTypeCommand.cs index 2809df9c..02670911 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetFarmTypeCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetFarmTypeCommand.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/Player/SetHealthCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetHealthCommand.cs index f169159f..1a1a9eab 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetHealthCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetHealthCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewValley; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetImmunityCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetImmunityCommand.cs index 1065bd21..d1dede1f 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetImmunityCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetImmunityCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewValley; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxHealthCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxHealthCommand.cs index c2c4931d..2b3b140c 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxHealthCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxHealthCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewValley; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxStaminaCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxStaminaCommand.cs index 8c794e75..f9ed6c58 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxStaminaCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMaxStaminaCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewValley; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMoneyCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMoneyCommand.cs index 3afcc62b..56447a65 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMoneyCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetMoneyCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewValley; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetNameCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetNameCommand.cs index 12d6b6e8..4ce7e1f8 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetNameCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetNameCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Diagnostics.CodeAnalysis; using StardewValley; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStaminaCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStaminaCommand.cs index 24718ace..ea8d74c2 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStaminaCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStaminaCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewValley; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStyleCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStyleCommand.cs index 558c327d..84625a34 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStyleCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/Player/SetStyleCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Diagnostics.CodeAnalysis; using StardewValley; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs index eeb95553..92c73e08 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/ClearCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics.CodeAnalysis; using System.Linq; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/DownMineLevelCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/DownMineLevelCommand.cs index 5b1a4a13..0f18c760 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/DownMineLevelCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/DownMineLevelCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Diagnostics.CodeAnalysis; using StardewValley; using StardewValley.Locations; 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 09531720..f9810dc3 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/HurryAllCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/HurryAllCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics.CodeAnalysis; using StardewValley; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetDayCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetDayCommand.cs index 399fd934..8aa27d93 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetDayCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetDayCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewModdingAPI.Utilities; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetMineLevelCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetMineLevelCommand.cs index f977fce3..ad6ac777 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetMineLevelCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetMineLevelCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics.CodeAnalysis; using StardewValley; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetSeasonCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetSeasonCommand.cs index d839c037..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,5 @@ +#nullable disable + using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewModdingAPI.Utilities; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetTimeCommand.cs index 8c4458dd..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,5 @@ +#nullable disable + using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.Xna.Framework; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetYearCommand.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetYearCommand.cs index a666a634..995f222e 100644 --- a/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetYearCommand.cs +++ b/src/SMAPI.Mods.ConsoleCommands/Framework/Commands/World/SetYearCommand.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewModdingAPI.Utilities; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemData/SearchableItem.cs index 3675a963..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; diff --git a/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs b/src/SMAPI.Mods.ConsoleCommands/Framework/ItemRepository.cs index 3915db9a..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; diff --git a/src/SMAPI.Mods.ConsoleCommands/ModEntry.cs b/src/SMAPI.Mods.ConsoleCommands/ModEntry.cs index 5e594984..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; 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 01bfb888..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; 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 273b1434..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; diff --git a/src/SMAPI.Tests.ModApiConsumer/ApiConsumer.cs b/src/SMAPI.Tests.ModApiConsumer/ApiConsumer.cs index ac7bd338..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; 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 { /// The base class for . 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 7b817d13..b7e34191 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; 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 86c50606..2ce1c74e 100644 --- a/src/SMAPI.Tests/Core/ModResolverTests.cs +++ b/src/SMAPI.Tests/Core/ModResolverTests.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; diff --git a/src/SMAPI.Tests/Core/TranslationTests.cs b/src/SMAPI.Tests/Core/TranslationTests.cs index 58bc59b1..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; diff --git a/src/SMAPI.Tests/Sample.cs b/src/SMAPI.Tests/Sample.cs index 9587a100..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 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 94819c2e..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; diff --git a/src/SMAPI.Tests/Utilities/SDateTests.cs b/src/SMAPI.Tests/Utilities/SDateTests.cs index 886f25cd..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; diff --git a/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs b/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs index 142c9814..66181ea6 100644 --- a/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs +++ b/src/SMAPI.Tests/Utilities/SemanticVersionTests.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics.CodeAnalysis; using Newtonsoft.Json; diff --git a/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs b/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs index 84cae8df..7695fbf8 100644 --- a/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs +++ b/src/SMAPI.Tests/WikiClient/ChangeDescriptorTests.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using NUnit.Framework; using StardewModdingAPI; diff --git a/src/SMAPI.Toolkit.CoreInterfaces/IManifest.cs b/src/SMAPI.Toolkit.CoreInterfaces/IManifest.cs index 7375f005..a9251446 100644 --- a/src/SMAPI.Toolkit.CoreInterfaces/IManifest.cs +++ b/src/SMAPI.Toolkit.CoreInterfaces/IManifest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; namespace StardewModdingAPI diff --git a/src/SMAPI.Toolkit.CoreInterfaces/IManifestContentPackFor.cs b/src/SMAPI.Toolkit.CoreInterfaces/IManifestContentPackFor.cs index f05a3873..d898b716 100644 --- a/src/SMAPI.Toolkit.CoreInterfaces/IManifestContentPackFor.cs +++ b/src/SMAPI.Toolkit.CoreInterfaces/IManifestContentPackFor.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI { /// Indicates which mod can read the content pack represented by the containing manifest. diff --git a/src/SMAPI.Toolkit.CoreInterfaces/IManifestDependency.cs b/src/SMAPI.Toolkit.CoreInterfaces/IManifestDependency.cs index e86cd1f4..49b7aed6 100644 --- a/src/SMAPI.Toolkit.CoreInterfaces/IManifestDependency.cs +++ b/src/SMAPI.Toolkit.CoreInterfaces/IManifestDependency.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI { /// A mod dependency listed in a mod manifest. diff --git a/src/SMAPI.Toolkit.CoreInterfaces/ISemanticVersion.cs b/src/SMAPI.Toolkit.CoreInterfaces/ISemanticVersion.cs index b228b2d1..52cec52e 100644 --- a/src/SMAPI.Toolkit.CoreInterfaces/ISemanticVersion.cs +++ b/src/SMAPI.Toolkit.CoreInterfaces/ISemanticVersion.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI 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 f7d26d21..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; 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 c936bb3e..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; 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 { /// Compatibility info for a mod. 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 { /// The compatibility status for a mod. 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 { /// The data overrides to apply to matching mods. 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 { /// A mod entry in the wiki list. 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 { /// Metadata from the wiki's mod compatibility list. diff --git a/src/SMAPI.Toolkit/Framework/Constants.cs b/src/SMAPI.Toolkit/Framework/Constants.cs index 55f26582..c3a787c7 100644 --- a/src/SMAPI.Toolkit/Framework/Constants.cs +++ b/src/SMAPI.Toolkit/Framework/Constants.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Toolkit.Framework { /// Contains the SMAPI installer's constants and assumptions. diff --git a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs index 768beba1..ac6fe411 100644 --- a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs +++ b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; diff --git a/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs b/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs index c0332331..9998edec 100644 --- a/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs +++ b/src/SMAPI.Toolkit/Framework/LowLevelEnvironmentUtility.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; 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 7e07ffde..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; 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 { /// The versioned fields from a for a specific manifest. diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDatabase.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDatabase.cs index 9bb3f558..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; diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs index 825b98e5..81d72c0b 100644 --- a/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs +++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModFolder.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.IO; using System.Linq; diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs index e16af3a8..4deaf19b 100644 --- a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs +++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; diff --git a/src/SMAPI.Toolkit/Framework/SemanticVersionReader.cs b/src/SMAPI.Toolkit/Framework/SemanticVersionReader.cs index 489e1c4d..57eea2f7 100644 --- a/src/SMAPI.Toolkit/Framework/SemanticVersionReader.cs +++ b/src/SMAPI.Toolkit/Framework/SemanticVersionReader.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Toolkit.Framework { /// Reads strings into a semantic version. diff --git a/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs b/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs index 077c0361..ec94ed51 100644 --- a/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs +++ b/src/SMAPI.Toolkit/Framework/UpdateData/UpdateKey.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Toolkit.Framework.UpdateData diff --git a/src/SMAPI.Toolkit/ModToolkit.cs b/src/SMAPI.Toolkit/ModToolkit.cs index 80008df7..9ae8cd1c 100644 --- a/src/SMAPI.Toolkit/ModToolkit.cs +++ b/src/SMAPI.Toolkit/ModToolkit.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; using System.IO; using System.Linq; diff --git a/src/SMAPI.Toolkit/SemanticVersion.cs b/src/SMAPI.Toolkit/SemanticVersion.cs index 2f3e282b..97b92555 100644 --- a/src/SMAPI.Toolkit/SemanticVersion.cs +++ b/src/SMAPI.Toolkit/SemanticVersion.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Text.RegularExpressions; using StardewModdingAPI.Toolkit.Framework; diff --git a/src/SMAPI.Toolkit/SemanticVersionComparer.cs b/src/SMAPI.Toolkit/SemanticVersionComparer.cs index 8eba2c9f..a0472f62 100644 --- a/src/SMAPI.Toolkit/SemanticVersionComparer.cs +++ b/src/SMAPI.Toolkit/SemanticVersionComparer.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Collections.Generic; namespace StardewModdingAPI.Toolkit diff --git a/src/SMAPI.Toolkit/Serialization/Converters/ManifestContentPackForConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/ManifestContentPackForConverter.cs index 5cabe9d8..d2dc0d22 100644 --- a/src/SMAPI.Toolkit/Serialization/Converters/ManifestContentPackForConverter.cs +++ b/src/SMAPI.Toolkit/Serialization/Converters/ManifestContentPackForConverter.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Newtonsoft.Json; using StardewModdingAPI.Toolkit.Serialization.Models; diff --git a/src/SMAPI.Toolkit/Serialization/Converters/ManifestDependencyArrayConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/ManifestDependencyArrayConverter.cs index 7b88d6b7..2c3c2ee7 100644 --- a/src/SMAPI.Toolkit/Serialization/Converters/ManifestDependencyArrayConverter.cs +++ b/src/SMAPI.Toolkit/Serialization/Converters/ManifestDependencyArrayConverter.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using Newtonsoft.Json; diff --git a/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs index cf69104d..9205cebe 100644 --- a/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs +++ b/src/SMAPI.Toolkit/Serialization/Converters/SemanticVersionConverter.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Newtonsoft.Json; using Newtonsoft.Json.Linq; diff --git a/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs b/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs index ccc5158b..c923350c 100644 --- a/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs +++ b/src/SMAPI.Toolkit/Serialization/Converters/SimpleReadOnlyConverter.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Newtonsoft.Json; using Newtonsoft.Json.Linq; diff --git a/src/SMAPI.Toolkit/Serialization/InternalExtensions.cs b/src/SMAPI.Toolkit/Serialization/InternalExtensions.cs index 10f88dde..1fce5f9d 100644 --- a/src/SMAPI.Toolkit/Serialization/InternalExtensions.cs +++ b/src/SMAPI.Toolkit/Serialization/InternalExtensions.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Newtonsoft.Json.Linq; diff --git a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs index 91c2e9d3..9700e712 100644 --- a/src/SMAPI.Toolkit/Serialization/JsonHelper.cs +++ b/src/SMAPI.Toolkit/Serialization/JsonHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; diff --git a/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs b/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs index 4ad97b6d..a5dbf604 100644 --- a/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs +++ b/src/SMAPI.Toolkit/Serialization/Models/Manifest.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Runtime.Serialization; diff --git a/src/SMAPI.Toolkit/Serialization/Models/ManifestContentPackFor.cs b/src/SMAPI.Toolkit/Serialization/Models/ManifestContentPackFor.cs index 1eb80889..ea5f0e6c 100644 --- a/src/SMAPI.Toolkit/Serialization/Models/ManifestContentPackFor.cs +++ b/src/SMAPI.Toolkit/Serialization/Models/ManifestContentPackFor.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Toolkit.Serialization.Models { /// Indicates which mod can read the content pack represented by the containing manifest. diff --git a/src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs b/src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs index 00f168f4..f52dd5ee 100644 --- a/src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs +++ b/src/SMAPI.Toolkit/Serialization/Models/ManifestDependency.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Toolkit.Serialization.Models { /// A mod dependency listed in a mod manifest. diff --git a/src/SMAPI.Toolkit/Serialization/SParseException.cs b/src/SMAPI.Toolkit/Serialization/SParseException.cs index 5f58b5b8..1581e027 100644 --- a/src/SMAPI.Toolkit/Serialization/SParseException.cs +++ b/src/SMAPI.Toolkit/Serialization/SParseException.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; namespace StardewModdingAPI.Toolkit.Serialization diff --git a/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs b/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs index 7536337a..f14678be 100644 --- a/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs +++ b/src/SMAPI.Toolkit/Utilities/EnvironmentUtility.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics.CodeAnalysis; using StardewModdingAPI.Toolkit.Framework; diff --git a/src/SMAPI.Toolkit/Utilities/FileUtilities.cs b/src/SMAPI.Toolkit/Utilities/FileUtilities.cs index a6bf5929..ba2d0f47 100644 --- a/src/SMAPI.Toolkit/Utilities/FileUtilities.cs +++ b/src/SMAPI.Toolkit/Utilities/FileUtilities.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.IO; using System.Security.Cryptography; diff --git a/src/SMAPI.Toolkit/Utilities/PathUtilities.cs b/src/SMAPI.Toolkit/Utilities/PathUtilities.cs index 85e12bfa..9a0e2ea7 100644 --- a/src/SMAPI.Toolkit/Utilities/PathUtilities.cs +++ b/src/SMAPI.Toolkit/Utilities/PathUtilities.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Diagnostics.Contracts; using System.IO; 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 69b54f47..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; diff --git a/src/SMAPI.Web/Controllers/JsonValidatorController.cs b/src/SMAPI.Web/Controllers/JsonValidatorController.cs index 985f91ae..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; 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 5329df99..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; 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 a16e6b73..2020d747 100644 --- a/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs +++ b/src/SMAPI.Web/Framework/Caching/Mods/IModCacheRepository.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using StardewModdingAPI.Toolkit.Framework.UpdateData; diff --git a/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs index f871a9da..338562d8 100644 --- a/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.cs +++ b/src/SMAPI.Web/Framework/Caching/Mods/ModCacheMemoryRepository.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/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 { /// The model for cached wiki metadata. diff --git a/src/SMAPI.Web/Framework/Clients/Chucklefish/ChucklefishClient.cs b/src/SMAPI.Web/Framework/Clients/Chucklefish/ChucklefishClient.cs index 269a04d4..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; diff --git a/src/SMAPI.Web/Framework/Clients/CurseForge/CurseForgeClient.cs b/src/SMAPI.Web/Framework/Clients/CurseForge/CurseForgeClient.cs index 50a3336d..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; 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 { /// Metadata from the CurseForge API about a mod file. 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 { /// An mod from the CurseForge API. 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 { /// Generic metadata about a file download on a mod page. 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 { /// Metadata about a mod from the ModDrop API. 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 { /// An entry in a mod list from the ModDrop API. diff --git a/src/SMAPI.Web/Framework/Clients/Nexus/NexusClient.cs b/src/SMAPI.Web/Framework/Clients/Nexus/NexusClient.cs index a6da0c1c..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; 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 { /// The response for a get-paste request. 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 93cde9d3..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; 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 { /// Handles GZip compression logic. 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 { /// The config settings for the API clients. 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 { /// Override update-check metadata for a mod. 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 { /// The config settings for mod update checks. 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 { /// The site config settings. 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 { /// The update-check config for SMAPI's own update checks. diff --git a/src/SMAPI.Web/Framework/Extensions.cs b/src/SMAPI.Web/Framework/Extensions.cs index 2e767b3d..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; 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 { /// Generic metadata about a file download on a mod page. 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 3c1405eb..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 diff --git a/src/SMAPI.Web/Framework/LogParsing/LogMessageBuilder.cs b/src/SMAPI.Web/Framework/LogParsing/LogMessageBuilder.cs index 9da27d61..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; 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 *********/ /// Construct an instance. /// The user-friendly error message. - 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 864caef1..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; 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 { /// A parsed log message. 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 { /// Metadata about a mod or content pack in the log. 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 86f70788..021d14fb 100644 --- a/src/SMAPI.Web/Framework/ModInfoModel.cs +++ b/src/SMAPI.Web/Framework/ModInfoModel.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Web.Framework { /// Generic metadata about a mod. 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 d67b5156..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; 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 265a605f..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; 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 1104df89..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; 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 { /// The result of an attempt to upload a file. 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 6d9591ee..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; 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 { /// The view model for the index page. 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 { /// The fields for a SMAPI version. 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 { /// The view model for a JSON validation request. diff --git a/src/SMAPI.Web/ViewModels/LogParserModel.cs b/src/SMAPI.Web/ViewModels/LogParserModel.cs index 5aa2fd92..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; 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 { /// Metadata about a link. 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 3ddc6303..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 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/Constants.cs b/src/SMAPI/Constants.cs index 2fac8c94..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; diff --git a/src/SMAPI/Context.cs b/src/SMAPI/Context.cs index aa4ecf35..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; 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..82b59290 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; 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 { /// Manages access to events raised by SMAPI. 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 0c0f6685..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; diff --git a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs index 643267ce..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; 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 { /// A core SMAPI console command. 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 5986e797..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; diff --git a/src/SMAPI/Framework/Content/AssetDataForObject.cs b/src/SMAPI/Framework/Content/AssetDataForObject.cs index bafd8941..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; 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 03d6da5a..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; 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 4973b444..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; 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 { /// A set of operations to apply to an asset for a given or implementation. 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 0339b802..cdc4bc62 100644 --- a/src/SMAPI/Framework/Content/TilesheetReference.cs +++ b/src/SMAPI/Framework/Content/TilesheetReference.cs @@ -1,3 +1,5 @@ +#nullable disable + using xTile.Dimensions; namespace StardewModdingAPI.Framework.Content diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 88ed96da..bfde649a 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; diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index c803905a..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; diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index cad5f6db..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; 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..c4625761 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; diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index e0c85265..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; diff --git a/src/SMAPI/Framework/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs index e02ef88b..2d33a22e 100644 --- a/src/SMAPI/Framework/ContentPack.cs +++ b/src/SMAPI/Framework/ContentPack.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.IO; using StardewModdingAPI.Framework.ModHelpers; 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 ad6a3677..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; 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 { /// A deprecation warning for a mod. 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 { /// Metadata for an event raised by SMAPI. diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs index 1c8b9d04..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; diff --git a/src/SMAPI/Framework/Events/ManagedEventHandler.cs b/src/SMAPI/Framework/Events/ManagedEventHandler.cs index 97040f76..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; 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 { /// An internal base class for event API classes. 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 09bc48b6..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; 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 72adca02..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; diff --git a/src/SMAPI/Framework/InternalExtensions.cs b/src/SMAPI/Framework/InternalExtensions.cs index 54aeffd7..a1d87487 100644 --- a/src/SMAPI/Framework/InternalExtensions.cs +++ b/src/SMAPI/Framework/InternalExtensions.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.Linq; 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 { /// The common base class for mod helpers. 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 14aa74c2..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; 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 0eb385d4..ed0c46d5 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; 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 7468cda1..90064354 100644 --- a/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Microsoft.Xna.Framework.Content; using StardewModdingAPI.Framework.Content; diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index 5b567ee0..2a8aeb3a 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; diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 09a392a6..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; 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 { /// A generic tuple which links something to a mod. 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 { /// Indicates the result of an assembly load. diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index 24214c96..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; 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 d52cdbb4..51463048 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; 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 f715fb6b..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; diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs index 2eee8ff9..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; 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 d28c4a22..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; 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 c0f8d537..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; diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index 1e78f316..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; diff --git a/src/SMAPI/Framework/Monitor.cs b/src/SMAPI/Framework/Monitor.cs index 6b53daff..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; 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 { /// Metadata about an installed mod exchanged with connected computers. 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 { /// Metadata about the game, SMAPI, and installed mods exchanged with connected computers. 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 71e11576..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; 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 ff871e64..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; 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 3a93ab5d..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; 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 { /// 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. 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 b471c383..8a5c10f6 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; diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 989b59d8..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; diff --git a/src/SMAPI/Framework/SGameRunner.cs b/src/SMAPI/Framework/SGameRunner.cs index 81cac145..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; 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 bcf97006..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; diff --git a/src/SMAPI/Framework/Serialization/ColorConverter.cs b/src/SMAPI/Framework/Serialization/ColorConverter.cs index 3b3720b5..8fb6cd9c 100644 --- a/src/SMAPI/Framework/Serialization/ColorConverter.cs +++ b/src/SMAPI/Framework/Serialization/ColorConverter.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Microsoft.Xna.Framework; using Newtonsoft.Json.Linq; diff --git a/src/SMAPI/Framework/Serialization/KeybindConverter.cs b/src/SMAPI/Framework/Serialization/KeybindConverter.cs index 7c5db3ad..9cf52228 100644 --- a/src/SMAPI/Framework/Serialization/KeybindConverter.cs +++ b/src/SMAPI/Framework/Serialization/KeybindConverter.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Newtonsoft.Json; using Newtonsoft.Json.Linq; diff --git a/src/SMAPI/Framework/Serialization/PointConverter.cs b/src/SMAPI/Framework/Serialization/PointConverter.cs index 21d1f845..b48d757a 100644 --- a/src/SMAPI/Framework/Serialization/PointConverter.cs +++ b/src/SMAPI/Framework/Serialization/PointConverter.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Microsoft.Xna.Framework; using Newtonsoft.Json.Linq; diff --git a/src/SMAPI/Framework/Serialization/RectangleConverter.cs b/src/SMAPI/Framework/Serialization/RectangleConverter.cs index 31f3ad77..7f060e3a 100644 --- a/src/SMAPI/Framework/Serialization/RectangleConverter.cs +++ b/src/SMAPI/Framework/Serialization/RectangleConverter.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Text.RegularExpressions; using Microsoft.Xna.Framework; diff --git a/src/SMAPI/Framework/Serialization/Vector2Converter.cs b/src/SMAPI/Framework/Serialization/Vector2Converter.cs index 589febf8..bcd483d3 100644 --- a/src/SMAPI/Framework/Serialization/Vector2Converter.cs +++ b/src/SMAPI/Framework/Serialization/Vector2Converter.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Microsoft.Xna.Framework; using Newtonsoft.Json.Linq; diff --git a/src/SMAPI/Framework/Singleton.cs b/src/SMAPI/Framework/Singleton.cs index 1bf318c4..da16c48e 100644 --- a/src/SMAPI/Framework/Singleton.cs +++ b/src/SMAPI/Framework/Singleton.cs @@ -1,3 +1,5 @@ +#nullable disable + namespace StardewModdingAPI.Framework { /// Provides singleton instances of a given type. 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 1c3ebfba..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; diff --git a/src/SMAPI/Framework/StateTracking/ChestTracker.cs b/src/SMAPI/Framework/StateTracking/ChestTracker.cs index 56aeeb3c..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; 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 256370ce..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; 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 84340fbf..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; diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs index 676c9fb4..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; 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 3e9fa8b1..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; 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 { /// A watcher which tracks changes to a value. 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 f86f86ee..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; diff --git a/src/SMAPI/Framework/StateTracking/PlayerTracker.cs b/src/SMAPI/Framework/StateTracking/PlayerTracker.cs index 3d470b5c..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; diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs index 2563d10c..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; diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs index e113d27c..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; diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/WatcherSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/WatcherSnapshot.cs index afea7fb4..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; diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/WorldLocationsSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/WorldLocationsSnapshot.cs index 7dee09ca..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; 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 c0f119f1..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. 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 { /// Counts down from a baseline value. internal class Countdown diff --git a/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs b/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs index d0f276d2..94ce0069 100644 --- a/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs +++ b/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; diff --git a/src/SMAPI/Framework/WatcherCore.cs b/src/SMAPI/Framework/WatcherCore.cs index a1612e75..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; 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 { /// The implementation for a Stardew Valley mod. 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 { /// Metadata for a loaded mod. 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 { /// An instance linked to a mod. 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 { /// Encapsulates monitoring and logging for a given module. 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 { /// Metadata about a mod installed by a connected player. 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 159e19fd..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; 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 1039cc9a..a8664160 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; diff --git a/src/SMAPI/SemanticVersion.cs b/src/SMAPI/SemanticVersion.cs index ae616419..4e484633 100644 --- a/src/SMAPI/SemanticVersion.cs +++ b/src/SMAPI/SemanticVersion.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using Newtonsoft.Json; diff --git a/src/SMAPI/Translation.cs b/src/SMAPI/Translation.cs index 5ab432f0..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; diff --git a/src/SMAPI/Utilities/CaseInsensitivePathCache.cs b/src/SMAPI/Utilities/CaseInsensitivePathCache.cs index 1d947b53..4596fdce 100644 --- a/src/SMAPI/Utilities/CaseInsensitivePathCache.cs +++ b/src/SMAPI/Utilities/CaseInsensitivePathCache.cs @@ -1,3 +1,5 @@ +#nullable disable + using System; using System.Collections.Generic; using System.IO; diff --git a/src/SMAPI/Utilities/Keybind.cs b/src/SMAPI/Utilities/Keybind.cs index 7aefe686..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; diff --git a/src/SMAPI/Utilities/KeybindList.cs b/src/SMAPI/Utilities/KeybindList.cs index f24976f7..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; diff --git a/src/SMAPI/Utilities/PathUtilities.cs b/src/SMAPI/Utilities/PathUtilities.cs index 541b163c..e8ab9645 100644 --- a/src/SMAPI/Utilities/PathUtilities.cs +++ b/src/SMAPI/Utilities/PathUtilities.cs @@ -1,3 +1,5 @@ +#nullable disable + using System.Diagnostics.Contracts; using ToolkitPathUtilities = StardewModdingAPI.Toolkit.Utilities.PathUtilities; diff --git a/src/SMAPI/Utilities/PerScreen.cs b/src/SMAPI/Utilities/PerScreen.cs index 6b7153ac..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; 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; -- cgit From b4e979cc991a0c2a45ad986210108edd2d43e43d Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 6 Apr 2022 22:46:19 -0400 Subject: fix all warnings to simplify migration to nullable annotations (#837) --- docs/technical/mod-package.md | 1 + .../Framework/DiagnosticVerifier.Helper.cs | 24 ++++++++--------- .../Framework/DiagnosticVerifier.cs | 30 +++++++++++----------- .../AnalyzerReleases.Shipped.md | 7 +++++ .../SMAPI.ModBuildConfig.Analyzer.csproj | 4 +++ .../SMAPI.ModBuildConfig.csproj | 1 + src/SMAPI.Tests/Core/AssetNameTests.cs | 1 - src/SMAPI/Framework/ContentCoordinator.cs | 2 ++ .../Framework/ContentManagers/IContentManager.cs | 1 + src/SMAPI/Framework/ModHelpers/ModHelper.cs | 10 +++++++- src/SMAPI/Framework/SCore.cs | 4 +++ 11 files changed, 56 insertions(+), 29 deletions(-) create mode 100644 src/SMAPI.ModBuildConfig.Analyzer/AnalyzerReleases.Shipped.md (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/docs/technical/mod-package.md b/docs/technical/mod-package.md index 5e408168..4c31f69b 100644 --- a/docs/technical/mod-package.md +++ b/docs/technical/mod-package.md @@ -414,6 +414,7 @@ when you compile it. ## Release notes ## Upcoming release * Added detection for Xbox app game folders. +* Internal refactoring. ## 4.0.0 Released 30 November 2021. diff --git a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.Helper.cs b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.Helper.cs index 8d72fea1..68a892a9 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.Helper.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.Helper.cs @@ -53,17 +53,17 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework protected static Diagnostic[] GetSortedDiagnosticsFromDocuments(DiagnosticAnalyzer analyzer, Document[] documents) { var projects = new HashSet(); - foreach (var document in documents) + foreach (Document document in documents) { projects.Add(document.Project); } var diagnostics = new List(); - 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) { @@ -73,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); @@ -115,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) @@ -148,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) @@ -160,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 09d3a3f8..4170042d 100644 --- a/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.cs +++ b/src/SMAPI.ModBuildConfig.Analyzer.Tests/Framework/DiagnosticVerifier.cs @@ -43,7 +43,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework /// DiagnosticResults that should appear after the analyzer is run on the source protected void VerifyCSharpDiagnostic(string source, params DiagnosticResult[] expected) { - VerifyDiagnostics(new[] { source }, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected); + this.VerifyDiagnostics(new[] { source }, LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzer(), expected); } /// @@ -54,7 +54,7 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework /// DiagnosticResults that should appear after the analyzer is run on the sources protected void VerifyCSharpDiagnostic(string[] sources, params DiagnosticResult[] expected) { - VerifyDiagnostics(sources, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected); + this.VerifyDiagnostics(sources, LanguageNames.CSharp, this.GetCSharpDiagnosticAnalyzer(), expected); } /// @@ -67,8 +67,8 @@ namespace SMAPI.ModBuildConfig.Analyzer.Tests.Framework /// DiagnosticResults that should appear after the analyzer is run on the sources 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 @@ -88,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)); @@ -105,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) @@ -118,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]); } } @@ -131,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))); } } } @@ -163,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; @@ -174,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))); } } @@ -185,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))); } } } 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/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 @@ -12,6 +12,10 @@ + + + + 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 @@ netstandard2.0 latest true + true Pathoschild.Stardew.ModBuildConfig diff --git a/src/SMAPI.Tests/Core/AssetNameTests.cs b/src/SMAPI.Tests/Core/AssetNameTests.cs index b7e34191..ef8a08ef 100644 --- a/src/SMAPI.Tests/Core/AssetNameTests.cs +++ b/src/SMAPI.Tests/Core/AssetNameTests.cs @@ -34,7 +34,6 @@ namespace SMAPI.Tests.Core name = PathUtilities.NormalizeAssetName(name); // act - string calledWithLocale = null; IAssetName assetName = AssetName.Parse(name, parseLocale: _ => expectedLanguageCode); // assert diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index bfde649a..81820b05 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -610,6 +610,7 @@ namespace StardewModdingAPI.Framework yield return group; // legacy load operations +#pragma warning disable CS0612, CS0618 // deprecated code foreach (ModLinked loader in this.Loaders) { // check if loader applies @@ -695,6 +696,7 @@ namespace StardewModdingAPI.Framework } ); } +#pragma warning restore CS0612, CS0618 } /// Get an asset info compatible with legacy and instances, which always expect the base name. diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs index c4625761..c8b2ae64 100644 --- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs @@ -38,6 +38,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Load an asset through the content pipeline, using a localized variant of the if available. /// The type of asset to load. /// The asset name relative to the loader root directory. + /// The language for which to load the asset. /// Whether to read/write the loaded asset to the asset cache. T LoadLocalized(IAssetName assetName, LocalizedContentManager.LanguageCode language, bool useCache); diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index 2a8aeb3a..3cfe52bf 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -95,7 +95,13 @@ namespace StardewModdingAPI.Framework.ModHelpers /// An API for reading translations stored in the mod's i18n folder. /// An argument is null or empty. /// The path does not exist on disk. - public ModHelper(string modID, string modDirectory, Func currentInputState, IModEvents events, ContentHelper 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 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 @@ -106,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)); diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 8a5c10f6..6ca463a2 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1593,6 +1593,7 @@ 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 @@ -1628,6 +1629,7 @@ namespace StardewModdingAPI.Framework content.ObservableAssetEditors.CollectionChanged += (_, e) => this.OnAssetInterceptorsChanged(metadata, e.NewItems?.Cast(), e.OldItems?.Cast(), this.ContentCore.Editors); content.ObservableAssetLoaders.CollectionChanged += (_, e) => this.OnAssetInterceptorsChanged(metadata, e.NewItems?.Cast(), e.OldItems?.Cast(), this.ContentCore.Loaders); } +#pragma warning restore CS0612, CS0618 // call entry method try @@ -1847,7 +1849,9 @@ namespace StardewModdingAPI.Framework IModEvents events = new ModEvents(mod, this.EventManager); ICommandHelper commandHelper = new CommandHelper(mod, this.CommandManager); 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(), relativePathCache); IContentPackHelper contentPackHelper = new ContentPackHelper(manifest.UniqueID, new Lazy(GetContentPacks), CreateFakeContentPack); -- cgit From df955c0d3e0110be0893082de0c82187de63d9d2 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 7 Apr 2022 18:46:29 -0400 Subject: enable asset caching for GameContentHelper --- src/SMAPI/Framework/ModHelpers/GameContentHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs index ed0c46d5..956bac7f 100644 --- a/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs @@ -75,7 +75,7 @@ namespace StardewModdingAPI.Framework.ModHelpers { try { - return this.GameContentManager.LoadLocalized(assetName, this.CurrentLocaleConstant, useCache: false); + return this.GameContentManager.LoadLocalized(assetName, this.CurrentLocaleConstant, useCache: true); } catch (Exception ex) when (ex is not SContentLoadException) { -- cgit From 1e61309d3dce484d5b646b1a8d15c825beac813f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 11 Apr 2022 22:56:14 -0400 Subject: add IAssetDataForMap.ExtendMap --- docs/release-notes.md | 1 + src/SMAPI/Framework/Content/AssetDataForMap.cs | 54 +++++++++++++++++++++- src/SMAPI/Framework/Content/AssetDataForObject.cs | 23 +++++++-- src/SMAPI/Framework/ContentCoordinator.cs | 3 +- .../ContentManagers/BaseContentManager.cs | 4 ++ .../ContentManagers/GameContentManager.cs | 6 +-- src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 16 ++++++- .../Framework/ModHelpers/GameContentHelper.cs | 16 ++++++- src/SMAPI/Framework/ModHelpers/ModContentHelper.cs | 16 ++++++- src/SMAPI/Framework/SCore.cs | 14 +++--- src/SMAPI/IAssetDataForImage.cs | 4 +- src/SMAPI/IAssetDataForMap.cs | 7 +++ 12 files changed, 138 insertions(+), 26 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/docs/release-notes.md b/docs/release-notes.md index 0687b888..7379cba3 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -45,6 +45,7 @@ the C# mod that loads them is updated. _This adds support for many previously unsupported cases: proxied interfaces in return values or input arguments, proxied enums if their values match, generic methods, and more. Existing mod APIs should work fine as-is._ * Mod files loaded through SMAPI APIs (including `helper.Content.Load`) are now case-insensitive, even on Linux. * Other improvements: + * Added `IAssetDataForImage.ExtendMap` to resize maps in asset editors. * Added [command-line arguments](technical/smapi.md#command-line-arguments) to toggle developer mode (thanks to Tondorian!). * Added `IContentPack.ModContent` property. * Added `Constants.ContentPath`. diff --git a/src/SMAPI/Framework/Content/AssetDataForMap.cs b/src/SMAPI/Framework/Content/AssetDataForMap.cs index 0425e195..93148277 100644 --- a/src/SMAPI/Framework/Content/AssetDataForMap.cs +++ b/src/SMAPI/Framework/Content/AssetDataForMap.cs @@ -4,17 +4,27 @@ using System; using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework; +using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Toolkit.Utilities; using StardewValley; using xTile; +using xTile.Dimensions; using xTile.Layers; using xTile.Tiles; +using Rectangle = Microsoft.Xna.Framework.Rectangle; namespace StardewModdingAPI.Framework.Content { /// Encapsulates access and changes to image content being read from a data file. internal class AssetDataForMap : AssetData, IAssetDataForMap { + /********* + ** Fields + *********/ + /// Simplifies access to private code. + private readonly Reflector Reflection; + + /********* ** Public methods *********/ @@ -24,8 +34,12 @@ namespace StardewModdingAPI.Framework.Content /// The content data being read. /// Normalizes an asset key to match the cache key. /// A callback to invoke when the data is replaced (if any). - public AssetDataForMap(string locale, IAssetName assetName, Map data, Func getNormalizedPath, Action onDataReplaced) - : base(locale, assetName, data, getNormalizedPath, onDataReplaced) { } + /// Simplifies access to private code. + public AssetDataForMap(string locale, IAssetName assetName, Map data, Func getNormalizedPath, Action onDataReplaced, Reflector reflection) + : base(locale, assetName, data, getNormalizedPath, onDataReplaced) + { + this.Reflection = reflection; + } /// /// Derived from with a few changes: @@ -137,6 +151,42 @@ namespace StardewModdingAPI.Framework.Content } } + /// + public bool ExtendMap(Map map, int minWidth, int minHeight) + { + bool resized = false; + + // resize layers + foreach (Layer layer in map.Layers) + { + // check if resize needed + if (layer.LayerWidth >= minWidth && layer.LayerHeight >= minHeight) + continue; + resized = true; + + // build new tile matrix + int width = Math.Max(minWidth, layer.LayerWidth); + int height = Math.Max(minHeight, layer.LayerHeight); + Tile[,] tiles = new Tile[width, height]; + for (int x = 0; x < layer.LayerWidth; x++) + { + for (int y = 0; y < layer.LayerHeight; y++) + tiles[x, y] = layer.Tiles[x, y]; + } + + // update fields + this.Reflection.GetField(layer, "m_tiles").SetValue(tiles); + this.Reflection.GetField(layer, "m_tileArray").SetValue(new TileArray(layer, tiles)); + this.Reflection.GetField(layer, "m_layerSize").SetValue(new Size(width, height)); + } + + // resize map + if (resized) + this.Reflection.GetMethod(map, "UpdateDisplaySize").Invoke(); + + return resized; + } + /********* ** Private methods diff --git a/src/SMAPI/Framework/Content/AssetDataForObject.cs b/src/SMAPI/Framework/Content/AssetDataForObject.cs index 4a6df64b..bb3966b9 100644 --- a/src/SMAPI/Framework/Content/AssetDataForObject.cs +++ b/src/SMAPI/Framework/Content/AssetDataForObject.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; +using StardewModdingAPI.Framework.Reflection; using xTile; namespace StardewModdingAPI.Framework.Content @@ -10,6 +11,13 @@ namespace StardewModdingAPI.Framework.Content /// Encapsulates access and changes to content being read from a data file. internal class AssetDataForObject : AssetData, IAssetData { + /********* + ** Fields + *********/ + /// Simplifies access to private code. + private readonly Reflector Reflection; + + /********* ** Public methods *********/ @@ -18,15 +26,20 @@ namespace StardewModdingAPI.Framework.Content /// The asset name being read. /// The content data being read. /// Normalizes an asset key to match the cache key. - public AssetDataForObject(string locale, IAssetName assetName, object data, Func getNormalizedPath) - : base(locale, assetName, data, getNormalizedPath, onDataReplaced: null) { } + /// Simplifies access to private code. + public AssetDataForObject(string locale, IAssetName assetName, object data, Func getNormalizedPath, Reflector reflection) + : base(locale, assetName, data, getNormalizedPath, onDataReplaced: null) + { + this.Reflection = reflection; + } /// Construct an instance. /// The asset metadata. /// The content data being read. /// Normalizes an asset key to match the cache key. - public AssetDataForObject(IAssetInfo info, object data, Func getNormalizedPath) - : this(info.Locale, info.Name, data, getNormalizedPath) { } + /// Simplifies access to private code. + public AssetDataForObject(IAssetInfo info, object data, Func getNormalizedPath, Reflector reflection) + : this(info.Locale, info.Name, data, getNormalizedPath, reflection) { } /// public IAssetDataForDictionary AsDictionary() @@ -43,7 +56,7 @@ namespace StardewModdingAPI.Framework.Content /// public IAssetDataForMap AsMap() { - return new AssetDataForMap(this.Locale, this.Name, this.GetData(), this.GetNormalizedPath, this.ReplaceWith); + return new AssetDataForMap(this.Locale, this.Name, this.GetData(), this.GetNormalizedPath, this.ReplaceWith, this.Reflection); } /// diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 81820b05..4e48b08c 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -727,7 +727,8 @@ namespace StardewModdingAPI.Framework locale: null, assetName: legacyName, data: asset.Data, - getNormalizedPath: this.MainContentManager.AssertAndNormalizeAssetName + getNormalizedPath: this.MainContentManager.AssertAndNormalizeAssetName, + reflection: this.Reflection ); } diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index 4594d235..f1ccab48 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -32,6 +32,9 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Encapsulates monitoring and logging. protected readonly IMonitor Monitor; + /// Simplifies access to private code. + protected readonly Reflector Reflection; + /// Whether to enable more aggressive memory optimizations. protected readonly bool AggressiveMemoryOptimizations; @@ -90,6 +93,7 @@ namespace StardewModdingAPI.Framework.ContentManagers this.Coordinator = coordinator ?? throw new ArgumentNullException(nameof(coordinator)); this.Cache = new ContentCache(this, reflection); this.Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor)); + this.Reflection = reflection; this.OnDisposing = onDisposing; this.IsNamespaced = isNamespaced; this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations; diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index f4e1bda4..e494559d 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -124,7 +124,7 @@ namespace StardewModdingAPI.Framework.ContentManagers IAssetInfo info = new AssetInfo(assetName.LocaleCode, assetName, typeof(T), this.AssertAndNormalizeAssetName); IAssetData asset = this.ApplyLoader(info) - ?? new AssetDataForObject(info, this.RawLoad(assetName, useCache), this.AssertAndNormalizeAssetName); + ?? new AssetDataForObject(info, this.RawLoad(assetName, useCache), this.AssertAndNormalizeAssetName, this.Reflection); asset = this.ApplyEditors(info, asset); return (T)asset.Data; }); @@ -187,7 +187,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // return matched asset return this.TryFixAndValidateLoadedAsset(info, data, loader) - ? new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName) + ? new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName, this.Reflection) : null; } @@ -197,7 +197,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The loaded asset. private IAssetData ApplyEditors(IAssetInfo info, IAssetData asset) { - IAssetData GetNewData(object data) => new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName); + IAssetData GetNewData(object data) => new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName, this.Reflection); // special case: if the asset was loaded with a more general type like 'object', call editors with the actual type instead. { diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index e72e397e..12ef0439 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -10,6 +10,7 @@ using System.Linq; using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.ContentManagers; using StardewModdingAPI.Framework.Exceptions; +using StardewModdingAPI.Framework.Reflection; using StardewValley; namespace StardewModdingAPI.Framework.ModHelpers @@ -36,6 +37,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Encapsulates monitoring and logging. private readonly IMonitor Monitor; + /// Simplifies access to private code. + private readonly Reflector Reflection; + /********* ** Accessors @@ -94,7 +98,8 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The unique ID of the relevant mod. /// The friendly mod name for use in errors. /// Encapsulates monitoring and logging. - public ContentHelper(ContentCoordinator contentCore, string modFolderPath, string modID, string modName, IMonitor monitor) + /// Simplifies access to private code. + public ContentHelper(ContentCoordinator contentCore, string modFolderPath, string modID, string modName, IMonitor monitor, Reflector reflection) : base(modID) { string managedAssetPrefix = contentCore.GetManagedAssetPrefix(modID); @@ -104,6 +109,7 @@ namespace StardewModdingAPI.Framework.ModHelpers this.ModContentManager = contentCore.CreateModContentManager(managedAssetPrefix, modName, modFolderPath, this.GameContentManager); this.ModName = modName; this.Monitor = monitor; + this.Reflection = reflection; } /// @@ -185,7 +191,13 @@ namespace StardewModdingAPI.Framework.ModHelpers assetName ??= $"temp/{Guid.NewGuid():N}"; - 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); + return new AssetDataForObject( + locale: this.CurrentLocale, + assetName: this.ContentCore.ParseAssetName(assetName, allowLocales: true/* no way to know if it's a game or mod asset here*/), + data: data, + getNormalizedPath: this.NormalizeAssetName, + reflection: this.Reflection + ); } diff --git a/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs index 956bac7f..6d0c2f5f 100644 --- a/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs @@ -5,6 +5,7 @@ using System.Linq; using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.ContentManagers; using StardewModdingAPI.Framework.Exceptions; +using StardewModdingAPI.Framework.Reflection; using StardewValley; namespace StardewModdingAPI.Framework.ModHelpers @@ -27,6 +28,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Encapsulates monitoring and logging. private readonly IMonitor Monitor; + /// Simplifies access to private code. + private readonly Reflector Reflection; + /********* ** Accessors @@ -46,7 +50,8 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The unique ID of the relevant mod. /// The friendly mod name for use in errors. /// Encapsulates monitoring and logging. - public GameContentHelper(ContentCoordinator contentCore, string modID, string modName, IMonitor monitor) + /// Simplifies access to private code. + public GameContentHelper(ContentCoordinator contentCore, string modID, string modName, IMonitor monitor, Reflector reflection) : base(modID) { string managedAssetPrefix = contentCore.GetManagedAssetPrefix(modID); @@ -55,6 +60,7 @@ namespace StardewModdingAPI.Framework.ModHelpers this.GameContentManager = contentCore.CreateGameContentManager(managedAssetPrefix + ".content"); this.ModName = modName; this.Monitor = monitor; + this.Reflection = reflection; } /// @@ -119,7 +125,13 @@ namespace StardewModdingAPI.Framework.ModHelpers assetName ??= $"temp/{Guid.NewGuid():N}"; - return new AssetDataForObject(this.CurrentLocale, this.ContentCore.ParseAssetName(assetName, allowLocales: true), data, key => this.ParseAssetName(key).Name); + return new AssetDataForObject( + locale: this.CurrentLocale, + assetName: this.ContentCore.ParseAssetName(assetName, allowLocales: true), + data: data, + getNormalizedPath: key => this.ParseAssetName(key).Name, + reflection: this.Reflection + ); } /// Get the underlying game content manager. diff --git a/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs index 90064354..b149ed82 100644 --- a/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs @@ -5,6 +5,7 @@ using Microsoft.Xna.Framework.Content; using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.ContentManagers; using StardewModdingAPI.Framework.Exceptions; +using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Utilities; namespace StardewModdingAPI.Framework.ModHelpers @@ -27,6 +28,9 @@ namespace StardewModdingAPI.Framework.ModHelpers /// A case-insensitive lookup of relative paths within the . private readonly CaseInsensitivePathCache RelativePathCache; + /// Simplifies access to private code. + private readonly Reflector Reflection; + /********* ** Public methods @@ -38,7 +42,8 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The friendly mod name for use in errors. /// The game content manager used for map tilesheets not provided by the mod. /// A case-insensitive lookup of relative paths within the . - public ModContentHelper(ContentCoordinator contentCore, string modFolderPath, string modID, string modName, IContentManager gameContentManager, CaseInsensitivePathCache relativePathCache) + /// Simplifies access to private code. + public ModContentHelper(ContentCoordinator contentCore, string modFolderPath, string modID, string modName, IContentManager gameContentManager, CaseInsensitivePathCache relativePathCache, Reflector reflection) : base(modID) { string managedAssetPrefix = contentCore.GetManagedAssetPrefix(modID); @@ -47,6 +52,7 @@ namespace StardewModdingAPI.Framework.ModHelpers this.ModContentManager = contentCore.CreateModContentManager(managedAssetPrefix, modName, modFolderPath, gameContentManager); this.ModName = modName; this.RelativePathCache = relativePathCache; + this.Reflection = reflection; } /// @@ -83,7 +89,13 @@ namespace StardewModdingAPI.Framework.ModHelpers ? this.RelativePathCache.GetAssetName(relativePath) : $"temp/{Guid.NewGuid():N}"; - return new AssetDataForObject(this.ContentCore.GetLocale(), this.ContentCore.ParseAssetName(relativePath, allowLocales: false), data, key => this.ContentCore.ParseAssetName(key, allowLocales: false).Name); + return new AssetDataForObject( + locale: this.ContentCore.GetLocale(), + assetName: this.ContentCore.ParseAssetName(relativePath, allowLocales: false), + data: data, + getNormalizedPath: key => this.ContentCore.ParseAssetName(key, allowLocales: false).Name, + reflection: this.Reflection + ); } } } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 1a58d84b..364a7632 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1754,8 +1754,8 @@ 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(), relativePathCache); + GameContentHelper gameContentHelper = new(this.ContentCore, manifest.UniqueID, mod.DisplayName, monitor, this.Reflection); + IModContentHelper modContentHelper = new ModContentHelper(this.ContentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager(), relativePathCache, this.Reflection); TranslationHelper translationHelper = new(manifest.UniqueID, contentCore.GetLocale(), contentCore.Language); IContentPack contentPack = new ContentPack(mod.DirectoryPath, manifest, modContentHelper, translationHelper, jsonHelper, relativePathCache); mod.SetMod(contentPack, monitor, translationHelper); @@ -1838,8 +1838,8 @@ namespace StardewModdingAPI.Framework 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(), relativePathCache); + GameContentHelper gameContentHelper = new(contentCore, packManifest.UniqueID, packManifest.Name, packMonitor, this.Reflection); + IModContentHelper packContentHelper = new ModContentHelper(contentCore, packDirPath, packManifest.UniqueID, packManifest.Name, gameContentHelper.GetUnderlyingContentManager(), relativePathCache, this.Reflection); TranslationHelper packTranslationHelper = new(packManifest.UniqueID, contentCore.GetLocale(), contentCore.Language); ContentPack contentPack = new(packDirPath, packManifest, packContentHelper, packTranslationHelper, this.Toolkit.JsonHelper, relativePathCache); @@ -1852,10 +1852,10 @@ namespace StardewModdingAPI.Framework ICommandHelper commandHelper = new CommandHelper(mod, this.CommandManager); CaseInsensitivePathCache relativePathCache = this.ContentCore.GetCaseInsensitivePathCache(mod.DirectoryPath); #pragma warning disable CS0612 // deprecated code - ContentHelper contentHelper = new(contentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, monitor); + ContentHelper contentHelper = new(contentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, monitor, this.Reflection); #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(), relativePathCache); + GameContentHelper gameContentHelper = new(contentCore, manifest.UniqueID, mod.DisplayName, monitor, this.Reflection); + IModContentHelper modContentHelper = new ModContentHelper(contentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager(), relativePathCache, this.Reflection); IContentPackHelper contentPackHelper = new ContentPackHelper(manifest.UniqueID, new Lazy(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/IAssetDataForImage.cs b/src/SMAPI/IAssetDataForImage.cs index 388caa68..1416592e 100644 --- a/src/SMAPI/IAssetDataForImage.cs +++ b/src/SMAPI/IAssetDataForImage.cs @@ -23,8 +23,8 @@ namespace StardewModdingAPI void PatchImage(Texture2D source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMode patchMode = PatchMode.Replace); /// Extend the image if needed to fit the given size. Note that this is an expensive operation, creates a new texture instance, and that extending a spritesheet horizontally may cause game errors or bugs. - /// The minimum texture width. - /// The minimum texture height. + /// The minimum texture width in pixels. + /// The minimum texture height in pixels. /// Whether the texture was resized. bool ExtendImage(int minWidth, int minHeight); } diff --git a/src/SMAPI/IAssetDataForMap.cs b/src/SMAPI/IAssetDataForMap.cs index 89ee28f2..0b637baf 100644 --- a/src/SMAPI/IAssetDataForMap.cs +++ b/src/SMAPI/IAssetDataForMap.cs @@ -17,5 +17,12 @@ namespace StardewModdingAPI /// The tile area within the target map to overwrite, or null to patch the whole map. The original content within this area will be erased. This must be within the bounds of the existing map. /// Indicates how the map should be patched. void PatchMap(Map source, Rectangle? sourceArea = null, Rectangle? targetArea = null, PatchMapMode patchMode = PatchMapMode.Overlay); + + /// Extend the map if needed to fit the given size. Note that this is an expensive operation and resizes the map in-place. + /// The map to resize. + /// The minimum map width in tiles. + /// The minimum map height in tiles. + /// Whether the map was resized. + bool ExtendMap(Map map, int minWidth, int minHeight); } } -- cgit From 6e9e8aef1ef97e1a4ef4410ce300cb1c47eca986 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 13 Apr 2022 19:00:24 -0400 Subject: enable nullable annotations in SMAPI where no changes are needed (#837) --- src/SMAPI/Context.cs | 2 -- src/SMAPI/Events/AssetReadyEventArgs.cs | 2 -- src/SMAPI/Events/AssetsInvalidatedEventArgs.cs | 2 -- src/SMAPI/Events/BuildingListChangedEventArgs.cs | 2 -- src/SMAPI/Events/ButtonPressedEventArgs.cs | 2 -- src/SMAPI/Events/ButtonReleasedEventArgs.cs | 2 -- src/SMAPI/Events/ButtonsChangedEventArgs.cs | 2 -- src/SMAPI/Events/ChestInventoryChangedEventArgs.cs | 2 -- src/SMAPI/Events/CursorMovedEventArgs.cs | 2 -- src/SMAPI/Events/DebrisListChangedEventArgs.cs | 2 -- src/SMAPI/Events/FurnitureListChangedEventArgs.cs | 2 -- src/SMAPI/Events/IContentEvents.cs | 2 -- src/SMAPI/Events/IDisplayEvents.cs | 2 -- src/SMAPI/Events/IGameLoopEvents.cs | 2 -- src/SMAPI/Events/IInputEvents.cs | 2 -- src/SMAPI/Events/IModEvents.cs | 2 -- src/SMAPI/Events/IMultiplayerEvents.cs | 2 -- src/SMAPI/Events/IPlayerEvents.cs | 2 -- src/SMAPI/Events/ISpecialisedEvents.cs | 2 -- src/SMAPI/Events/IWorldEvents.cs | 2 -- src/SMAPI/Events/InventoryChangedEventArgs.cs | 2 -- src/SMAPI/Events/ItemStackSizeChange.cs | 2 -- src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs | 2 -- src/SMAPI/Events/LevelChangedEventArgs.cs | 2 -- src/SMAPI/Events/LocaleChangedEventArgs.cs | 2 -- src/SMAPI/Events/LocationListChangedEventArgs.cs | 2 -- src/SMAPI/Events/NpcListChangedEventArgs.cs | 2 -- src/SMAPI/Events/ObjectListChangedEventArgs.cs | 2 -- src/SMAPI/Events/PeerConnectedEventArgs.cs | 2 -- src/SMAPI/Events/PeerContextReceivedEventArgs.cs | 2 -- src/SMAPI/Events/PeerDisconnectedEventArgs.cs | 2 -- src/SMAPI/Events/RenderedActiveMenuEventArgs.cs | 2 -- src/SMAPI/Events/RenderedEventArgs.cs | 2 -- src/SMAPI/Events/RenderedHudEventArgs.cs | 2 -- src/SMAPI/Events/RenderedWorldEventArgs.cs | 2 -- src/SMAPI/Events/RenderingActiveMenuEventArgs.cs | 2 -- src/SMAPI/Events/RenderingEventArgs.cs | 2 -- src/SMAPI/Events/RenderingHudEventArgs.cs | 2 -- src/SMAPI/Events/RenderingWorldEventArgs.cs | 2 -- src/SMAPI/Events/TerrainFeatureListChangedEventArgs.cs | 2 -- src/SMAPI/Events/WarpedEventArgs.cs | 2 -- src/SMAPI/Framework/Commands/IInternalCommand.cs | 2 -- src/SMAPI/Framework/Commands/ReloadI18nCommand.cs | 2 -- src/SMAPI/Framework/Content/AssetOperationGroup.cs | 2 -- src/SMAPI/Framework/Content/TilesheetReference.cs | 2 -- src/SMAPI/Framework/DeprecationWarning.cs | 2 -- src/SMAPI/Framework/Events/EventManager.cs | 2 -- src/SMAPI/Framework/Events/IManagedEvent.cs | 2 -- src/SMAPI/Framework/Events/ModContentEvents.cs | 2 -- src/SMAPI/Framework/Events/ModDisplayEvents.cs | 2 -- src/SMAPI/Framework/Events/ModEvents.cs | 2 -- src/SMAPI/Framework/Events/ModEventsBase.cs | 2 -- src/SMAPI/Framework/Events/ModGameLoopEvents.cs | 2 -- src/SMAPI/Framework/Events/ModInputEvents.cs | 2 -- src/SMAPI/Framework/Events/ModMultiplayerEvents.cs | 2 -- src/SMAPI/Framework/Events/ModPlayerEvents.cs | 2 -- src/SMAPI/Framework/Events/ModSpecialisedEvents.cs | 2 -- src/SMAPI/Framework/Events/ModWorldEvents.cs | 2 -- src/SMAPI/Framework/Exceptions/SAssemblyLoadFailedException.cs | 2 -- src/SMAPI/Framework/Input/IInputStateBuilder.cs | 2 -- src/SMAPI/Framework/Input/KeyboardStateBuilder.cs | 4 +--- src/SMAPI/Framework/Input/MouseStateBuilder.cs | 2 -- src/SMAPI/Framework/ModHelpers/BaseHelper.cs | 2 -- src/SMAPI/Framework/ModHelpers/ContentPackHelper.cs | 2 -- src/SMAPI/Framework/ModHelpers/InputHelper.cs | 2 -- src/SMAPI/Framework/ModHelpers/ModHelper.cs | 2 -- src/SMAPI/Framework/ModHelpers/TranslationHelper.cs | 2 -- src/SMAPI/Framework/ModLinked.cs | 2 -- src/SMAPI/Framework/ModLoading/AssemblyLoadStatus.cs | 2 -- src/SMAPI/Framework/ModLoading/IInstructionHandler.cs | 2 -- src/SMAPI/Framework/ModLoading/IncompatibleInstructionException.cs | 2 -- src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs | 2 -- src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs | 5 ++--- .../Framework/ModLoading/Rewriters/ArchitectureAssemblyRewriter.cs | 2 -- src/SMAPI/Framework/ModLoading/Symbols/SymbolReader.cs | 2 -- src/SMAPI/Framework/ModLoading/Symbols/SymbolWriterProvider.cs | 2 -- src/SMAPI/Framework/Monitor.cs | 2 -- src/SMAPI/Framework/Networking/SGalaxyNetClient.cs | 2 -- src/SMAPI/Framework/Networking/SGalaxyNetServer.cs | 2 -- src/SMAPI/Framework/Networking/SLidgrenClient.cs | 2 -- src/SMAPI/Framework/Networking/SLidgrenServer.cs | 2 -- src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs | 2 -- src/SMAPI/Framework/SChatBox.cs | 2 -- src/SMAPI/Framework/SGameRunner.cs | 4 +--- src/SMAPI/Framework/Singleton.cs | 2 -- .../Framework/StateTracking/FieldWatchers/BaseDisposableWatcher.cs | 2 -- .../Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs | 2 -- .../StateTracking/FieldWatchers/ImmutableCollectionWatcher.cs | 2 -- .../Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs | 2 -- src/SMAPI/Framework/StateTracking/FieldWatchers/NetListWatcher.cs | 2 -- src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs | 2 -- src/SMAPI/Framework/StateTracking/ICollectionWatcher.cs | 2 -- src/SMAPI/Framework/StateTracking/IDictionaryWatcher.cs | 2 -- src/SMAPI/Framework/StateTracking/IValueWatcher.cs | 2 -- src/SMAPI/Framework/StateTracking/IWatcher.cs | 2 -- src/SMAPI/Framework/Utilities/ContextHash.cs | 2 -- src/SMAPI/GamePlatform.cs | 2 -- src/SMAPI/IAssetDataForDictionary.cs | 2 -- src/SMAPI/IAssetDataForImage.cs | 2 -- src/SMAPI/IAssetDataForMap.cs | 2 -- src/SMAPI/IAssetEditor.cs | 2 -- src/SMAPI/IAssetLoader.cs | 2 -- src/SMAPI/ICommandHelper.cs | 2 -- src/SMAPI/IContentPackHelper.cs | 2 -- src/SMAPI/ICursorPosition.cs | 2 -- src/SMAPI/IInputHelper.cs | 2 -- src/SMAPI/IModHelper.cs | 2 -- src/SMAPI/IModInfo.cs | 2 -- src/SMAPI/IModLinked.cs | 2 -- src/SMAPI/IMonitor.cs | 2 -- src/SMAPI/IMultiplayerPeerMod.cs | 2 -- src/SMAPI/ITranslationHelper.cs | 4 +--- 112 files changed, 5 insertions(+), 228 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Context.cs b/src/SMAPI/Context.cs index e906375b..aa4ecf35 100644 --- a/src/SMAPI/Context.cs +++ b/src/SMAPI/Context.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using StardewModdingAPI.Enums; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Events/AssetReadyEventArgs.cs b/src/SMAPI/Events/AssetReadyEventArgs.cs index 19e5a9df..2c308f18 100644 --- a/src/SMAPI/Events/AssetReadyEventArgs.cs +++ b/src/SMAPI/Events/AssetReadyEventArgs.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/AssetsInvalidatedEventArgs.cs b/src/SMAPI/Events/AssetsInvalidatedEventArgs.cs index bd0df598..614cdf49 100644 --- a/src/SMAPI/Events/AssetsInvalidatedEventArgs.cs +++ b/src/SMAPI/Events/AssetsInvalidatedEventArgs.cs @@ -1,5 +1,3 @@ -#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 ba9574cc..74f37710 100644 --- a/src/SMAPI/Events/BuildingListChangedEventArgs.cs +++ b/src/SMAPI/Events/BuildingListChangedEventArgs.cs @@ -1,5 +1,3 @@ -#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 94684513..1b30fd23 100644 --- a/src/SMAPI/Events/ButtonPressedEventArgs.cs +++ b/src/SMAPI/Events/ButtonPressedEventArgs.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Framework.Input; diff --git a/src/SMAPI/Events/ButtonReleasedEventArgs.cs b/src/SMAPI/Events/ButtonReleasedEventArgs.cs index 6ff3727d..40ec1cc1 100644 --- a/src/SMAPI/Events/ButtonReleasedEventArgs.cs +++ b/src/SMAPI/Events/ButtonReleasedEventArgs.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Framework.Input; diff --git a/src/SMAPI/Events/ButtonsChangedEventArgs.cs b/src/SMAPI/Events/ButtonsChangedEventArgs.cs index c63d34e6..a5e87735 100644 --- a/src/SMAPI/Events/ButtonsChangedEventArgs.cs +++ b/src/SMAPI/Events/ButtonsChangedEventArgs.cs @@ -1,5 +1,3 @@ -#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 bc8ac0c0..4b4c4210 100644 --- a/src/SMAPI/Events/ChestInventoryChangedEventArgs.cs +++ b/src/SMAPI/Events/ChestInventoryChangedEventArgs.cs @@ -1,5 +1,3 @@ -#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 f3e7513b..43ff90ce 100644 --- a/src/SMAPI/Events/CursorMovedEventArgs.cs +++ b/src/SMAPI/Events/CursorMovedEventArgs.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/DebrisListChangedEventArgs.cs b/src/SMAPI/Events/DebrisListChangedEventArgs.cs index 56b1f30a..61b7590a 100644 --- a/src/SMAPI/Events/DebrisListChangedEventArgs.cs +++ b/src/SMAPI/Events/DebrisListChangedEventArgs.cs @@ -1,5 +1,3 @@ -#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 cda1b6cc..683f4620 100644 --- a/src/SMAPI/Events/FurnitureListChangedEventArgs.cs +++ b/src/SMAPI/Events/FurnitureListChangedEventArgs.cs @@ -1,5 +1,3 @@ -#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 109f9753..d537db70 100644 --- a/src/SMAPI/Events/IContentEvents.cs +++ b/src/SMAPI/Events/IContentEvents.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/IDisplayEvents.cs b/src/SMAPI/Events/IDisplayEvents.cs index b8b89120..dbf8d90f 100644 --- a/src/SMAPI/Events/IDisplayEvents.cs +++ b/src/SMAPI/Events/IDisplayEvents.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewValley; diff --git a/src/SMAPI/Events/IGameLoopEvents.cs b/src/SMAPI/Events/IGameLoopEvents.cs index 52bac3f8..6855737b 100644 --- a/src/SMAPI/Events/IGameLoopEvents.cs +++ b/src/SMAPI/Events/IGameLoopEvents.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/IInputEvents.cs b/src/SMAPI/Events/IInputEvents.cs index 01ceb224..081c40c0 100644 --- a/src/SMAPI/Events/IInputEvents.cs +++ b/src/SMAPI/Events/IInputEvents.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/IModEvents.cs b/src/SMAPI/Events/IModEvents.cs index a1aacbce..2603961b 100644 --- a/src/SMAPI/Events/IModEvents.cs +++ b/src/SMAPI/Events/IModEvents.cs @@ -1,5 +1,3 @@ -#nullable disable - namespace StardewModdingAPI.Events { /// Manages access to events raised by SMAPI. diff --git a/src/SMAPI/Events/IMultiplayerEvents.cs b/src/SMAPI/Events/IMultiplayerEvents.cs index c50eaf04..af9b5f17 100644 --- a/src/SMAPI/Events/IMultiplayerEvents.cs +++ b/src/SMAPI/Events/IMultiplayerEvents.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/IPlayerEvents.cs b/src/SMAPI/Events/IPlayerEvents.cs index 9d18bfad..81e17b1a 100644 --- a/src/SMAPI/Events/IPlayerEvents.cs +++ b/src/SMAPI/Events/IPlayerEvents.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/ISpecialisedEvents.cs b/src/SMAPI/Events/ISpecialisedEvents.cs index 0ec5bf54..bf70956d 100644 --- a/src/SMAPI/Events/ISpecialisedEvents.cs +++ b/src/SMAPI/Events/ISpecialisedEvents.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/IWorldEvents.cs b/src/SMAPI/Events/IWorldEvents.cs index 785dfa8f..c023e1f0 100644 --- a/src/SMAPI/Events/IWorldEvents.cs +++ b/src/SMAPI/Events/IWorldEvents.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/InventoryChangedEventArgs.cs b/src/SMAPI/Events/InventoryChangedEventArgs.cs index 58c0ff8f..40cd4128 100644 --- a/src/SMAPI/Events/InventoryChangedEventArgs.cs +++ b/src/SMAPI/Events/InventoryChangedEventArgs.cs @@ -1,5 +1,3 @@ -#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 5d0986aa..35369be2 100644 --- a/src/SMAPI/Events/ItemStackSizeChange.cs +++ b/src/SMAPI/Events/ItemStackSizeChange.cs @@ -1,5 +1,3 @@ -#nullable disable - using StardewValley; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs b/src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs index aedb0e46..59d79f0f 100644 --- a/src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs +++ b/src/SMAPI/Events/LargeTerrainFeatureListChangedEventArgs.cs @@ -1,5 +1,3 @@ -#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 3beb9fd5..c7303603 100644 --- a/src/SMAPI/Events/LevelChangedEventArgs.cs +++ b/src/SMAPI/Events/LevelChangedEventArgs.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Enums; using StardewValley; diff --git a/src/SMAPI/Events/LocaleChangedEventArgs.cs b/src/SMAPI/Events/LocaleChangedEventArgs.cs index 015e7ec8..09d3f6e5 100644 --- a/src/SMAPI/Events/LocaleChangedEventArgs.cs +++ b/src/SMAPI/Events/LocaleChangedEventArgs.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using LanguageCode = StardewValley.LocalizedContentManager.LanguageCode; diff --git a/src/SMAPI/Events/LocationListChangedEventArgs.cs b/src/SMAPI/Events/LocationListChangedEventArgs.cs index 055463dd..1ebb3e2d 100644 --- a/src/SMAPI/Events/LocationListChangedEventArgs.cs +++ b/src/SMAPI/Events/LocationListChangedEventArgs.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI/Events/NpcListChangedEventArgs.cs b/src/SMAPI/Events/NpcListChangedEventArgs.cs index fb6dc1c5..3a37f1e7 100644 --- a/src/SMAPI/Events/NpcListChangedEventArgs.cs +++ b/src/SMAPI/Events/NpcListChangedEventArgs.cs @@ -1,5 +1,3 @@ -#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 b1a636aa..b21d2867 100644 --- a/src/SMAPI/Events/ObjectListChangedEventArgs.cs +++ b/src/SMAPI/Events/ObjectListChangedEventArgs.cs @@ -1,5 +1,3 @@ -#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 3d11a3b5..bfaa2bd3 100644 --- a/src/SMAPI/Events/PeerConnectedEventArgs.cs +++ b/src/SMAPI/Events/PeerConnectedEventArgs.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/PeerContextReceivedEventArgs.cs b/src/SMAPI/Events/PeerContextReceivedEventArgs.cs index 35a4b20d..151a295c 100644 --- a/src/SMAPI/Events/PeerContextReceivedEventArgs.cs +++ b/src/SMAPI/Events/PeerContextReceivedEventArgs.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/PeerDisconnectedEventArgs.cs b/src/SMAPI/Events/PeerDisconnectedEventArgs.cs index 0675b8fe..8517988a 100644 --- a/src/SMAPI/Events/PeerDisconnectedEventArgs.cs +++ b/src/SMAPI/Events/PeerDisconnectedEventArgs.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Events diff --git a/src/SMAPI/Events/RenderedActiveMenuEventArgs.cs b/src/SMAPI/Events/RenderedActiveMenuEventArgs.cs index 3da0b4b4..efd4163b 100644 --- a/src/SMAPI/Events/RenderedActiveMenuEventArgs.cs +++ b/src/SMAPI/Events/RenderedActiveMenuEventArgs.cs @@ -1,5 +1,3 @@ -#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 e8beaaac..d6341b19 100644 --- a/src/SMAPI/Events/RenderedEventArgs.cs +++ b/src/SMAPI/Events/RenderedEventArgs.cs @@ -1,5 +1,3 @@ -#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 b25ecd4c..46e89013 100644 --- a/src/SMAPI/Events/RenderedHudEventArgs.cs +++ b/src/SMAPI/Events/RenderedHudEventArgs.cs @@ -1,5 +1,3 @@ -#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 a99d6ab3..56145381 100644 --- a/src/SMAPI/Events/RenderedWorldEventArgs.cs +++ b/src/SMAPI/Events/RenderedWorldEventArgs.cs @@ -1,5 +1,3 @@ -#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 3e3f3258..103f56df 100644 --- a/src/SMAPI/Events/RenderingActiveMenuEventArgs.cs +++ b/src/SMAPI/Events/RenderingActiveMenuEventArgs.cs @@ -1,5 +1,3 @@ -#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 8f6b3557..5acbef09 100644 --- a/src/SMAPI/Events/RenderingEventArgs.cs +++ b/src/SMAPI/Events/RenderingEventArgs.cs @@ -1,5 +1,3 @@ -#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 87269b90..84c96ecd 100644 --- a/src/SMAPI/Events/RenderingHudEventArgs.cs +++ b/src/SMAPI/Events/RenderingHudEventArgs.cs @@ -1,5 +1,3 @@ -#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 2fc9964f..d0d44789 100644 --- a/src/SMAPI/Events/RenderingWorldEventArgs.cs +++ b/src/SMAPI/Events/RenderingWorldEventArgs.cs @@ -1,5 +1,3 @@ -#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 77a73102..cdf1e6dc 100644 --- a/src/SMAPI/Events/TerrainFeatureListChangedEventArgs.cs +++ b/src/SMAPI/Events/TerrainFeatureListChangedEventArgs.cs @@ -1,5 +1,3 @@ -#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 92a8ea77..9afe4a4e 100644 --- a/src/SMAPI/Events/WarpedEventArgs.cs +++ b/src/SMAPI/Events/WarpedEventArgs.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewValley; diff --git a/src/SMAPI/Framework/Commands/IInternalCommand.cs b/src/SMAPI/Framework/Commands/IInternalCommand.cs index 32e3e9f1..abf105b6 100644 --- a/src/SMAPI/Framework/Commands/IInternalCommand.cs +++ b/src/SMAPI/Framework/Commands/IInternalCommand.cs @@ -1,5 +1,3 @@ -#nullable disable - namespace StardewModdingAPI.Framework.Commands { /// A core SMAPI console command. diff --git a/src/SMAPI/Framework/Commands/ReloadI18nCommand.cs b/src/SMAPI/Framework/Commands/ReloadI18nCommand.cs index 2043b35e..12328bb6 100644 --- a/src/SMAPI/Framework/Commands/ReloadI18nCommand.cs +++ b/src/SMAPI/Framework/Commands/ReloadI18nCommand.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Framework.Commands diff --git a/src/SMAPI/Framework/Content/AssetOperationGroup.cs b/src/SMAPI/Framework/Content/AssetOperationGroup.cs index e3c3f92c..a2fcb722 100644 --- a/src/SMAPI/Framework/Content/AssetOperationGroup.cs +++ b/src/SMAPI/Framework/Content/AssetOperationGroup.cs @@ -1,5 +1,3 @@ -#nullable disable - namespace StardewModdingAPI.Framework.Content { /// A set of operations to apply to an asset for a given or implementation. diff --git a/src/SMAPI/Framework/Content/TilesheetReference.cs b/src/SMAPI/Framework/Content/TilesheetReference.cs index cdc4bc62..0339b802 100644 --- a/src/SMAPI/Framework/Content/TilesheetReference.cs +++ b/src/SMAPI/Framework/Content/TilesheetReference.cs @@ -1,5 +1,3 @@ -#nullable disable - using xTile.Dimensions; namespace StardewModdingAPI.Framework.Content diff --git a/src/SMAPI/Framework/DeprecationWarning.cs b/src/SMAPI/Framework/DeprecationWarning.cs index f155358b..5201b06c 100644 --- a/src/SMAPI/Framework/DeprecationWarning.cs +++ b/src/SMAPI/Framework/DeprecationWarning.cs @@ -1,5 +1,3 @@ -#nullable disable - namespace StardewModdingAPI.Framework { /// A deprecation warning for a mod. diff --git a/src/SMAPI/Framework/Events/EventManager.cs b/src/SMAPI/Framework/Events/EventManager.cs index c977e73d..41540047 100644 --- a/src/SMAPI/Framework/Events/EventManager.cs +++ b/src/SMAPI/Framework/Events/EventManager.cs @@ -1,5 +1,3 @@ -#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 57277576..e4e3ca08 100644 --- a/src/SMAPI/Framework/Events/IManagedEvent.cs +++ b/src/SMAPI/Framework/Events/IManagedEvent.cs @@ -1,5 +1,3 @@ -#nullable disable - namespace StardewModdingAPI.Framework.Events { /// Metadata for an event raised by SMAPI. diff --git a/src/SMAPI/Framework/Events/ModContentEvents.cs b/src/SMAPI/Framework/Events/ModContentEvents.cs index f198b793..beb96031 100644 --- a/src/SMAPI/Framework/Events/ModContentEvents.cs +++ b/src/SMAPI/Framework/Events/ModContentEvents.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Framework/Events/ModDisplayEvents.cs b/src/SMAPI/Framework/Events/ModDisplayEvents.cs index b2110cce..48f55324 100644 --- a/src/SMAPI/Framework/Events/ModDisplayEvents.cs +++ b/src/SMAPI/Framework/Events/ModDisplayEvents.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Framework/Events/ModEvents.cs b/src/SMAPI/Framework/Events/ModEvents.cs index e8f8885d..1fb3482c 100644 --- a/src/SMAPI/Framework/Events/ModEvents.cs +++ b/src/SMAPI/Framework/Events/ModEvents.cs @@ -1,5 +1,3 @@ -#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 295caa0d..77708fc1 100644 --- a/src/SMAPI/Framework/Events/ModEventsBase.cs +++ b/src/SMAPI/Framework/Events/ModEventsBase.cs @@ -1,5 +1,3 @@ -#nullable disable - namespace StardewModdingAPI.Framework.Events { /// An internal base class for event API classes. diff --git a/src/SMAPI/Framework/Events/ModGameLoopEvents.cs b/src/SMAPI/Framework/Events/ModGameLoopEvents.cs index 51803daf..5f0db369 100644 --- a/src/SMAPI/Framework/Events/ModGameLoopEvents.cs +++ b/src/SMAPI/Framework/Events/ModGameLoopEvents.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Framework/Events/ModInputEvents.cs b/src/SMAPI/Framework/Events/ModInputEvents.cs index 6af79c59..40edf806 100644 --- a/src/SMAPI/Framework/Events/ModInputEvents.cs +++ b/src/SMAPI/Framework/Events/ModInputEvents.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Framework/Events/ModMultiplayerEvents.cs b/src/SMAPI/Framework/Events/ModMultiplayerEvents.cs index 7d3ce510..b90f64fa 100644 --- a/src/SMAPI/Framework/Events/ModMultiplayerEvents.cs +++ b/src/SMAPI/Framework/Events/ModMultiplayerEvents.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Framework/Events/ModPlayerEvents.cs b/src/SMAPI/Framework/Events/ModPlayerEvents.cs index dac8f05b..b2d89e9a 100644 --- a/src/SMAPI/Framework/Events/ModPlayerEvents.cs +++ b/src/SMAPI/Framework/Events/ModPlayerEvents.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs b/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs index 4b438034..7980208b 100644 --- a/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs +++ b/src/SMAPI/Framework/Events/ModSpecialisedEvents.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Framework/Events/ModWorldEvents.cs b/src/SMAPI/Framework/Events/ModWorldEvents.cs index 614945c7..a7b7d799 100644 --- a/src/SMAPI/Framework/Events/ModWorldEvents.cs +++ b/src/SMAPI/Framework/Events/ModWorldEvents.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Framework/Exceptions/SAssemblyLoadFailedException.cs b/src/SMAPI/Framework/Exceptions/SAssemblyLoadFailedException.cs index 4e03a687..ec9279f1 100644 --- a/src/SMAPI/Framework/Exceptions/SAssemblyLoadFailedException.cs +++ b/src/SMAPI/Framework/Exceptions/SAssemblyLoadFailedException.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Framework.Exceptions diff --git a/src/SMAPI/Framework/Input/IInputStateBuilder.cs b/src/SMAPI/Framework/Input/IInputStateBuilder.cs index 3fb62686..28d62439 100644 --- a/src/SMAPI/Framework/Input/IInputStateBuilder.cs +++ b/src/SMAPI/Framework/Input/IInputStateBuilder.cs @@ -1,5 +1,3 @@ -#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 81ca0ebb..f66fbd07 100644 --- a/src/SMAPI/Framework/Input/KeyboardStateBuilder.cs +++ b/src/SMAPI/Framework/Input/KeyboardStateBuilder.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework.Input; @@ -29,7 +27,7 @@ namespace StardewModdingAPI.Framework.Input this.State = state; this.PressedButtons.Clear(); - foreach (var button in state.GetPressedKeys()) + foreach (Keys button in state.GetPressedKeys()) this.PressedButtons.Add(button); } diff --git a/src/SMAPI/Framework/Input/MouseStateBuilder.cs b/src/SMAPI/Framework/Input/MouseStateBuilder.cs index 85b38d32..c2a0891b 100644 --- a/src/SMAPI/Framework/Input/MouseStateBuilder.cs +++ b/src/SMAPI/Framework/Input/MouseStateBuilder.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using Microsoft.Xna.Framework.Input; diff --git a/src/SMAPI/Framework/ModHelpers/BaseHelper.cs b/src/SMAPI/Framework/ModHelpers/BaseHelper.cs index 1cd1a6b3..5a3d4bed 100644 --- a/src/SMAPI/Framework/ModHelpers/BaseHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/BaseHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - namespace StardewModdingAPI.Framework.ModHelpers { /// The common base class for mod helpers. diff --git a/src/SMAPI/Framework/ModHelpers/ContentPackHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentPackHelper.cs index 336214e2..d39abc7d 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentPackHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentPackHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.IO; diff --git a/src/SMAPI/Framework/ModHelpers/InputHelper.cs b/src/SMAPI/Framework/ModHelpers/InputHelper.cs index 29f80d87..88caf4c3 100644 --- a/src/SMAPI/Framework/ModHelpers/InputHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/InputHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Framework.Input; using StardewModdingAPI.Utilities; diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index 3cfe52bf..e1529a75 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.IO; using StardewModdingAPI.Events; diff --git a/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs b/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs index 37345a76..869664fe 100644 --- a/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using StardewValley; diff --git a/src/SMAPI/Framework/ModLinked.cs b/src/SMAPI/Framework/ModLinked.cs index 5a3e38ca..8cfe6f5f 100644 --- a/src/SMAPI/Framework/ModLinked.cs +++ b/src/SMAPI/Framework/ModLinked.cs @@ -1,5 +1,3 @@ -#nullable disable - namespace StardewModdingAPI.Framework { /// A generic tuple which links something to a mod. diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoadStatus.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoadStatus.cs index d2d5d83b..11be19fc 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoadStatus.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoadStatus.cs @@ -1,5 +1,3 @@ -#nullable disable - namespace StardewModdingAPI.Framework.ModLoading { /// Indicates the result of an assembly load. diff --git a/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs b/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs index 126504e3..d41732f8 100644 --- a/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs +++ b/src/SMAPI/Framework/ModLoading/IInstructionHandler.cs @@ -1,5 +1,3 @@ -#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 29406f2a..1f9add30 100644 --- a/src/SMAPI/Framework/ModLoading/IncompatibleInstructionException.cs +++ b/src/SMAPI/Framework/ModLoading/IncompatibleInstructionException.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Framework.ModLoading diff --git a/src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs b/src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs index 0898f095..d4366294 100644 --- a/src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs +++ b/src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs index 20a30f8f..67569424 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/SpriteBatchFacade.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Diagnostics.CodeAnalysis; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; @@ -20,7 +18,8 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades ** Public methods *********/ /// Construct an instance. - public SpriteBatchFacade(GraphicsDevice graphicsDevice) : base(graphicsDevice) { } + public SpriteBatchFacade(GraphicsDevice graphicsDevice) + : base(graphicsDevice) { } /**** diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/ArchitectureAssemblyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/ArchitectureAssemblyRewriter.cs index 4985d72a..cc830216 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/ArchitectureAssemblyRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/ArchitectureAssemblyRewriter.cs @@ -1,5 +1,3 @@ -#nullable disable - 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 55b7e0c8..2171895d 100644 --- a/src/SMAPI/Framework/ModLoading/Symbols/SymbolReader.cs +++ b/src/SMAPI/Framework/ModLoading/Symbols/SymbolReader.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.IO; using Mono.Cecil; using Mono.Cecil.Cil; diff --git a/src/SMAPI/Framework/ModLoading/Symbols/SymbolWriterProvider.cs b/src/SMAPI/Framework/ModLoading/Symbols/SymbolWriterProvider.cs index c2ac4cd6..8f7e05d1 100644 --- a/src/SMAPI/Framework/ModLoading/Symbols/SymbolWriterProvider.cs +++ b/src/SMAPI/Framework/ModLoading/Symbols/SymbolWriterProvider.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.IO; using Mono.Cecil; using Mono.Cecil.Cil; diff --git a/src/SMAPI/Framework/Monitor.cs b/src/SMAPI/Framework/Monitor.cs index de145d1d..6b53daff 100644 --- a/src/SMAPI/Framework/Monitor.cs +++ b/src/SMAPI/Framework/Monitor.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI/Framework/Networking/SGalaxyNetClient.cs b/src/SMAPI/Framework/Networking/SGalaxyNetClient.cs index 8e19b4a7..01095c66 100644 --- a/src/SMAPI/Framework/Networking/SGalaxyNetClient.cs +++ b/src/SMAPI/Framework/Networking/SGalaxyNetClient.cs @@ -1,5 +1,3 @@ -#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 07a004a2..71e11576 100644 --- a/src/SMAPI/Framework/Networking/SGalaxyNetServer.cs +++ b/src/SMAPI/Framework/Networking/SGalaxyNetServer.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Diagnostics.CodeAnalysis; using System.IO; diff --git a/src/SMAPI/Framework/Networking/SLidgrenClient.cs b/src/SMAPI/Framework/Networking/SLidgrenClient.cs index ecf18cbd..39876744 100644 --- a/src/SMAPI/Framework/Networking/SLidgrenClient.cs +++ b/src/SMAPI/Framework/Networking/SLidgrenClient.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewValley.Network; diff --git a/src/SMAPI/Framework/Networking/SLidgrenServer.cs b/src/SMAPI/Framework/Networking/SLidgrenServer.cs index c0b247c8..ff871e64 100644 --- a/src/SMAPI/Framework/Networking/SLidgrenServer.cs +++ b/src/SMAPI/Framework/Networking/SLidgrenServer.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Diagnostics.CodeAnalysis; using System.IO; diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs index 4c49e219..40adde8e 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Reflection; using System.Reflection.Emit; using Nanoray.Pintail; diff --git a/src/SMAPI/Framework/SChatBox.cs b/src/SMAPI/Framework/SChatBox.cs index d6286c12..e000d1cd 100644 --- a/src/SMAPI/Framework/SChatBox.cs +++ b/src/SMAPI/Framework/SChatBox.cs @@ -1,5 +1,3 @@ -#nullable disable - using StardewValley; using StardewValley.Menus; diff --git a/src/SMAPI/Framework/SGameRunner.cs b/src/SMAPI/Framework/SGameRunner.cs index dae314af..213fe561 100644 --- a/src/SMAPI/Framework/SGameRunner.cs +++ b/src/SMAPI/Framework/SGameRunner.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -150,7 +148,7 @@ namespace StardewModdingAPI.Framework /// Update metadata when a split screen is added or removed. private void UpdateForSplitScreenChanges() { - HashSet oldScreenIds = new HashSet(Context.ActiveScreenIds); + HashSet oldScreenIds = new(Context.ActiveScreenIds); // track active screens Context.ActiveScreenIds.Clear(); diff --git a/src/SMAPI/Framework/Singleton.cs b/src/SMAPI/Framework/Singleton.cs index da16c48e..1bf318c4 100644 --- a/src/SMAPI/Framework/Singleton.cs +++ b/src/SMAPI/Framework/Singleton.cs @@ -1,5 +1,3 @@ -#nullable disable - namespace StardewModdingAPI.Framework { /// Provides singleton instances of a given type. diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/BaseDisposableWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/BaseDisposableWatcher.cs index 03bf84d9..60006c51 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/BaseDisposableWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/BaseDisposableWatcher.cs @@ -1,5 +1,3 @@ -#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 52e1dbad..256370ce 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using System.Linq; diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ImmutableCollectionWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ImmutableCollectionWatcher.cs index 94ce0c8e..84340fbf 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/ImmutableCollectionWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ImmutableCollectionWatcher.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs index e662c433..676c9fb4 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetCollectionWatcher.cs @@ -1,5 +1,3 @@ -#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 a97e754c..0b4d3030 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetListWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetListWatcher.cs @@ -1,5 +1,3 @@ -#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 26641750..48d5d681 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetValueWatcher.cs @@ -1,5 +1,3 @@ -#nullable disable - using Netcode; namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers diff --git a/src/SMAPI/Framework/StateTracking/ICollectionWatcher.cs b/src/SMAPI/Framework/StateTracking/ICollectionWatcher.cs index 74c9313b..7a7759e3 100644 --- a/src/SMAPI/Framework/StateTracking/ICollectionWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/ICollectionWatcher.cs @@ -1,5 +1,3 @@ -#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 81fb7460..691ed377 100644 --- a/src/SMAPI/Framework/StateTracking/IDictionaryWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/IDictionaryWatcher.cs @@ -1,5 +1,3 @@ -#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 7d46053c..4afca972 100644 --- a/src/SMAPI/Framework/StateTracking/IValueWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/IValueWatcher.cs @@ -1,5 +1,3 @@ -#nullable disable - namespace StardewModdingAPI.Framework.StateTracking { /// A watcher which tracks changes to a value. diff --git a/src/SMAPI/Framework/StateTracking/IWatcher.cs b/src/SMAPI/Framework/StateTracking/IWatcher.cs index 3603b6f8..8c7fa51c 100644 --- a/src/SMAPI/Framework/StateTracking/IWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/IWatcher.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Framework.StateTracking diff --git a/src/SMAPI/Framework/Utilities/ContextHash.cs b/src/SMAPI/Framework/Utilities/ContextHash.cs index 46b9099e..6c0fdc90 100644 --- a/src/SMAPI/Framework/Utilities/ContextHash.cs +++ b/src/SMAPI/Framework/Utilities/ContextHash.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; diff --git a/src/SMAPI/GamePlatform.cs b/src/SMAPI/GamePlatform.cs index 8013faa9..cce5ed8d 100644 --- a/src/SMAPI/GamePlatform.cs +++ b/src/SMAPI/GamePlatform.cs @@ -1,5 +1,3 @@ -#nullable disable - using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingAPI diff --git a/src/SMAPI/IAssetDataForDictionary.cs b/src/SMAPI/IAssetDataForDictionary.cs index 82ba25cb..1136316f 100644 --- a/src/SMAPI/IAssetDataForDictionary.cs +++ b/src/SMAPI/IAssetDataForDictionary.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; namespace StardewModdingAPI diff --git a/src/SMAPI/IAssetDataForImage.cs b/src/SMAPI/IAssetDataForImage.cs index 1416592e..6f8a4719 100644 --- a/src/SMAPI/IAssetDataForImage.cs +++ b/src/SMAPI/IAssetDataForImage.cs @@ -1,5 +1,3 @@ -#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 0b637baf..19bdf3b2 100644 --- a/src/SMAPI/IAssetDataForMap.cs +++ b/src/SMAPI/IAssetDataForMap.cs @@ -1,5 +1,3 @@ -#nullable disable - using Microsoft.Xna.Framework; using xTile; diff --git a/src/SMAPI/IAssetEditor.cs b/src/SMAPI/IAssetEditor.cs index f3d91bd0..9f22ed83 100644 --- a/src/SMAPI/IAssetEditor.cs +++ b/src/SMAPI/IAssetEditor.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/IAssetLoader.cs b/src/SMAPI/IAssetLoader.cs index 0d52a481..96b98793 100644 --- a/src/SMAPI/IAssetLoader.cs +++ b/src/SMAPI/IAssetLoader.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/ICommandHelper.cs b/src/SMAPI/ICommandHelper.cs index a0c524d6..9f1c345c 100644 --- a/src/SMAPI/ICommandHelper.cs +++ b/src/SMAPI/ICommandHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI diff --git a/src/SMAPI/IContentPackHelper.cs b/src/SMAPI/IContentPackHelper.cs index 5464df22..c48a4f86 100644 --- a/src/SMAPI/IContentPackHelper.cs +++ b/src/SMAPI/IContentPackHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; namespace StardewModdingAPI diff --git a/src/SMAPI/ICursorPosition.cs b/src/SMAPI/ICursorPosition.cs index da6cbb62..99c1b84d 100644 --- a/src/SMAPI/ICursorPosition.cs +++ b/src/SMAPI/ICursorPosition.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using Microsoft.Xna.Framework; using StardewValley; diff --git a/src/SMAPI/IInputHelper.cs b/src/SMAPI/IInputHelper.cs index b7ed0838..2b907b0d 100644 --- a/src/SMAPI/IInputHelper.cs +++ b/src/SMAPI/IInputHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using StardewModdingAPI.Utilities; namespace StardewModdingAPI diff --git a/src/SMAPI/IModHelper.cs b/src/SMAPI/IModHelper.cs index 5e4246aa..15e4ed8d 100644 --- a/src/SMAPI/IModHelper.cs +++ b/src/SMAPI/IModHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Events; diff --git a/src/SMAPI/IModInfo.cs b/src/SMAPI/IModInfo.cs index 2788e4fc..3c85d454 100644 --- a/src/SMAPI/IModInfo.cs +++ b/src/SMAPI/IModInfo.cs @@ -1,5 +1,3 @@ -#nullable disable - namespace StardewModdingAPI { /// Metadata for a loaded mod. diff --git a/src/SMAPI/IModLinked.cs b/src/SMAPI/IModLinked.cs index cf08c9c5..172ee30c 100644 --- a/src/SMAPI/IModLinked.cs +++ b/src/SMAPI/IModLinked.cs @@ -1,5 +1,3 @@ -#nullable disable - namespace StardewModdingAPI { /// An instance linked to a mod. diff --git a/src/SMAPI/IMonitor.cs b/src/SMAPI/IMonitor.cs index 535f56e3..c400a211 100644 --- a/src/SMAPI/IMonitor.cs +++ b/src/SMAPI/IMonitor.cs @@ -1,5 +1,3 @@ -#nullable disable - namespace StardewModdingAPI { /// Encapsulates monitoring and logging for a given module. diff --git a/src/SMAPI/IMultiplayerPeerMod.cs b/src/SMAPI/IMultiplayerPeerMod.cs index 81978bef..005408b1 100644 --- a/src/SMAPI/IMultiplayerPeerMod.cs +++ b/src/SMAPI/IMultiplayerPeerMod.cs @@ -1,5 +1,3 @@ -#nullable disable - namespace StardewModdingAPI { /// Metadata about a mod installed by a connected player. diff --git a/src/SMAPI/ITranslationHelper.cs b/src/SMAPI/ITranslationHelper.cs index 3c297731..32199910 100644 --- a/src/SMAPI/ITranslationHelper.cs +++ b/src/SMAPI/ITranslationHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using StardewValley; @@ -11,7 +9,7 @@ namespace StardewModdingAPI /********* ** Accessors *********/ - /// The current locale. + /// The current locale code like fr-FR, or an empty string for English. string Locale { get; } /// The game's current language code. -- cgit From f39da383a17b368e92fd243cf155b27ba42671f3 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 13 Apr 2022 20:24:14 -0400 Subject: enable nullable annotations in SMAPI where no logic changes are needed (#837) --- src/SMAPI.Tests/Core/AssetNameTests.cs | 2 +- src/SMAPI.Tests/Core/TranslationTests.cs | 4 +- .../Framework/ModData/ModDataRecord.cs | 2 +- src/SMAPI/Constants.cs | 26 +++++----- src/SMAPI/Events/AssetRequestedEventArgs.cs | 14 +++--- src/SMAPI/Events/MenuChangedEventArgs.cs | 16 +++---- src/SMAPI/Framework/Command.cs | 6 +-- .../Framework/Commands/HarmonySummaryCommand.cs | 20 ++++---- src/SMAPI/Framework/Content/AssetData.cs | 7 ++- .../Framework/Content/AssetDataForDictionary.cs | 4 +- src/SMAPI/Framework/Content/AssetDataForImage.cs | 4 +- src/SMAPI/Framework/Content/AssetDataForObject.cs | 4 +- src/SMAPI/Framework/Content/AssetEditOperation.cs | 6 +-- src/SMAPI/Framework/Content/AssetInfo.cs | 12 ++--- .../Framework/Content/AssetInterceptorChange.cs | 7 ++- src/SMAPI/Framework/Content/AssetLoadOperation.cs | 6 +-- src/SMAPI/Framework/ContentCoordinator.cs | 35 +++++++------- .../ContentManagers/GameContentManager.cs | 34 +++++++------ .../GameContentManagerForAssetPropagation.cs | 4 +- .../Framework/ContentManagers/IContentManager.cs | 13 ++--- src/SMAPI/Framework/ContentPack.cs | 9 ++-- src/SMAPI/Framework/CursorPosition.cs | 4 +- src/SMAPI/Framework/Events/ManagedEvent.cs | 8 ++-- src/SMAPI/Framework/Events/ManagedEventHandler.cs | 4 +- .../Framework/Exceptions/SContentLoadException.cs | 4 +- src/SMAPI/Framework/GameVersion.cs | 10 ++-- src/SMAPI/Framework/IModMetadata.cs | 26 +++++----- src/SMAPI/Framework/Logging/LogFileManager.cs | 4 +- src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 9 ++-- src/SMAPI/Framework/ModHelpers/DataHelper.cs | 28 ++++++----- .../Framework/ModHelpers/GameContentHelper.cs | 8 ++-- src/SMAPI/Framework/ModHelpers/ModContentHelper.cs | 6 +-- .../Framework/ModHelpers/ModRegistryHelper.cs | 13 +++-- .../Framework/ModHelpers/MultiplayerHelper.cs | 8 ++-- src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs | 26 +++++----- .../ModLoading/AssemblyDefinitionResolver.cs | 8 ++-- .../Framework/ModLoading/Finders/EventFinder.cs | 4 +- .../Framework/ModLoading/Finders/FieldFinder.cs | 4 +- .../Framework/ModLoading/Finders/MethodFinder.cs | 4 +- .../Framework/ModLoading/Finders/PropertyFinder.cs | 4 +- .../ReferenceToMemberWithUnexpectedTypeFinder.cs | 15 +++--- .../Finders/ReferenceToMissingMemberFinder.cs | 13 +++-- .../ModLoading/Finders/TypeAssemblyFinder.cs | 6 +-- .../Framework/ModLoading/Finders/TypeFinder.cs | 8 ++-- .../ModLoading/Framework/BaseInstructionHandler.cs | 4 +- .../ModLoading/Framework/RecursiveRewriter.cs | 25 +++++----- .../ModLoading/Framework/RewriteHelper.cs | 10 ++-- .../ModLoading/InvalidModStateException.cs | 4 +- src/SMAPI/Framework/ModLoading/ModMetadata.cs | 38 ++++++++------- .../ModLoading/RewriteFacades/AccessToolsFacade.cs | 6 +-- .../RewriteFacades/HarmonyInstanceFacade.cs | 9 ++-- .../RewriteFacades/HarmonyMethodFacade.cs | 6 +-- .../ModLoading/Rewriters/HarmonyRewriter.cs | 16 +++---- .../ModLoading/Rewriters/HeuristicFieldRewriter.cs | 19 ++++---- .../Rewriters/HeuristicMethodRewriter.cs | 15 +++--- .../ModLoading/Rewriters/MethodParentRewriter.cs | 13 +++-- .../ModLoading/Rewriters/TypeReferenceRewriter.cs | 6 +-- .../ModLoading/Symbols/SymbolReaderProvider.cs | 6 +-- .../Framework/ModLoading/TypeReferenceComparer.cs | 14 +++--- src/SMAPI/Framework/ModRegistry.cs | 16 +++---- src/SMAPI/Framework/Reflection/ReflectedField.cs | 16 +++---- src/SMAPI/Framework/Reflection/ReflectedMethod.cs | 18 ++++--- .../Framework/Reflection/ReflectedProperty.cs | 18 ++++--- src/SMAPI/Framework/Rendering/SDisplayDevice.cs | 8 ++-- src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs | 4 +- src/SMAPI/Framework/SGame.cs | 27 +++++------ src/SMAPI/Framework/SModHooks.cs | 4 +- src/SMAPI/Framework/SnapshotDiff.cs | 6 +-- src/SMAPI/Framework/SnapshotItemListDiff.cs | 5 +- src/SMAPI/Framework/SnapshotListDiff.cs | 4 +- src/SMAPI/Framework/StateTracking/ChestTracker.cs | 5 +- .../StateTracking/Comparers/EquatableComparer.cs | 4 +- .../Comparers/GenericEqualsComparer.cs | 4 +- .../Comparers/ObjectReferenceComparer.cs | 4 +- .../FieldWatchers/NetDictionaryWatcher.cs | 3 +- .../FieldWatchers/ObservableCollectionWatcher.cs | 8 ++-- .../StateTracking/FieldWatchers/WatcherFactory.cs | 15 +++--- .../Framework/StateTracking/LocationTracker.cs | 14 +++--- .../StateTracking/Snapshots/LocationSnapshot.cs | 4 +- .../StateTracking/Snapshots/WatcherSnapshot.cs | 6 +-- .../Snapshots/WorldLocationsSnapshot.cs | 4 +- .../StateTracking/WorldLocationsTracker.cs | 23 ++++----- src/SMAPI/Framework/Translator.cs | 20 ++++---- .../Framework/Utilities/TickCacheDictionary.cs | 5 +- src/SMAPI/Framework/WatcherCore.cs | 4 +- src/SMAPI/IAssetData.cs | 3 +- src/SMAPI/IAssetInfo.cs | 4 +- src/SMAPI/IAssetName.cs | 12 ++--- src/SMAPI/IContentHelper.cs | 14 +++--- src/SMAPI/IContentPack.cs | 11 +++-- src/SMAPI/IDataHelper.cs | 20 ++++---- src/SMAPI/IGameContentHelper.cs | 14 +++--- src/SMAPI/IMod.cs | 4 +- src/SMAPI/IModContentHelper.cs | 8 ++-- src/SMAPI/IModRegistry.cs | 9 ++-- src/SMAPI/IMultiplayerHelper.cs | 6 +-- src/SMAPI/IMultiplayerPeer.cs | 10 ++-- src/SMAPI/IReflectedField.cs | 6 +-- src/SMAPI/IReflectedMethod.cs | 6 +-- src/SMAPI/IReflectedProperty.cs | 6 +-- src/SMAPI/IReflectionHelper.cs | 30 ++++++++---- src/SMAPI/Metadata/InstructionMetadata.cs | 24 +++++----- src/SMAPI/Mod.cs | 12 ++--- src/SMAPI/Patches/Game1Patcher.cs | 6 +-- src/SMAPI/Patches/TitleMenuPatcher.cs | 4 +- src/SMAPI/Program.cs | 22 ++++----- src/SMAPI/Translation.cs | 30 ++++++------ src/SMAPI/Utilities/CaseInsensitivePathCache.cs | 4 +- src/SMAPI/Utilities/Keybind.cs | 5 +- src/SMAPI/Utilities/KeybindList.cs | 11 ++--- src/SMAPI/Utilities/SDate.cs | 55 ++++++++++------------ 111 files changed, 558 insertions(+), 677 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI.Tests/Core/AssetNameTests.cs b/src/SMAPI.Tests/Core/AssetNameTests.cs index 8018442c..a1712726 100644 --- a/src/SMAPI.Tests/Core/AssetNameTests.cs +++ b/src/SMAPI.Tests/Core/AssetNameTests.cs @@ -56,7 +56,7 @@ namespace SMAPI.Tests.Core public void Constructor_NullOrWhitespace(string? name) { // act - ArgumentException exception = Assert.Throws(() => _ = AssetName.Parse(name!, null))!; + ArgumentException exception = Assert.Throws(() => _ = AssetName.Parse(name!, _ => null))!; // assert exception.ParamName.Should().Be("rawName"); diff --git a/src/SMAPI.Tests/Core/TranslationTests.cs b/src/SMAPI.Tests/Core/TranslationTests.cs index a65bf772..ced1525a 100644 --- a/src/SMAPI.Tests/Core/TranslationTests.cs +++ b/src/SMAPI.Tests/Core/TranslationTests.cs @@ -134,9 +134,9 @@ namespace SMAPI.Tests.Core // assert if (translation.HasValue()) - Assert.AreEqual(text, (string)translation, "The translation returned an unexpected value given a valid input."); + Assert.AreEqual(text, (string?)translation, "The translation returned an unexpected value given a valid input."); else - Assert.AreEqual(this.GetPlaceholderText("key"), (string)translation, "The translation returned an unexpected value given a null or empty input."); + Assert.AreEqual(this.GetPlaceholderText("key"), (string?)translation, "The translation returned an unexpected value given a null or empty input."); } [Test(Description = "Assert that the translation returns the expected text given a use-placeholder setting.")] diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs index e9ece387..ab0e4377 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModDataRecord.cs @@ -80,7 +80,7 @@ namespace StardewModdingAPI.Toolkit.Framework.ModData /// Get a parsed representation of the which match a given manifest. /// The manifest to match. - public ModDataRecordVersionedFields GetVersionedFields(IManifest manifest) + public ModDataRecordVersionedFields GetVersionedFields(IManifest? manifest) { ModDataRecordVersionedFields parsed = new(this); foreach (ModDataField field in this.Fields.Where(field => field.IsMatch(manifest))) diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index 2d9ab666..fd2b813a 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.IO; @@ -33,7 +31,7 @@ namespace StardewModdingAPI ** Accessors *********/ /// The path to the game folder. - public static string GamePath { get; } = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + public static string GamePath { get; } = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!; /// The absolute path to the folder containing SMAPI's internal files. public static readonly string InternalFilesPath = Path.Combine(EarlyConstants.GamePath, "smapi-internal"); @@ -69,8 +67,8 @@ namespace StardewModdingAPI /// The minimum supported version of Stardew Valley. public static ISemanticVersion MinimumGameVersion { get; } = new GameVersion("1.5.6"); - /// The maximum supported version of Stardew Valley. - public static ISemanticVersion MaximumGameVersion { get; } = null; + /// The maximum supported version of Stardew Valley, if any. + public static ISemanticVersion? MaximumGameVersion { get; } = null; /// The target game platform. public static GamePlatform TargetPlatform { get; } = EarlyConstants.Platform; @@ -111,10 +109,10 @@ namespace StardewModdingAPI public static string SavesPath { get; } = Path.Combine(Constants.DataPath, "Saves"); /// The name of the current save folder (if save info is available, regardless of whether the save file exists yet). - public static string SaveFolderName => Constants.GetSaveFolderName(); + public static string? SaveFolderName => Constants.GetSaveFolderName(); /// The absolute path to the current save folder (if save info is available and the save file exists). - public static string CurrentSavePath => Constants.GetSaveFolderPathIfExists(); + public static string? CurrentSavePath => Constants.GetSaveFolderPathIfExists(); /**** ** Internal @@ -164,7 +162,7 @@ namespace StardewModdingAPI internal static string DefaultModsPath { get; } = Path.Combine(Constants.GamePath, "Mods"); /// The actual full path to search for mods. - internal static string ModsPath { get; set; } + internal static string ModsPath { get; set; } = null!; // initialized early during SMAPI startup /// The game's current semantic version. internal static ISemanticVersion GameVersion { get; } = new GameVersion(Game1.version); @@ -179,7 +177,7 @@ namespace StardewModdingAPI /// Get the SMAPI version to recommend for an older game version, if any. /// The game version to search. /// Returns the compatible SMAPI version, or null if none was found. - internal static ISemanticVersion GetCompatibleApiVersion(ISemanticVersion version) + internal static ISemanticVersion? GetCompatibleApiVersion(ISemanticVersion version) { // This covers all officially supported public game updates. It might seem like version // ranges would be better, but the given SMAPI versions may not be compatible with @@ -337,22 +335,22 @@ namespace StardewModdingAPI } /// Get the name of the save folder, if any. - private static string GetSaveFolderName() + private static string? GetSaveFolderName() { return Constants.GetSaveFolder()?.Name; } /// Get the absolute path to the current save folder, if any. - private static string GetSaveFolderPathIfExists() + private static string? GetSaveFolderPathIfExists() { - DirectoryInfo saveFolder = Constants.GetSaveFolder(); + DirectoryInfo? saveFolder = Constants.GetSaveFolder(); return saveFolder?.Exists == true ? saveFolder.FullName : null; } /// Get the current save folder, if any. - private static DirectoryInfo GetSaveFolder() + private static DirectoryInfo? GetSaveFolder() { // save not available if (Context.LoadStage == LoadStage.None) @@ -365,7 +363,7 @@ namespace StardewModdingAPI : Game1.uniqueIDForThisGame; // get best match (accounting for rare case where folder name isn't sanitized) - DirectoryInfo folder = null; + DirectoryInfo? folder = null; foreach (string saveName in new[] { rawSaveName, new string(rawSaveName.Where(char.IsLetterOrDigit).ToArray()) }) { try diff --git a/src/SMAPI/Events/AssetRequestedEventArgs.cs b/src/SMAPI/Events/AssetRequestedEventArgs.cs index 3c51c95d..3bcf83b9 100644 --- a/src/SMAPI/Events/AssetRequestedEventArgs.cs +++ b/src/SMAPI/Events/AssetRequestedEventArgs.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; @@ -19,7 +17,7 @@ namespace StardewModdingAPI.Events private readonly IModMetadata Mod; /// Get the mod metadata for a content pack, if it's a valid content pack for the mod. - private readonly Func GetOnBehalfOf; + private readonly Func GetOnBehalfOf; /********* @@ -51,7 +49,7 @@ namespace StardewModdingAPI.Events /// The requested data type. /// The with any locale codes stripped. /// Get the mod metadata for a content pack, if it's a valid content pack for the mod. - internal AssetRequestedEventArgs(IModMetadata mod, IAssetName name, IAssetName nameWithoutLocale, Type dataType, Func getOnBehalfOf) + internal AssetRequestedEventArgs(IModMetadata mod, IAssetName name, IAssetName nameWithoutLocale, Type dataType, Func getOnBehalfOf) { this.Mod = mod; this.Name = name; @@ -71,7 +69,7 @@ namespace StardewModdingAPI.Events /// Each asset can logically only have one initial instance. If multiple loads apply at the same time, SMAPI will use the parameter to decide what happens. If you're making changes to the existing asset instead of replacing it, you should use instead to avoid those limitations and improve mod compatibility. /// /// - public void LoadFrom(Func load, AssetLoadPriority priority, string onBehalfOf = null) + public void LoadFrom(Func load, AssetLoadPriority priority, string? onBehalfOf = null) { this.LoadOperations.Add( new AssetLoadOperation( @@ -95,13 +93,15 @@ namespace StardewModdingAPI.Events /// /// public void LoadFromModFile(string relativePath, AssetLoadPriority priority) + where TAsset : notnull { this.LoadOperations.Add( new AssetLoadOperation( mod: this.Mod, priority: priority, onBehalfOf: null, - _ => this.Mod.Mod.Helper.ModContent.Load(relativePath)) + _ => this.Mod.Mod!.Helper.ModContent.Load(relativePath) + ) ); } @@ -116,7 +116,7 @@ namespace StardewModdingAPI.Events /// You can apply any number of edits to the asset. Each edit will be applied on top of the previous one (i.e. it'll see the merged asset from all previous edits as its input). /// /// - public void Edit(Action apply, AssetEditPriority priority = AssetEditPriority.Default, string onBehalfOf = null) + public void Edit(Action apply, AssetEditPriority priority = AssetEditPriority.Default, string? onBehalfOf = null) { this.EditOperations.Add( new AssetEditOperation( diff --git a/src/SMAPI/Events/MenuChangedEventArgs.cs b/src/SMAPI/Events/MenuChangedEventArgs.cs index 362accec..c37fd216 100644 --- a/src/SMAPI/Events/MenuChangedEventArgs.cs +++ b/src/SMAPI/Events/MenuChangedEventArgs.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewValley.Menus; @@ -11,20 +9,20 @@ namespace StardewModdingAPI.Events /********* ** Accessors *********/ - /// The previous menu. - public IClickableMenu OldMenu { get; } + /// The previous menu, if any. + public IClickableMenu? OldMenu { get; } - /// The current menu. - public IClickableMenu NewMenu { get; } + /// The current menu, if any. + public IClickableMenu? NewMenu { get; } /********* ** Public methods *********/ /// Construct an instance. - /// The previous menu. - /// The current menu. - internal MenuChangedEventArgs(IClickableMenu oldMenu, IClickableMenu newMenu) + /// The previous menu, if any. + /// The current menu, if any. + internal MenuChangedEventArgs(IClickableMenu? oldMenu, IClickableMenu? newMenu) { this.OldMenu = oldMenu; this.NewMenu = newMenu; diff --git a/src/SMAPI/Framework/Command.cs b/src/SMAPI/Framework/Command.cs index 776ba238..dca1dd09 100644 --- a/src/SMAPI/Framework/Command.cs +++ b/src/SMAPI/Framework/Command.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Framework @@ -11,7 +9,7 @@ namespace StardewModdingAPI.Framework ** Accessor *********/ /// The mod that registered the command (or null if registered by SMAPI). - public IModMetadata Mod { get; } + public IModMetadata? Mod { get; } /// The command name, which the user must type to trigger it. public string Name { get; } @@ -31,7 +29,7 @@ namespace StardewModdingAPI.Framework /// The command name, which the user must type to trigger it. /// The human-readable documentation shown when the player runs the built-in 'help' command. /// The method to invoke when the command is triggered. This method is passed the command name and arguments submitted by the user. - public Command(IModMetadata mod, string name, string documentation, Action callback) + public Command(IModMetadata? mod, string name, string documentation, Action callback) { this.Mod = mod; this.Name = name; diff --git a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs index fcfa928e..6dc6f131 100644 --- a/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs +++ b/src/SMAPI/Framework/Commands/HarmonySummaryCommand.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -73,9 +71,9 @@ namespace StardewModdingAPI.Framework.Commands private IEnumerable FilterPatches(string[] searchTerms) { bool hasSearch = searchTerms.Any(); - bool IsMatch(string target) => !hasSearch || searchTerms.Any(search => target != null && target.IndexOf(search, StringComparison.OrdinalIgnoreCase) > -1); + bool IsMatch(string? target) => !hasSearch || searchTerms.Any(search => target != null && target.IndexOf(search, StringComparison.OrdinalIgnoreCase) > -1); - foreach (var patch in this.GetAllPatches()) + foreach (SearchResult patch in this.GetAllPatches()) { // matches entire patch if (IsMatch(patch.MethodDescription)) @@ -85,10 +83,10 @@ namespace StardewModdingAPI.Framework.Commands } // matches individual patchers - foreach (var pair in patch.PatchTypesByOwner.ToArray()) + foreach ((string patcherId, ISet patchTypes) in patch.PatchTypesByOwner.ToArray()) { - if (!IsMatch(pair.Key) && !pair.Value.Any(type => IsMatch(type.ToString()))) - patch.PatchTypesByOwner.Remove(pair.Key); + if (!IsMatch(patcherId) && !patchTypes.Any(type => IsMatch(type.ToString()))) + patch.PatchTypesByOwner.Remove(patcherId); } if (patch.PatchTypesByOwner.Any()) @@ -114,13 +112,13 @@ namespace StardewModdingAPI.Framework.Commands // get patch types by owner var typesByOwner = new Dictionary>(); - foreach (var group in patchGroups) + foreach ((PatchType type, IReadOnlyCollection patches) in patchGroups) { - foreach (var patch in group.Value) + foreach (Patch patch in patches) { - if (!typesByOwner.TryGetValue(patch.owner, out ISet patchTypes)) + if (!typesByOwner.TryGetValue(patch.owner, out ISet? patchTypes)) typesByOwner[patch.owner] = patchTypes = new HashSet(); - patchTypes.Add(group.Key); + patchTypes.Add(type); } } diff --git a/src/SMAPI/Framework/Content/AssetData.cs b/src/SMAPI/Framework/Content/AssetData.cs index be4a7ce6..0367e999 100644 --- a/src/SMAPI/Framework/Content/AssetData.cs +++ b/src/SMAPI/Framework/Content/AssetData.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Framework.Content @@ -7,12 +5,13 @@ namespace StardewModdingAPI.Framework.Content /// Base implementation for a content helper which encapsulates access and changes to content being read from a data file. /// The interface value type. internal class AssetData : AssetInfo, IAssetData + where TValue : notnull { /********* ** Fields *********/ /// A callback to invoke when the data is replaced (if any). - private readonly Action OnDataReplaced; + private readonly Action? OnDataReplaced; /********* @@ -31,7 +30,7 @@ namespace StardewModdingAPI.Framework.Content /// The content data being read. /// Normalizes an asset key to match the cache key. /// A callback to invoke when the data is replaced (if any). - public AssetData(string locale, IAssetName assetName, TValue data, Func getNormalizedPath, Action onDataReplaced) + public AssetData(string? locale, IAssetName assetName, TValue data, Func getNormalizedPath, Action? onDataReplaced) : base(locale, assetName, data.GetType(), getNormalizedPath) { this.Data = data; diff --git a/src/SMAPI/Framework/Content/AssetDataForDictionary.cs b/src/SMAPI/Framework/Content/AssetDataForDictionary.cs index 06dbe259..d9bfa7bf 100644 --- a/src/SMAPI/Framework/Content/AssetDataForDictionary.cs +++ b/src/SMAPI/Framework/Content/AssetDataForDictionary.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; @@ -17,7 +15,7 @@ namespace StardewModdingAPI.Framework.Content /// The content data being read. /// Normalizes an asset key to match the cache key. /// A callback to invoke when the data is replaced (if any). - public AssetDataForDictionary(string locale, IAssetName assetName, IDictionary data, Func getNormalizedPath, Action> onDataReplaced) + public AssetDataForDictionary(string? locale, IAssetName assetName, IDictionary data, Func getNormalizedPath, Action> onDataReplaced) : base(locale, assetName, data, getNormalizedPath, onDataReplaced) { } } } diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index 8e59cd27..97729c95 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; @@ -27,7 +25,7 @@ namespace StardewModdingAPI.Framework.Content /// The content data being read. /// Normalizes an asset key to match the cache key. /// A callback to invoke when the data is replaced (if any). - public AssetDataForImage(string locale, IAssetName assetName, Texture2D data, Func getNormalizedPath, Action onDataReplaced) + public AssetDataForImage(string? locale, IAssetName assetName, Texture2D data, Func getNormalizedPath, Action onDataReplaced) : base(locale, assetName, data, getNormalizedPath, onDataReplaced) { } /// diff --git a/src/SMAPI/Framework/Content/AssetDataForObject.cs b/src/SMAPI/Framework/Content/AssetDataForObject.cs index bb3966b9..e508ca30 100644 --- a/src/SMAPI/Framework/Content/AssetDataForObject.cs +++ b/src/SMAPI/Framework/Content/AssetDataForObject.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; @@ -27,7 +25,7 @@ namespace StardewModdingAPI.Framework.Content /// The content data being read. /// Normalizes an asset key to match the cache key. /// Simplifies access to private code. - public AssetDataForObject(string locale, IAssetName assetName, object data, Func getNormalizedPath, Reflector reflection) + public AssetDataForObject(string? locale, IAssetName assetName, object data, Func getNormalizedPath, Reflector reflection) : base(locale, assetName, data, getNormalizedPath, onDataReplaced: null) { this.Reflection = reflection; diff --git a/src/SMAPI/Framework/Content/AssetEditOperation.cs b/src/SMAPI/Framework/Content/AssetEditOperation.cs index 1b7d0c93..464948b0 100644 --- a/src/SMAPI/Framework/Content/AssetEditOperation.cs +++ b/src/SMAPI/Framework/Content/AssetEditOperation.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Events; @@ -18,7 +16,7 @@ namespace StardewModdingAPI.Framework.Content public AssetEditPriority Priority { get; } /// The content pack on whose behalf the edit is being applied, if any. - public IModMetadata OnBehalfOf { get; } + public IModMetadata? OnBehalfOf { get; } /// Apply the edit to an asset. public Action ApplyEdit { get; } @@ -32,7 +30,7 @@ namespace StardewModdingAPI.Framework.Content /// If there are multiple edits that apply to the same asset, the priority with which this one should be applied. /// The content pack on whose behalf the edit is being applied, if any. /// Apply the edit to an asset. - public AssetEditOperation(IModMetadata mod, AssetEditPriority priority, IModMetadata onBehalfOf, Action applyEdit) + public AssetEditOperation(IModMetadata mod, AssetEditPriority priority, IModMetadata? onBehalfOf, Action applyEdit) { this.Mod = mod; this.Priority = priority; diff --git a/src/SMAPI/Framework/Content/AssetInfo.cs b/src/SMAPI/Framework/Content/AssetInfo.cs index 51dcc61f..0f0e9bf3 100644 --- a/src/SMAPI/Framework/Content/AssetInfo.cs +++ b/src/SMAPI/Framework/Content/AssetInfo.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; @@ -19,7 +17,7 @@ namespace StardewModdingAPI.Framework.Content ** Accessors *********/ /// - public string Locale { get; } + public string? Locale { get; } /// public IAssetName Name { get; } @@ -28,7 +26,7 @@ namespace StardewModdingAPI.Framework.Content public IAssetName NameWithoutLocale { get; } /// - [Obsolete($"Use {nameof(Name)} or {nameof(NameWithoutLocale)} instead. This property will be removed in SMAPI 4.0.0.")] + [Obsolete($"Use {nameof(AssetInfo.Name)} or {nameof(AssetInfo.NameWithoutLocale)} instead. This property will be removed in SMAPI 4.0.0.")] public string AssetName { get @@ -56,7 +54,7 @@ namespace StardewModdingAPI.Framework.Content /// The asset name being read. /// The content type being read. /// Normalizes an asset key to match the cache key. - public AssetInfo(string locale, IAssetName assetName, Type type, Func getNormalizedPath) + public AssetInfo(string? locale, IAssetName assetName, Type type, Func getNormalizedPath) { this.Locale = locale; this.Name = assetName; @@ -66,7 +64,7 @@ namespace StardewModdingAPI.Framework.Content } /// - [Obsolete($"Use {nameof(Name)}.{nameof(IAssetName.IsEquivalentTo)} or {nameof(NameWithoutLocale)}.{nameof(IAssetName.IsEquivalentTo)} instead. This method will be removed in SMAPI 4.0.0.")] + [Obsolete($"Use {nameof(Name)}.{nameof(IAssetName.IsEquivalentTo)} or {nameof(AssetInfo.NameWithoutLocale)}.{nameof(IAssetName.IsEquivalentTo)} instead. This method will be removed in SMAPI 4.0.0.")] public bool AssetNameEquals(string path) { SCore.DeprecationManager.Warn( @@ -106,7 +104,7 @@ namespace StardewModdingAPI.Framework.Content return "string"; // default - return type.FullName; + return type.FullName!; } } } diff --git a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs index 7f53db9b..fc8199e8 100644 --- a/src/SMAPI/Framework/Content/AssetInterceptorChange.cs +++ b/src/SMAPI/Framework/Content/AssetInterceptorChange.cs @@ -1,9 +1,8 @@ -#nullable disable - using System; using System.Reflection; using StardewModdingAPI.Internal; +#pragma warning disable CS0618 // obsolete asset interceptors deliberately supported here namespace StardewModdingAPI.Framework.Content { /// A wrapper for and for internal cache invalidation. @@ -46,11 +45,11 @@ namespace StardewModdingAPI.Framework.Content /// Basic metadata about the asset being loaded. public bool CanIntercept(IAssetInfo asset) { - MethodInfo canIntercept = this.GetType().GetMethod(nameof(this.CanInterceptImpl), BindingFlags.Instance | BindingFlags.NonPublic); + MethodInfo? canIntercept = this.GetType().GetMethod(nameof(this.CanInterceptImpl), BindingFlags.Instance | BindingFlags.NonPublic); if (canIntercept == null) throw new InvalidOperationException($"SMAPI couldn't access the {nameof(AssetInterceptorChange)}.{nameof(this.CanInterceptImpl)} implementation."); - return (bool)canIntercept.MakeGenericMethod(asset.DataType).Invoke(this, new object[] { asset }); + return (bool)canIntercept.MakeGenericMethod(asset.DataType).Invoke(this, new object[] { asset })!; } diff --git a/src/SMAPI/Framework/Content/AssetLoadOperation.cs b/src/SMAPI/Framework/Content/AssetLoadOperation.cs index 73e60e24..b6cdec27 100644 --- a/src/SMAPI/Framework/Content/AssetLoadOperation.cs +++ b/src/SMAPI/Framework/Content/AssetLoadOperation.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Events; @@ -15,7 +13,7 @@ namespace StardewModdingAPI.Framework.Content public IModMetadata Mod { get; } /// The content pack on whose behalf the asset is being loaded, if any. - public IModMetadata OnBehalfOf { get; } + public IModMetadata? OnBehalfOf { get; } /// If there are multiple loads that apply to the same asset, the priority with which this one should be applied. public AssetLoadPriority Priority { get; } @@ -32,7 +30,7 @@ namespace StardewModdingAPI.Framework.Content /// If there are multiple loads that apply to the same asset, the priority with which this one should be applied. /// The content pack on whose behalf the asset is being loaded, if any. /// Load the initial value for an asset. - public AssetLoadOperation(IModMetadata mod, AssetLoadPriority priority, IModMetadata onBehalfOf, Func getData) + public AssetLoadOperation(IModMetadata mod, AssetLoadPriority priority, IModMetadata? onBehalfOf, Func getData) { this.Mod = mod; this.Priority = priority; diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index 4e48b08c..92452224 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -71,7 +69,7 @@ namespace StardewModdingAPI.Framework private readonly ReaderWriterLockSlim ContentManagerLock = new(); /// A cache of ordered tilesheet IDs used by vanilla maps. - private readonly Dictionary VanillaTilesheets = new(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary VanillaTilesheets = new(StringComparer.OrdinalIgnoreCase); /// An unmodified content manager which doesn't intercept assets, used to compare asset data. private readonly LocalizedContentManager VanillaContentManager; @@ -230,7 +228,7 @@ namespace StardewModdingAPI.Framework public void OnAdditionalLanguagesInitialized() { // update locale cache for custom languages, and load it now (since languages added later won't work) - var customLanguages = this.MainContentManager.Load>("Data/AdditionalLanguages"); + var customLanguages = this.MainContentManager.Load>("Data/AdditionalLanguages"); this.LocaleCodes = new Lazy>(() => this.GetLocaleCodes(customLanguages)); _ = this.LocaleCodes.Value; } @@ -303,7 +301,7 @@ namespace StardewModdingAPI.Framework /// The unique name for the content manager which should load this asset. /// The asset name within the mod folder. /// Returns whether the asset was parsed successfully. - public bool TryParseManagedAssetKey(string key, out string contentManagerID, out IAssetName relativePath) + public bool TryParseManagedAssetKey(string key, [NotNullWhen(true)] out string? contentManagerID, [NotNullWhen(true)] out IAssetName? relativePath) { contentManagerID = null; relativePath = null; @@ -333,9 +331,10 @@ namespace StardewModdingAPI.Framework /// The unique name for the content manager which should load this asset. /// The asset name within the mod folder. public bool DoesManagedAssetExist(string contentManagerID, IAssetName assetName) + where T : notnull { // get content manager - IContentManager contentManager = this.ContentManagerLock.InReadLock(() => + IContentManager? contentManager = this.ContentManagerLock.InReadLock(() => this.ContentManagers.FirstOrDefault(p => p.IsNamespaced && p.Name == contentManagerID) ); if (contentManager == null) @@ -350,9 +349,10 @@ namespace StardewModdingAPI.Framework /// The unique name for the content manager which should load this asset. /// The asset name within the mod folder. public T LoadManagedAsset(string contentManagerID, IAssetName relativePath) + where T : notnull { // get content manager - IContentManager contentManager = this.ContentManagerLock.InReadLock(() => + IContentManager? contentManager = this.ContentManagerLock.InReadLock(() => this.ContentManagers.FirstOrDefault(p => p.IsNamespaced && p.Name == contentManagerID) ); if (contentManager == null) @@ -461,6 +461,7 @@ namespace StardewModdingAPI.Framework /// The asset type. /// The asset info to load or edit. public IEnumerable GetAssetOperations(IAssetInfo info) + where T : notnull { return this.AssetOperationsByKey.GetOrSet( info.Name, @@ -491,7 +492,7 @@ namespace StardewModdingAPI.Framework { rootPath = PathUtilities.NormalizePath(rootPath); - if (!this.CaseInsensitivePathCaches.TryGetValue(rootPath, out CaseInsensitivePathCache cache)) + if (!this.CaseInsensitivePathCaches.TryGetValue(rootPath, out CaseInsensitivePathCache? cache)) this.CaseInsensitivePathCaches[rootPath] = cache = new CaseInsensitivePathCache(rootPath); return cache; @@ -501,9 +502,9 @@ namespace StardewModdingAPI.Framework /// The asset path relative to the loader root directory, not including the .xnb extension. public TilesheetReference[] GetVanillaTilesheetIds(string assetName) { - if (!this.VanillaTilesheets.TryGetValue(assetName, out TilesheetReference[] tilesheets)) + if (!this.VanillaTilesheets.TryGetValue(assetName, out TilesheetReference[]? tilesheets)) { - tilesheets = this.TryLoadVanillaAsset(assetName, out Map map) + tilesheets = this.TryLoadVanillaAsset(assetName, out Map? map) ? map.TileSheets.Select((sheet, index) => new TilesheetReference(index, sheet.Id, sheet.ImageSource, sheet.SheetSize, sheet.TileSize)).ToArray() : null; @@ -516,7 +517,7 @@ namespace StardewModdingAPI.Framework /// Get the locale code which corresponds to a language enum (e.g. fr-FR given ). /// The language enum to search. - public string GetLocaleCode(LocalizedContentManager.LanguageCode language) + public string? GetLocaleCode(LocalizedContentManager.LanguageCode language) { if (language == LocalizedContentManager.LanguageCode.mod && LocalizedContentManager.CurrentModLanguage == null) return null; @@ -535,7 +536,7 @@ namespace StardewModdingAPI.Framework foreach (IContentManager contentManager in this.ContentManagers) contentManager.Dispose(); this.ContentManagers.Clear(); - this.MainContentManager = null; + this.MainContentManager = null!; // instance no longer usable this.ContentManagerLock.Dispose(); } @@ -560,7 +561,8 @@ namespace StardewModdingAPI.Framework /// The type of asset to load. /// The asset path relative to the loader root directory, not including the .xnb extension. /// The loaded asset data. - private bool TryLoadVanillaAsset(string assetName, out T asset) + private bool TryLoadVanillaAsset(string assetName, [NotNullWhen(true)] out T? asset) + where T : notnull { try { @@ -576,12 +578,12 @@ namespace StardewModdingAPI.Framework /// Get the language enums (like ) indexed by locale code (like ja-JP). /// The custom languages to add to the lookup. - private Dictionary GetLocaleCodes(IEnumerable customLanguages) + private Dictionary GetLocaleCodes(IEnumerable customLanguages) { var map = new Dictionary(StringComparer.OrdinalIgnoreCase); // custom languages - foreach (ModLanguage language in customLanguages) + foreach (ModLanguage? language in customLanguages) { if (!string.IsNullOrWhiteSpace(language?.LanguageCode)) map[language.LanguageCode] = LocalizedContentManager.LanguageCode.mod; @@ -590,7 +592,7 @@ namespace StardewModdingAPI.Framework // vanilla languages (override custom language if they conflict) foreach (LocalizedContentManager.LanguageCode code in Enum.GetValues(typeof(LocalizedContentManager.LanguageCode))) { - string locale = this.GetLocaleCode(code); + string? locale = this.GetLocaleCode(code); if (locale != null) map[locale] = code; } @@ -602,6 +604,7 @@ namespace StardewModdingAPI.Framework /// The asset type. /// The asset info to load or edit. private IEnumerable GetAssetOperationsWithoutCache(IAssetInfo info) + where T : notnull { IAssetInfo legacyInfo = this.GetLegacyAssetInfo(info); diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index e494559d..6469fea4 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; @@ -70,7 +69,7 @@ namespace StardewModdingAPI.Framework.ContentManagers return true; // managed asset - if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string contentManagerID, out IAssetName relativePath)) + if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string? contentManagerID, out IAssetName? relativePath)) return this.Coordinator.DoesManagedAssetExist(contentManagerID, relativePath); // custom asset from a loader @@ -78,7 +77,7 @@ namespace StardewModdingAPI.Framework.ContentManagers IAssetInfo info = new AssetInfo(locale, assetName, typeof(T), this.AssertAndNormalizeAssetName); AssetLoadOperation[] loaders = this.GetLoaders(info).ToArray(); - if (!this.AssertMaxOneRequiredLoader(info, loaders, out string error)) + if (!this.AssertMaxOneRequiredLoader(info, loaders, out string? error)) { this.Monitor.Log(error, LogLevel.Warn); return false; @@ -102,7 +101,7 @@ namespace StardewModdingAPI.Framework.ContentManagers return this.RawLoad(assetName, useCache: true); // get managed asset - if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string contentManagerID, out IAssetName relativePath)) + if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string? contentManagerID, out IAssetName? relativePath)) { T managedAsset = this.Coordinator.LoadManagedAsset(contentManagerID, relativePath); this.TrackAsset(assetName, managedAsset, useCache); @@ -151,14 +150,15 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Load the initial asset from the registered loaders. /// The basic asset metadata. /// Returns the loaded asset metadata, or null if no loader matched. - private IAssetData ApplyLoader(IAssetInfo info) + private IAssetData? ApplyLoader(IAssetInfo info) + where T : notnull { // find matching loader - AssetLoadOperation loader; + AssetLoadOperation? loader; { AssetLoadOperation[] loaders = this.GetLoaders(info).OrderByDescending(p => p.Priority).ToArray(); - if (!this.AssertMaxOneRequiredLoader(info, loaders, out string error)) + if (!this.AssertMaxOneRequiredLoader(info, loaders, out string? error)) { this.Monitor.Log(error, LogLevel.Warn); return null; @@ -196,20 +196,21 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The basic asset metadata. /// The loaded asset. private IAssetData ApplyEditors(IAssetInfo info, IAssetData asset) + where T : notnull { IAssetData GetNewData(object data) => new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName, this.Reflection); // special case: if the asset was loaded with a more general type like 'object', call editors with the actual type instead. { Type actualType = asset.Data.GetType(); - Type actualOpenType = actualType.IsGenericType ? actualType.GetGenericTypeDefinition() : null; + Type? actualOpenType = actualType.IsGenericType ? actualType.GetGenericTypeDefinition() : null; if (typeof(T) != actualType && (actualOpenType == typeof(Dictionary<,>) || actualOpenType == typeof(List<>) || actualType == typeof(Texture2D) || actualType == typeof(Map))) { return (IAssetData)this.GetType() .GetMethod(nameof(this.ApplyEditors), BindingFlags.NonPublic | BindingFlags.Instance)! .MakeGenericMethod(actualType) - .Invoke(this, new object[] { info, asset }); + .Invoke(this, new object[] { info, asset })!; } } @@ -232,6 +233,7 @@ namespace StardewModdingAPI.Framework.ContentManagers } // validate edit + // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- it's only guaranteed non-null after this method if (asset.Data == null) { mod.LogAsMod($"Mod incorrectly set asset '{info.Name}'{this.GetOnBehalfOfLabel(editor.OnBehalfOf)} to a null value; ignoring override.", LogLevel.Warn); @@ -252,6 +254,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The asset type. /// The basic asset metadata. private IEnumerable GetLoaders(IAssetInfo info) + where T : notnull { return this.Coordinator .GetAssetOperations(info) @@ -262,6 +265,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The asset type. /// The basic asset metadata. private IEnumerable GetEditors(IAssetInfo info) + where T : notnull { return this.Coordinator .GetAssetOperations(info) @@ -273,7 +277,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The asset loaders to apply. /// The error message to show to the user, if the method returns false. /// Returns true if only one loader will apply, else false. - private bool AssertMaxOneRequiredLoader(IAssetInfo info, AssetLoadOperation[] loaders, out string error) + private bool AssertMaxOneRequiredLoader(IAssetInfo info, AssetLoadOperation[] loaders, [NotNullWhen(false)] out string? error) { AssetLoadOperation[] required = loaders.Where(p => p.Priority == AssetLoadPriority.Exclusive).ToArray(); if (required.Length <= 1) @@ -299,7 +303,8 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The content pack on whose behalf the action is being performed. /// whether to format the label as a parenthetical shown after the mod name like (for the 'X' content pack), instead of a standalone label like the 'X' content pack. /// Returns the on-behalf-of label if applicable, else null. - private string GetOnBehalfOfLabel(IModMetadata onBehalfOf, bool parenthetical = true) + [return: NotNullIfNotNull("onBehalfOf")] + private string? GetOnBehalfOfLabel(IModMetadata? onBehalfOf, bool parenthetical = true) { if (onBehalfOf == null) return null; @@ -315,7 +320,8 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The loaded asset data. /// The loader which loaded the asset. /// Returns whether the asset passed validation checks (after any fixes were applied). - private bool TryFixAndValidateLoadedAsset(IAssetInfo info, T data, AssetLoadOperation loader) + private bool TryFixAndValidateLoadedAsset(IAssetInfo info, [NotNullWhen(true)] T? data, AssetLoadOperation loader) + where T : notnull { IModMetadata mod = loader.Mod; @@ -335,7 +341,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // add missing tilesheet if (loadedMap.GetTileSheet(vanillaSheet.Id) == null) { - mod.Monitor.LogOnce("SMAPI fixed maps loaded by this mod to prevent errors. See the log file for details.", LogLevel.Warn); + mod.Monitor!.LogOnce("SMAPI fixed maps loaded by this mod to prevent errors. See the log file for details.", LogLevel.Warn); this.Monitor.Log($"Fixed broken map replacement: {mod.DisplayName} loaded '{info.Name}' without a required tilesheet (id: {vanillaSheet.Id}, source: {vanillaSheet.ImageSource})."); loadedMap.AddTileSheet(new TileSheet(vanillaSheet.Id, loadedMap, vanillaSheet.ImageSource, vanillaSheet.SheetSize, vanillaSheet.TileSize)); diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs index 46d5d24e..1b0e1016 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManagerForAssetPropagation.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Globalization; using Microsoft.Xna.Framework.Graphics; @@ -39,7 +37,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Get whether a texture was loaded by this content manager. /// The texture to check. - public bool IsResponsibleFor(Texture2D texture) + public bool IsResponsibleFor(Texture2D? texture) { return texture?.Tag is string tag diff --git a/src/SMAPI/Framework/ContentManagers/IContentManager.cs b/src/SMAPI/Framework/ContentManagers/IContentManager.cs index c8b2ae64..ac67cad5 100644 --- a/src/SMAPI/Framework/ContentManagers/IContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/IContentManager.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Content; @@ -33,25 +31,28 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Get whether an asset exists and can be loaded. /// The expected asset type. /// The normalized asset name. - bool DoesAssetExist(IAssetName assetName); + bool DoesAssetExist(IAssetName assetName) + where T: notnull; /// Load an asset through the content pipeline, using a localized variant of the if available. /// The type of asset to load. /// The asset name relative to the loader root directory. /// The language for which to load the asset. /// Whether to read/write the loaded asset to the asset cache. - T LoadLocalized(IAssetName assetName, LocalizedContentManager.LanguageCode language, bool useCache); + T LoadLocalized(IAssetName assetName, LocalizedContentManager.LanguageCode language, bool useCache) + where T : notnull; /// Load an asset through the content pipeline, using the exact asset name without checking for localized variants. /// The type of asset to load. /// The asset name relative to the loader root directory. /// Whether to read/write the loaded asset to the asset cache. - T LoadExact(IAssetName assetName, bool useCache); + T LoadExact(IAssetName assetName, bool useCache) + where T : notnull; /// Assert that the given key has a valid format and return a normalized form consistent with the underlying cache. /// The asset key to check. /// The asset key is empty or contains invalid characters. - string AssertAndNormalizeAssetName(string assetName); + string AssertAndNormalizeAssetName(string? assetName); /// Get the current content locale. string GetLocale(); diff --git a/src/SMAPI/Framework/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs index 2d33a22e..2cfd5cce 100644 --- a/src/SMAPI/Framework/ContentPack.cs +++ b/src/SMAPI/Framework/ContentPack.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.IO; using StardewModdingAPI.Framework.ModHelpers; @@ -69,12 +67,12 @@ namespace StardewModdingAPI.Framework } /// - public TModel ReadJsonFile(string path) where TModel : class + public TModel? ReadJsonFile(string path) where TModel : class { path = PathUtilities.NormalizePath(path); FileInfo file = this.GetFile(path); - return file.Exists && this.JsonHelper.ReadJsonFileIfExists(file.FullName, out TModel model) + return file.Exists && this.JsonHelper.ReadJsonFileIfExists(file.FullName, out TModel? model) ? model : null; } @@ -93,6 +91,7 @@ namespace StardewModdingAPI.Framework /// [Obsolete] public T LoadAsset(string key) + where T : notnull { return this.ModContent.Load(key); } @@ -101,7 +100,7 @@ namespace StardewModdingAPI.Framework [Obsolete] public string GetActualAssetKey(string key) { - return this.ModContent.GetInternalAssetName(key)?.Name; + return this.ModContent.GetInternalAssetName(key).Name; } diff --git a/src/SMAPI/Framework/CursorPosition.cs b/src/SMAPI/Framework/CursorPosition.cs index 8f36a554..24084830 100644 --- a/src/SMAPI/Framework/CursorPosition.cs +++ b/src/SMAPI/Framework/CursorPosition.cs @@ -1,5 +1,3 @@ -#nullable disable - using Microsoft.Xna.Framework; using StardewValley; @@ -41,7 +39,7 @@ namespace StardewModdingAPI.Framework } /// - public bool Equals(ICursorPosition other) + public bool Equals(ICursorPosition? other) { return other != null && this.AbsolutePixels == other.AbsolutePixels; } diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs index 8fa31165..4b8a770d 100644 --- a/src/SMAPI/Framework/Events/ManagedEvent.cs +++ b/src/SMAPI/Framework/Events/ManagedEvent.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -23,7 +21,7 @@ namespace StardewModdingAPI.Framework.Events private readonly List> Handlers = new(); /// A cached snapshot of , or null to rebuild it next raise. - private ManagedEventHandler[] CachedHandlers = Array.Empty>(); + private ManagedEventHandler[]? CachedHandlers = Array.Empty>(); /// The total number of event handlers registered for this events, regardless of whether they're still registered. private int RegistrationIndex; @@ -100,7 +98,7 @@ namespace StardewModdingAPI.Framework.Events /// Raise the event and notify all handlers. /// The event arguments to pass. /// A lambda which returns true if the event should be raised for the given mod. - public void Raise(TEventArgs args, Func match = null) + public void Raise(TEventArgs args, Func? match = null) { this.Raise((_, invoke) => invoke(args), match); } @@ -108,7 +106,7 @@ namespace StardewModdingAPI.Framework.Events /// Raise the event and notify all handlers. /// Invoke an event handler. This receives the mod which registered the handler, and should invoke the callback with the event arguments to pass it. /// A lambda which returns true if the event should be raised for the given mod. - public void Raise(Action> invoke, Func match = null) + public void Raise(Action> invoke, Func? match = null) { // skip if no handlers if (this.Handlers.Count == 0) diff --git a/src/SMAPI/Framework/Events/ManagedEventHandler.cs b/src/SMAPI/Framework/Events/ManagedEventHandler.cs index f31bc04d..d32acdb9 100644 --- a/src/SMAPI/Framework/Events/ManagedEventHandler.cs +++ b/src/SMAPI/Framework/Events/ManagedEventHandler.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Events; @@ -42,7 +40,7 @@ namespace StardewModdingAPI.Framework.Events } /// - public int CompareTo(object obj) + public int CompareTo(object? obj) { if (obj is not ManagedEventHandler other) throw new ArgumentException("Can't compare to an unrelated object type."); diff --git a/src/SMAPI/Framework/Exceptions/SContentLoadException.cs b/src/SMAPI/Framework/Exceptions/SContentLoadException.cs index c21a6b0e..be1fe748 100644 --- a/src/SMAPI/Framework/Exceptions/SContentLoadException.cs +++ b/src/SMAPI/Framework/Exceptions/SContentLoadException.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using Microsoft.Xna.Framework.Content; @@ -14,7 +12,7 @@ namespace StardewModdingAPI.Framework.Exceptions /// Construct an instance. /// The error message. /// The underlying exception, if any. - public SContentLoadException(string message, Exception ex = null) + public SContentLoadException(string message, Exception? ex = null) : base(message, ex) { } } } diff --git a/src/SMAPI/Framework/GameVersion.cs b/src/SMAPI/Framework/GameVersion.cs index aa91d8f3..542c1345 100644 --- a/src/SMAPI/Framework/GameVersion.cs +++ b/src/SMAPI/Framework/GameVersion.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; @@ -55,7 +53,7 @@ namespace StardewModdingAPI.Framework private static string GetSemanticVersionString(string gameVersion) { // mapped version - return GameVersion.VersionMap.TryGetValue(gameVersion, out string semanticVersion) + return GameVersion.VersionMap.TryGetValue(gameVersion, out string? semanticVersion) ? semanticVersion : gameVersion; } @@ -64,10 +62,10 @@ namespace StardewModdingAPI.Framework /// The semantic version string. private static string GetGameVersionString(string semanticVersion) { - foreach (var mapping in GameVersion.VersionMap) + foreach ((string gameVersion, string equivalentSemanticVersion) in GameVersion.VersionMap) { - if (mapping.Value.Equals(semanticVersion, StringComparison.OrdinalIgnoreCase)) - return mapping.Key; + if (equivalentSemanticVersion.Equals(semanticVersion, StringComparison.OrdinalIgnoreCase)) + return gameVersion; } return semanticVersion; diff --git a/src/SMAPI/Framework/IModMetadata.cs b/src/SMAPI/Framework/IModMetadata.cs index 800b198a..7cee20b9 100644 --- a/src/SMAPI/Framework/IModMetadata.cs +++ b/src/SMAPI/Framework/IModMetadata.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using StardewModdingAPI.Framework.ModHelpers; @@ -29,7 +27,7 @@ namespace StardewModdingAPI.Framework string RelativeDirectoryPath { get; } /// Metadata about the mod from SMAPI's internal data (if any). - ModDataRecordVersionedFields DataRecord { get; } + ModDataRecordVersionedFields? DataRecord { get; } /// The metadata resolution status. ModMetadataStatus Status { get; } @@ -41,31 +39,31 @@ namespace StardewModdingAPI.Framework ModWarning Warnings { get; } /// The reason the metadata is invalid, if any. - string Error { get; } + string? Error { get; } /// A detailed technical message for , if any. - public string ErrorDetails { get; } + string? ErrorDetails { get; } /// Whether the mod folder should be ignored. This is true if it was found within a folder whose name starts with a dot. bool IsIgnored { get; } /// The mod instance (if loaded and is false). - IMod Mod { get; } + IMod? Mod { get; } /// The content pack instance (if loaded and is true). - IContentPack ContentPack { get; } + IContentPack? ContentPack { get; } /// The translations for this mod (if loaded). - TranslationHelper Translations { get; } + TranslationHelper? Translations { get; } /// Writes messages to the console and log file as this mod. - IMonitor Monitor { get; } + IMonitor? Monitor { get; } /// The mod-provided API (if any). - object Api { get; } + object? Api { get; } /// The update-check metadata for this mod (if any). - ModEntryModel UpdateCheckData { get; } + ModEntryModel? UpdateCheckData { get; } /// The fake content packs created by this mod, if any. ISet> FakeContentPacks { get; } @@ -84,7 +82,7 @@ namespace StardewModdingAPI.Framework /// The reason the metadata is invalid, if any. /// A detailed technical message, if any. /// Return the instance for chaining. - IModMetadata SetStatus(ModMetadataStatus status, ModFailReason reason, string error, string errorDetails = null); + IModMetadata SetStatus(ModMetadataStatus status, ModFailReason reason, string? error, string? errorDetails = null); /// Set a warning flag for the mod. /// The warning to set. @@ -103,7 +101,7 @@ namespace StardewModdingAPI.Framework /// Set the mod-provided API instance. /// The mod-provided API. - IModMetadata SetApi(object api); + IModMetadata SetApi(object? api); /// Set the update-check metadata for this mod. /// The update-check metadata. @@ -117,7 +115,7 @@ namespace StardewModdingAPI.Framework /// Whether the mod has the given ID. /// The mod ID to check. - bool HasID(string id); + bool HasID(string? id); /// Get the defined update keys. /// Only return valid update keys. diff --git a/src/SMAPI/Framework/Logging/LogFileManager.cs b/src/SMAPI/Framework/Logging/LogFileManager.cs index 0b6f9ad2..b396091a 100644 --- a/src/SMAPI/Framework/Logging/LogFileManager.cs +++ b/src/SMAPI/Framework/Logging/LogFileManager.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.IO; @@ -32,7 +30,7 @@ namespace StardewModdingAPI.Framework.Logging this.Path = path; // create log directory if needed - string logDir = System.IO.Path.GetDirectoryName(path); + string? logDir = System.IO.Path.GetDirectoryName(path); if (logDir == null) throw new ArgumentException($"The log path '{path}' is not valid."); Directory.CreateDirectory(logDir); diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index 12ef0439..b610b395 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -114,6 +112,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// public T Load(string key, ContentSource source = ContentSource.ModFolder) + where T : notnull { IAssetName assetName = this.ContentCore.ParseAssetName(key, allowLocales: source == ContentSource.GameContent); @@ -140,7 +139,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// [Pure] - public string NormalizeAssetName(string assetName) + public string NormalizeAssetName(string? assetName) { return this.ModContentManager.AssertAndNormalizeAssetName(assetName); } @@ -171,6 +170,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// public bool InvalidateCache() + where T : notnull { 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(); @@ -184,7 +184,8 @@ namespace StardewModdingAPI.Framework.ModHelpers } /// - public IAssetData GetPatchHelper(T data, string assetName = null) + public IAssetData GetPatchHelper(T data, string? assetName = null) + where T : notnull { if (data == null) throw new ArgumentNullException(nameof(data), "Can't get a patch helper for a null value."); diff --git a/src/SMAPI/Framework/ModHelpers/DataHelper.cs b/src/SMAPI/Framework/ModHelpers/DataHelper.cs index 86a34ee8..92b3b398 100644 --- a/src/SMAPI/Framework/ModHelpers/DataHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/DataHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.IO; @@ -42,19 +40,21 @@ namespace StardewModdingAPI.Framework.ModHelpers ** JSON file ****/ /// - public TModel ReadJsonFile(string path) where TModel : class + public TModel? ReadJsonFile(string path) + where TModel : class { if (!PathUtilities.IsSafeRelativePath(path)) throw new InvalidOperationException($"You must call {nameof(IModHelper.Data)}.{nameof(this.ReadJsonFile)} with a relative path."); path = Path.Combine(this.ModFolderPath, PathUtilities.NormalizePath(path)); - return this.JsonHelper.ReadJsonFileIfExists(path, out TModel data) + return this.JsonHelper.ReadJsonFileIfExists(path, out TModel? data) ? data : null; } /// - public void WriteJsonFile(string path, TModel data) where TModel : class + public void WriteJsonFile(string path, TModel? data) + where TModel : class { if (!PathUtilities.IsSafeRelativePath(path)) throw new InvalidOperationException($"You must call {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.WriteJsonFile)} with a relative path (without directory climbing)."); @@ -71,7 +71,8 @@ namespace StardewModdingAPI.Framework.ModHelpers ** Save file ****/ /// - public TModel ReadSaveData(string key) where TModel : class + public TModel? ReadSaveData(string key) + where TModel : class { if (Context.LoadStage == LoadStage.None) throw new InvalidOperationException($"Can't use {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.ReadSaveData)} when a save file isn't loaded."); @@ -82,14 +83,15 @@ namespace StardewModdingAPI.Framework.ModHelpers string internalKey = this.GetSaveFileKey(key); foreach (IDictionary dataField in this.GetDataFields(Context.LoadStage)) { - if (dataField.TryGetValue(internalKey, out string value)) + if (dataField.TryGetValue(internalKey, out string? value)) return this.JsonHelper.Deserialize(value); } return null; } /// - public void WriteSaveData(string key, TModel model) where TModel : class + public void WriteSaveData(string key, TModel? model) + where TModel : class { if (Context.LoadStage == LoadStage.None) throw new InvalidOperationException($"Can't use {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.WriteSaveData)} when a save file isn't loaded."); @@ -97,7 +99,7 @@ namespace StardewModdingAPI.Framework.ModHelpers throw new InvalidOperationException($"Can't use {nameof(IMod.Helper)}.{nameof(IModHelper.Data)}.{nameof(this.WriteSaveData)} when connected to a remote host. (Save files are stored on the main player's computer.)"); string internalKey = this.GetSaveFileKey(key); - string data = model != null + string? data = model != null ? this.JsonHelper.Serialize(model, Formatting.None) : null; @@ -114,16 +116,18 @@ namespace StardewModdingAPI.Framework.ModHelpers ** Global app data ****/ /// - public TModel ReadGlobalData(string key) where TModel : class + public TModel? ReadGlobalData(string key) + where TModel : class { string path = this.GetGlobalDataPath(key); - return this.JsonHelper.ReadJsonFileIfExists(path, out TModel data) + return this.JsonHelper.ReadJsonFileIfExists(path, out TModel? data) ? data : null; } /// - public void WriteGlobalData(string key, TModel data) where TModel : class + public void WriteGlobalData(string key, TModel? data) + where TModel : class { string path = this.GetGlobalDataPath(key); if (data != null) diff --git a/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs index 6d0c2f5f..4c1cde02 100644 --- a/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Linq; using StardewModdingAPI.Framework.Content; @@ -71,6 +69,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// public T Load(string key) + where T : notnull { IAssetName assetName = this.ContentCore.ParseAssetName(key, allowLocales: true); return this.Load(assetName); @@ -78,6 +77,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// public T Load(IAssetName assetName) + where T : notnull { try { @@ -105,6 +105,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// public bool InvalidateCache() + where T : notnull { 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(); @@ -118,7 +119,8 @@ namespace StardewModdingAPI.Framework.ModHelpers } /// - public IAssetData GetPatchHelper(T data, string assetName = null) + public IAssetData GetPatchHelper(T data, string? assetName = null) + where T : notnull { if (data == null) throw new ArgumentNullException(nameof(data), "Can't get a patch helper for a null value."); diff --git a/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs index b149ed82..fc47f269 100644 --- a/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using Microsoft.Xna.Framework.Content; using StardewModdingAPI.Framework.Content; @@ -57,6 +55,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// public T Load(string relativePath) + where T : notnull { relativePath = this.RelativePathCache.GetAssetName(relativePath); @@ -80,7 +79,8 @@ namespace StardewModdingAPI.Framework.ModHelpers } /// - public IAssetData GetPatchHelper(T data, string relativePath = null) + public IAssetData GetPatchHelper(T data, string? relativePath = null) + where T : notnull { if (data == null) throw new ArgumentNullException(nameof(data), "Can't get a patch helper for a null value."); diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index e277e6fa..84899610 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using StardewModdingAPI.Framework.Reflection; @@ -47,7 +45,7 @@ namespace StardewModdingAPI.Framework.ModHelpers } /// - public IModInfo Get(string uniqueID) + public IModInfo? Get(string uniqueID) { return this.Registry.Get(uniqueID); } @@ -59,7 +57,7 @@ namespace StardewModdingAPI.Framework.ModHelpers } /// - public object GetApi(string uniqueID) + public object? GetApi(string uniqueID) { // validate ready if (!this.Registry.AreAllModsInitialized) @@ -69,17 +67,18 @@ namespace StardewModdingAPI.Framework.ModHelpers } // get raw API - IModMetadata mod = this.Registry.Get(uniqueID); + 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}."); return mod?.Api; } /// - public TInterface GetApi(string uniqueID) where TInterface : class + public TInterface? GetApi(string uniqueID) + where TInterface : class { // get raw API - object api = this.GetApi(uniqueID); + object? api = this.GetApi(uniqueID); if (api == null) return null; diff --git a/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs b/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs index 96b074e2..a419327e 100644 --- a/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using StardewModdingAPI.Framework.Networking; using StardewValley; @@ -41,9 +39,9 @@ namespace StardewModdingAPI.Framework.ModHelpers } /// - public IMultiplayerPeer GetConnectedPlayer(long id) + public IMultiplayerPeer? GetConnectedPlayer(long id) { - return this.Multiplayer.Peers.TryGetValue(id, out MultiplayerPeer peer) + return this.Multiplayer.Peers.TryGetValue(id, out MultiplayerPeer? peer) ? peer : null; } @@ -55,7 +53,7 @@ namespace StardewModdingAPI.Framework.ModHelpers } /// - public void SendMessage(TMessage message, string messageType, string[] modIDs = null, long[] playerIDs = null) + public void SendMessage(TMessage message, string messageType, string[]? modIDs = null, long[]? playerIDs = null) { this.Multiplayer.BroadcastModMessage( message: message, diff --git a/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs b/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs index 24cbd01c..af018c87 100644 --- a/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Reflection; using StardewModdingAPI.Framework.Reflection; @@ -39,7 +37,7 @@ namespace StardewModdingAPI.Framework.ModHelpers { return this.AssertAccessAllowed( this.Reflector.GetField(obj, name, required) - ); + )!; } /// @@ -47,7 +45,7 @@ namespace StardewModdingAPI.Framework.ModHelpers { return this.AssertAccessAllowed( this.Reflector.GetField(type, name, required) - ); + )!; } /// @@ -55,7 +53,7 @@ namespace StardewModdingAPI.Framework.ModHelpers { return this.AssertAccessAllowed( this.Reflector.GetProperty(obj, name, required) - ); + )!; } /// @@ -63,7 +61,7 @@ namespace StardewModdingAPI.Framework.ModHelpers { return this.AssertAccessAllowed( this.Reflector.GetProperty(type, name, required) - ); + )!; } /// @@ -71,7 +69,7 @@ namespace StardewModdingAPI.Framework.ModHelpers { return this.AssertAccessAllowed( this.Reflector.GetMethod(obj, name, required) - ); + )!; } /// @@ -79,7 +77,7 @@ namespace StardewModdingAPI.Framework.ModHelpers { return this.AssertAccessAllowed( this.Reflector.GetMethod(type, name, required) - ); + )!; } @@ -90,7 +88,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The field value type. /// The field being accessed. /// Returns the same field instance for convenience. - private IReflectedField AssertAccessAllowed(IReflectedField field) + private IReflectedField? AssertAccessAllowed(IReflectedField? field) { this.AssertAccessAllowed(field?.FieldInfo); return field; @@ -100,7 +98,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The property value type. /// The property being accessed. /// Returns the same property instance for convenience. - private IReflectedProperty AssertAccessAllowed(IReflectedProperty property) + private IReflectedProperty? AssertAccessAllowed(IReflectedProperty? property) { this.AssertAccessAllowed(property?.PropertyInfo.GetMethod?.GetBaseDefinition()); this.AssertAccessAllowed(property?.PropertyInfo.SetMethod?.GetBaseDefinition()); @@ -110,7 +108,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Assert that mods can use the reflection helper to access the given member. /// The method being accessed. /// Returns the same method instance for convenience. - private IReflectedMethod AssertAccessAllowed(IReflectedMethod method) + private IReflectedMethod? AssertAccessAllowed(IReflectedMethod? method) { this.AssertAccessAllowed(method?.MethodInfo.GetBaseDefinition()); return method; @@ -118,18 +116,18 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Assert that mods can use the reflection helper to access the given member. /// The member being accessed. - private void AssertAccessAllowed(MemberInfo member) + private void AssertAccessAllowed(MemberInfo? member) { if (member == null) return; // get type which defines the member - Type declaringType = member.DeclaringType; + Type? declaringType = member.DeclaringType; if (declaringType == null) throw new InvalidOperationException($"Can't validate access to {member.MemberType} {member.Name} because it has no declaring type."); // should never happen // validate access - string rootNamespace = typeof(Program).Namespace; + string? rootNamespace = typeof(Program).Namespace; if (declaringType.Namespace == rootNamespace || declaringType.Namespace?.StartsWith(rootNamespace + ".") == true) throw new InvalidOperationException($"SMAPI blocked access by {this.ModName} to its internals through the reflection API. Accessing the SMAPI internals is strongly discouraged since they're subject to change, which means the mod can break without warning. (Detected access to {declaringType.FullName}.{member.Name}.)"); } diff --git a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs index 1d4ddf72..b3378ad1 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyDefinitionResolver.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using Mono.Cecil; @@ -38,6 +36,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// Resolve an assembly reference. /// The assembly name. + /// The assembly can't be resolved. public override AssemblyDefinition Resolve(AssemblyNameReference name) { return this.ResolveName(name.Name) ?? base.Resolve(name); @@ -46,6 +45,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// Resolve an assembly reference. /// The assembly name. /// The assembly reader parameters. + /// The assembly can't be resolved. public override AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) { return this.ResolveName(name.Name) ?? base.Resolve(name, parameters); @@ -57,9 +57,9 @@ namespace StardewModdingAPI.Framework.ModLoading *********/ /// Resolve a known assembly definition based on its short or full name. /// The assembly's short or full name. - private AssemblyDefinition ResolveName(string name) + private AssemblyDefinition? ResolveName(string name) { - return this.Lookup.TryGetValue(name, out AssemblyDefinition match) + return this.Lookup.TryGetValue(name, out AssemblyDefinition? match) ? match : null; } diff --git a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs index 7c94beb7..f5d449c5 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/EventFinder.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using System.Linq; using Mono.Cecil; @@ -57,7 +55,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders { if (this.MethodNames.Any()) { - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); if (methodRef != null && methodRef.DeclaringType.FullName == this.FullTypeName && this.MethodNames.Contains(methodRef.Name)) { string eventName = methodRef.Name.Split(new[] { '_' }, 2)[1]; diff --git a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs index 96b4098a..7fe4abec 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/FieldFinder.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using System.Linq; using Mono.Cecil; @@ -51,7 +49,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders { if (this.FieldNames.Any()) { - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef != null && fieldRef.DeclaringType.FullName == this.FullTypeName && this.FieldNames.Contains(fieldRef.Name)) { this.FieldNames.Remove(fieldRef.Name); diff --git a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs index 7d3c1fd7..e8fdc8c7 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/MethodFinder.cs @@ -1,5 +1,3 @@ -#nullable disable - using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -54,7 +52,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// The IL instruction. protected bool IsMatch(Instruction instruction) { - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); return methodRef != null && methodRef.DeclaringType.FullName == this.FullTypeName diff --git a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs index b2f2e193..2af76f55 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/PropertyFinder.cs @@ -1,5 +1,3 @@ -#nullable disable - using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -54,7 +52,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// The IL instruction. protected bool IsMatch(Instruction instruction) { - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); return methodRef != null && methodRef.DeclaringType.FullName == this.FullTypeName diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs index 81f90498..f34542c3 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMemberWithUnexpectedTypeFinder.cs @@ -1,6 +1,5 @@ -#nullable disable - using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; @@ -34,11 +33,11 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // field reference - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef != null && this.ShouldValidate(fieldRef.DeclaringType)) { // get target field - FieldDefinition targetField = fieldRef.DeclaringType.Resolve()?.Fields.FirstOrDefault(p => p.Name == fieldRef.Name); + FieldDefinition? targetField = fieldRef.DeclaringType.Resolve()?.Fields.FirstOrDefault(p => p.Name == fieldRef.Name); if (targetField == null) return false; @@ -51,16 +50,16 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders } // method reference - MethodReference methodReference = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodReference = RewriteHelper.AsMethodReference(instruction); if (methodReference != null && !this.IsUnsupported(methodReference) && this.ShouldValidate(methodReference.DeclaringType)) { // get potential targets - MethodDefinition[] candidateMethods = methodReference.DeclaringType.Resolve()?.Methods.Where(found => found.Name == methodReference.Name).ToArray(); + MethodDefinition[]? candidateMethods = methodReference.DeclaringType.Resolve()?.Methods.Where(found => found.Name == methodReference.Name).ToArray(); if (candidateMethods == null || !candidateMethods.Any()) return false; // compare return types - MethodDefinition methodDef = methodReference.Resolve(); + MethodDefinition? methodDef = methodReference.Resolve(); if (methodDef == null) return false; // validated by ReferenceToMissingMemberFinder @@ -80,7 +79,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders *********/ /// Whether references to the given type should be validated. /// The type reference. - private bool ShouldValidate(TypeReference type) + private bool ShouldValidate([NotNullWhen(true)] TypeReference? type) { return type != null && this.ValidateReferencesToAssemblies.Contains(type.Scope.Name); } diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs index 001d1986..fae7fb12 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToMissingMemberFinder.cs @@ -1,6 +1,5 @@ -#nullable disable - using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -33,10 +32,10 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // field reference - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef != null && this.ShouldValidate(fieldRef.DeclaringType)) { - FieldDefinition target = fieldRef.Resolve(); + FieldDefinition? target = fieldRef.Resolve(); if (target == null || target.HasConstant) { this.MarkFlag(InstructionHandleResult.NotCompatible, $"reference to {fieldRef.DeclaringType.FullName}.{fieldRef.Name} (no such field)"); @@ -45,10 +44,10 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders } // method reference - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); if (methodRef != null && this.ShouldValidate(methodRef.DeclaringType) && !this.IsUnsupported(methodRef)) { - MethodDefinition target = methodRef.Resolve(); + MethodDefinition? target = methodRef.Resolve(); if (target == null) { string phrase; @@ -73,7 +72,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders *********/ /// Whether references to the given type should be validated. /// The type reference. - private bool ShouldValidate(TypeReference type) + private bool ShouldValidate([NotNullWhen(true)] TypeReference? type) { return type != null && this.ValidateReferencesToAssemblies.Contains(type.Scope.Name); } diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs index 4c589ed8..17acbf9a 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/TypeAssemblyFinder.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using Mono.Cecil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -19,7 +17,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders private readonly InstructionHandleResult Result; /// Get whether a matched type should be ignored. - private readonly Func ShouldIgnore; + private readonly Func? ShouldIgnore; /********* @@ -29,7 +27,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// The full assembly name to which to find references. /// The result to return for matching instructions. /// Get whether a matched type should be ignored. - public TypeAssemblyFinder(string assemblyName, InstructionHandleResult result, Func shouldIgnore = null) + public TypeAssemblyFinder(string assemblyName, InstructionHandleResult result, Func? shouldIgnore = null) : base(defaultPhrase: $"{assemblyName} assembly") { this.AssemblyName = assemblyName; diff --git a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs index 04a5b970..77762f41 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/TypeFinder.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using Mono.Cecil; @@ -20,7 +18,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders private readonly InstructionHandleResult Result; /// Get whether a matched type should be ignored. - private readonly Func ShouldIgnore; + private readonly Func? ShouldIgnore; /********* @@ -30,7 +28,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// The full type names to match. /// The result to return for matching instructions. /// Get whether a matched type should be ignored. - public TypeFinder(string[] fullTypeNames, InstructionHandleResult result, Func shouldIgnore = null) + public TypeFinder(string[] fullTypeNames, InstructionHandleResult result, Func? shouldIgnore = null) : base(defaultPhrase: $"{string.Join(", ", fullTypeNames)} type{(fullTypeNames.Length != 1 ? "s" : "")}") // default phrase should never be used { this.FullTypeNames = new HashSet(fullTypeNames); @@ -42,7 +40,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Finders /// The full type name to match. /// The result to return for matching instructions. /// Get whether a matched type should be ignored. - public TypeFinder(string fullTypeName, InstructionHandleResult result, Func shouldIgnore = null) + public TypeFinder(string fullTypeName, InstructionHandleResult result, Func? shouldIgnore = null) : this(new[] { fullTypeName }, result, shouldIgnore) { } /// diff --git a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs index bea786cd..865bf076 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/BaseInstructionHandler.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using Mono.Cecil; @@ -59,7 +57,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /// The result flag to set. /// The result message to add. /// Returns true for convenience. - protected bool MarkFlag(InstructionHandleResult flag, string resultMessage = null) + protected bool MarkFlag(InstructionHandleResult flag, string? resultMessage = null) { this.Flags.Add(flag); if (resultMessage != null) diff --git a/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs b/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs index 09ff78f7..55369602 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; @@ -10,6 +9,7 @@ using Mono.Collections.Generic; namespace StardewModdingAPI.Framework.ModLoading.Framework { /// Handles recursively rewriting loaded assembly code. + [SuppressMessage("ReSharper", "AccessToModifiedClosure", Justification = "Rewrite callbacks are invoked immediately.")] internal class RecursiveRewriter { /********* @@ -77,7 +77,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework { changed |= this.RewriteModuleImpl(this.Module); - foreach (var type in types) + foreach (TypeDefinition type in types) changed |= this.RewriteTypeDefinition(type); } catch (Exception ex) @@ -129,9 +129,10 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework ILProcessor cil = method.Body.GetILProcessor(); Collection instructions = cil.Body.Instructions; bool addedInstructions = false; + // ReSharper disable once ForCanBeConvertedToForeach -- deliberate to allow changing the collection for (int i = 0; i < instructions.Count; i++) { - var instruction = instructions[i]; + Instruction instruction = instructions[i]; if (instruction.OpCode.Code == Code.Nop) continue; @@ -174,7 +175,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework bool rewritten = false; // field reference - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef != null) { rewritten |= this.RewriteTypeReference(fieldRef.DeclaringType, newType => fieldRef.DeclaringType = newType); @@ -182,7 +183,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework } // method reference - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); if (methodRef != null) this.RewriteMethodReference(methodRef); @@ -212,7 +213,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework }); rewritten |= this.RewriteTypeReference(methodRef.ReturnType, newType => methodRef.ReturnType = newType); - foreach (var parameter in methodRef.Parameters) + foreach (ParameterDefinition parameter in methodRef.Parameters) rewritten |= this.RewriteTypeReference(parameter.ParameterType, newType => parameter.ParameterType = newType); if (methodRef is GenericInstanceMethod genericRef) @@ -264,7 +265,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework bool curChanged = false; // attribute type - TypeReference newAttrType = null; + TypeReference? newAttrType = null; rewritten |= this.RewriteTypeReference(attribute.AttributeType, newType => { newAttrType = newType; @@ -289,9 +290,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework if (curChanged) { // get constructor - MethodDefinition constructor = (newAttrType ?? attribute.AttributeType) + MethodDefinition? constructor = (newAttrType ?? attribute.AttributeType) .Resolve() - .Methods + ?.Methods .Where(method => method.IsConstructor) .FirstOrDefault(ctor => RewriteHelper.HasMatchingSignature(ctor, attribute.Constructor)); if (constructor == null) @@ -301,9 +302,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework var newAttr = new CustomAttribute(this.Module.ImportReference(constructor)); for (int i = 0; i < argTypes.Length; i++) newAttr.ConstructorArguments.Add(new CustomAttributeArgument(argTypes[i], attribute.ConstructorArguments[i].Value)); - foreach (var prop in attribute.Properties) + foreach (CustomAttributeNamedArgument prop in attribute.Properties) newAttr.Properties.Add(new CustomAttributeNamedArgument(prop.Name, prop.Argument)); - foreach (var field in attribute.Fields) + foreach (CustomAttributeNamedArgument field in attribute.Fields) newAttr.Fields.Add(new CustomAttributeNamedArgument(field.Name, field.Argument)); // swap attribute diff --git a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs index 8f47fbdd..15f71251 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Linq; using System.Reflection; @@ -23,7 +21,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework *********/ /// Get the field reference from an instruction if it matches. /// The IL instruction. - public static FieldReference AsFieldReference(Instruction instruction) + public static FieldReference? AsFieldReference(Instruction instruction) { return instruction.OpCode == OpCodes.Ldfld || instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stfld || instruction.OpCode == OpCodes.Stsfld ? (FieldReference)instruction.Operand @@ -32,7 +30,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /// Get the method reference from an instruction if it matches. /// The IL instruction. - public static MethodReference AsMethodReference(Instruction instruction) + public static MethodReference? AsMethodReference(Instruction instruction) { return instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt || instruction.OpCode == OpCodes.Newobj ? (MethodReference)instruction.Operand @@ -42,7 +40,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /// Get the CIL instruction to load a value onto the stack. /// The constant value to inject. /// Returns the instruction, or null if the value type isn't supported. - public static Instruction GetLoadValueInstruction(object rawValue) + public static Instruction? GetLoadValueInstruction(object? rawValue) { return rawValue switch { @@ -151,7 +149,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Framework /// The type ID to compare. /// The other type ID to compare. /// true if the type IDs look like the same type, false if not. - public static bool LooksLikeSameType(TypeReference typeA, TypeReference typeB) + public static bool LooksLikeSameType(TypeReference? typeA, TypeReference? typeB) { return RewriteHelper.TypeDefinitionComparer.Equals(typeA, typeB); } diff --git a/src/SMAPI/Framework/ModLoading/InvalidModStateException.cs b/src/SMAPI/Framework/ModLoading/InvalidModStateException.cs index 9dca9bc4..b53a9886 100644 --- a/src/SMAPI/Framework/ModLoading/InvalidModStateException.cs +++ b/src/SMAPI/Framework/ModLoading/InvalidModStateException.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Framework.ModLoading @@ -10,7 +8,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// Construct an instance. /// The error message. /// The underlying exception, if any. - public InvalidModStateException(string message, Exception ex = null) + public InvalidModStateException(string message, Exception? ex = null) : base(message, ex) { } } } diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs index 0e698bfd..fe54634b 100644 --- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs +++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using StardewModdingAPI.Framework.ModHelpers; @@ -44,7 +43,7 @@ namespace StardewModdingAPI.Framework.ModLoading public IManifest Manifest { get; } /// - public ModDataRecordVersionedFields DataRecord { get; } + public ModDataRecordVersionedFields? DataRecord { get; } /// public ModMetadataStatus Status { get; private set; } @@ -56,33 +55,35 @@ namespace StardewModdingAPI.Framework.ModLoading public ModWarning Warnings => this.ActualWarnings & ~(this.DataRecord?.DataRecord.SuppressWarnings ?? ModWarning.None); /// - public string Error { get; private set; } + public string? Error { get; private set; } /// - public string ErrorDetails { get; private set; } + public string? ErrorDetails { get; private set; } /// public bool IsIgnored { get; } /// - public IMod Mod { get; private set; } + public IMod? Mod { get; private set; } /// - public IContentPack ContentPack { get; private set; } + public IContentPack? ContentPack { get; private set; } /// - public TranslationHelper Translations { get; private set; } + public TranslationHelper? Translations { get; private set; } /// - public IMonitor Monitor { get; private set; } + public IMonitor? Monitor { get; private set; } /// - public object Api { get; private set; } + public object? Api { get; private set; } /// - public ModEntryModel UpdateCheckData { get; private set; } + public ModEntryModel? UpdateCheckData { get; private set; } /// + [MemberNotNullWhen(true, nameof(ModMetadata.ContentPack))] + [SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "The manifest may be null for broken mods while loading.")] public bool IsContentPack => this.Manifest?.ContentPackFor != null; /// The fake content packs created by this mod, if any. @@ -99,13 +100,13 @@ namespace StardewModdingAPI.Framework.ModLoading /// The mod manifest. /// Metadata about the mod from SMAPI's internal data (if any). /// Whether the mod folder should be ignored. This should be true if it was found within a folder whose name starts with a dot. - public ModMetadata(string displayName, string directoryPath, string rootPath, IManifest manifest, ModDataRecordVersionedFields dataRecord, bool isIgnored) + public ModMetadata(string displayName, string directoryPath, string rootPath, IManifest? manifest, ModDataRecordVersionedFields? dataRecord, bool isIgnored) { this.DisplayName = displayName; this.DirectoryPath = directoryPath; this.RootPath = rootPath; this.RelativeDirectoryPath = PathUtilities.GetRelativePath(this.RootPath, this.DirectoryPath); - this.Manifest = manifest; + this.Manifest = manifest!; // manifest may be null in low-level SMAPI code, but won't be null once it's received by mods via IModInfo this.DataRecord = dataRecord; this.IsIgnored = isIgnored; @@ -121,7 +122,7 @@ namespace StardewModdingAPI.Framework.ModLoading } /// - public IModMetadata SetStatus(ModMetadataStatus status, ModFailReason reason, string error, string errorDetails = null) + public IModMetadata SetStatus(ModMetadataStatus status, ModFailReason reason, string? error, string? errorDetails = null) { this.Status = status; this.FailReason = reason; @@ -162,7 +163,7 @@ namespace StardewModdingAPI.Framework.ModLoading } /// - public IModMetadata SetApi(object api) + public IModMetadata SetApi(object? api) { this.Api = api; return this; @@ -176,6 +177,7 @@ namespace StardewModdingAPI.Framework.ModLoading } /// + [MemberNotNullWhen(true, nameof(IModInfo.Manifest))] public bool HasManifest() { return this.Manifest != null; @@ -190,7 +192,7 @@ namespace StardewModdingAPI.Framework.ModLoading } /// - public bool HasID(string id) + public bool HasID(string? id) { return this.HasID() @@ -245,7 +247,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// public string GetRelativePathWithRoot() { - string rootFolderName = Path.GetFileName(this.RootPath) ?? ""; + string rootFolderName = Path.GetFileName(this.RootPath); return Path.Combine(rootFolderName, this.RelativeDirectoryPath); } @@ -254,7 +256,7 @@ namespace StardewModdingAPI.Framework.ModLoading { foreach (var reference in this.FakeContentPacks.ToArray()) { - if (!reference.TryGetTarget(out ContentPack pack)) + if (!reference.TryGetTarget(out ContentPack? pack)) { this.FakeContentPacks.Remove(reference); continue; diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs index c05005b8..afe38bfd 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/AccessToolsFacade.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -19,7 +17,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades /********* ** Public methods *********/ - public static ConstructorInfo DeclaredConstructor(Type type, Type[] parameters = null) + public static ConstructorInfo DeclaredConstructor(Type type, Type[]? parameters = null) { // Harmony 1.x matched both static and instance constructors return @@ -27,7 +25,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades ?? AccessTools.DeclaredConstructor(type, parameters, searchForStatic: true); } - public static ConstructorInfo Constructor(Type type, Type[] parameters = null) + public static ConstructorInfo Constructor(Type type, Type[]? parameters = null) { // Harmony 1.x matched both static and instance constructors return diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs index fea8c100..9c8ba2b0 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyInstanceFacade.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -30,7 +28,8 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades return new Harmony(id); } - public DynamicMethod Patch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null) + [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse", Justification = "If the user passes a null original method, we let it fail in the underlying Harmony instance instead of handling it here.")] + public DynamicMethod Patch(MethodBase original, HarmonyMethod? prefix = null, HarmonyMethod? postfix = null, HarmonyMethod? transpiler = null) { // In Harmony 1.x you could target a virtual method that's not implemented by the // target type, but in Harmony 2.0 you need to target the concrete implementation. @@ -60,7 +59,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades /// The prefix method, if any. /// The postfix method, if any. /// The transpiler method, if any. - private string GetPatchTypesLabel(HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null) + private string GetPatchTypesLabel(HarmonyMethod? prefix = null, HarmonyMethod? postfix = null, HarmonyMethod? transpiler = null) { var patchTypes = new List(); @@ -76,7 +75,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades /// Get a human-readable label for the method being patched. /// The method being patched. - private string GetMethodLabel(MethodBase method) + private string GetMethodLabel(MethodBase? method) { return method != null ? $"method {method.DeclaringType?.FullName}.{method.Name}" diff --git a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs index 93124591..2b1ca54b 100644 --- a/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs +++ b/src/SMAPI/Framework/ModLoading/RewriteFacades/HarmonyMethodFacade.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Diagnostics.CodeAnalysis; using System.Reflection; @@ -23,7 +21,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades this.ImportMethodImpl(method); } - public HarmonyMethodFacade(Type type, string name, Type[] parameters = null) + public HarmonyMethodFacade(Type type, string name, Type[]? parameters = null) { this.ImportMethodImpl(AccessTools.Method(type, name, parameters)); } @@ -40,7 +38,7 @@ namespace StardewModdingAPI.Framework.ModLoading.RewriteFacades // internal code still handles null fine. For backwards compatibility, this bypasses // the new restriction when the mod hasn't been updated for Harmony 2.0 yet. - MethodInfo importMethod = typeof(HarmonyMethod).GetMethod("ImportMethod", BindingFlags.Instance | BindingFlags.NonPublic); + MethodInfo? importMethod = typeof(HarmonyMethod).GetMethod("ImportMethod", BindingFlags.Instance | BindingFlags.NonPublic); if (importMethod == null) throw new InvalidOperationException("Can't find 'HarmonyMethod.ImportMethod' method"); importMethod.Invoke(this, new object[] { methodInfo }); diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs index 92397c58..aea490c8 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HarmonyRewriter.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using HarmonyLib; using Mono.Cecil; @@ -59,7 +57,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters if (this.ShouldRewrite) { // rewrite Harmony 1.x methods to Harmony 2.0 - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); if (this.TryRewriteMethodsToFacade(module, methodRef)) { this.OnChanged(); @@ -67,7 +65,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters } // rewrite renamed fields - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef != null) { if (fieldRef.DeclaringType.FullName == "HarmonyLib.HarmonyMethod" && fieldRef.Name == "prioritiy") @@ -95,13 +93,13 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// Rewrite methods to use Harmony facades if needed. /// The assembly module containing the method reference. /// The method reference to map. - private bool TryRewriteMethodsToFacade(ModuleDefinition module, MethodReference methodRef) + private bool TryRewriteMethodsToFacade(ModuleDefinition module, MethodReference? methodRef) { if (!this.ReplacedTypes) return false; // not Harmony (or already using Harmony 2.0) // get facade type - Type toType = methodRef?.DeclaringType.FullName switch + Type? toType = methodRef?.DeclaringType.FullName switch { "HarmonyLib.Harmony" => typeof(HarmonyInstanceFacade), "HarmonyLib.AccessTools" => typeof(AccessToolsFacade), @@ -112,9 +110,9 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters return false; // map if there's a matching method - if (RewriteHelper.HasMatchingSignature(toType, methodRef)) + if (RewriteHelper.HasMatchingSignature(toType, methodRef!)) { - methodRef.DeclaringType = module.ImportReference(toType); + methodRef!.DeclaringType = module.ImportReference(toType); return true; } @@ -139,7 +137,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters { string fullName = type.FullName.Replace("Harmony.", "HarmonyLib."); string targetName = typeof(Harmony).AssemblyQualifiedName!.Replace(typeof(Harmony).FullName!, fullName); - return Type.GetType(targetName, throwOnError: true); + return Type.GetType(targetName, throwOnError: true)!; } } } diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs index fc06e779..9c6a3980 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs @@ -1,6 +1,5 @@ -#nullable disable - using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; @@ -33,17 +32,17 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // get field ref - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); + FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef == null || !this.ShouldValidate(fieldRef.DeclaringType)) return false; // skip if not broken - FieldDefinition fieldDefinition = fieldRef.Resolve(); + FieldDefinition? fieldDefinition = fieldRef.Resolve(); if (fieldDefinition?.HasConstant == false) return false; // rewrite if possible - TypeDefinition declaringType = fieldRef.DeclaringType.Resolve(); + TypeDefinition? declaringType = fieldRef.DeclaringType.Resolve(); bool isRead = instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld; return this.TryRewriteToProperty(module, instruction, fieldRef, declaringType, isRead) @@ -56,7 +55,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters *********/ /// Whether references to the given type should be validated. /// The type reference. - private bool ShouldValidate(TypeReference type) + private bool ShouldValidate([NotNullWhen(true)] TypeReference? type) { return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name); } @@ -70,8 +69,8 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters private bool TryRewriteToProperty(ModuleDefinition module, Instruction instruction, FieldReference fieldRef, TypeDefinition declaringType, bool isRead) { // get equivalent property - PropertyDefinition property = declaringType?.Properties.FirstOrDefault(p => p.Name == fieldRef.Name); - MethodDefinition method = isRead ? property?.GetMethod : property?.SetMethod; + PropertyDefinition? property = declaringType?.Properties.FirstOrDefault(p => p.Name == fieldRef.Name); + MethodDefinition? method = isRead ? property?.GetMethod : property?.SetMethod; if (method == null) return false; @@ -86,14 +85,14 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// Try rewriting the field into a matching const field. /// The CIL instruction to rewrite. /// The field definition. - private bool TryRewriteToConstField(Instruction instruction, FieldDefinition field) + private bool TryRewriteToConstField(Instruction instruction, FieldDefinition? field) { // must have been a static field read, and the new field must be const if (instruction.OpCode != OpCodes.Ldsfld || field?.HasConstant != true) return false; // get opcode for value type - Instruction loadInstruction = RewriteHelper.GetLoadValueInstruction(field.Constant); + Instruction? loadInstruction = RewriteHelper.GetLoadValueInstruction(field.Constant); if (loadInstruction == null) return false; diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs index 4860072c..601ecbbc 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs @@ -1,6 +1,5 @@ -#nullable disable - using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; @@ -33,7 +32,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // get method ref - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); if (methodRef == null || !this.ShouldValidate(methodRef.DeclaringType)) return false; @@ -42,13 +41,13 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters return false; // get type - var type = methodRef.DeclaringType.Resolve(); + TypeDefinition? type = methodRef.DeclaringType.Resolve(); if (type == null) return false; // get method definition - MethodDefinition method = null; - foreach (var match in type.Methods.Where(p => p.Name == methodRef.Name)) + MethodDefinition? method = null; + foreach (MethodDefinition match in type.Methods.Where(p => p.Name == methodRef.Name)) { // reference matches initial parameters of definition if (methodRef.Parameters.Count >= match.Parameters.Count || !this.InitialParametersMatch(methodRef, match)) @@ -72,7 +71,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters return false; // SMAPI needs to load the value onto the stack before the method call, but the optional parameter type wasn't recognized // rewrite method reference - foreach (Instruction loadInstruction in loadInstructions) + foreach (Instruction? loadInstruction in loadInstructions) cil.InsertBefore(instruction, loadInstruction); instruction.Operand = module.ImportReference(method); @@ -86,7 +85,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters *********/ /// Whether references to the given type should be validated. /// The type reference. - private bool ShouldValidate(TypeReference type) + private bool ShouldValidate([NotNullWhen(true)] TypeReference? type) { return type != null && this.RewriteReferencesToAssemblies.Contains(type.Scope.Name); } diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs index 00daf337..2e2f6316 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/MethodParentRewriter.cs @@ -1,6 +1,5 @@ -#nullable disable - using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; @@ -28,7 +27,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// The type whose methods to remap. /// The type with methods to map to. /// A brief noun phrase indicating what the instruction finder matches (or null to generate one). - public MethodParentRewriter(string fromType, Type toType, string nounPhrase = null) + public MethodParentRewriter(string fromType, Type toType, string? nounPhrase = null) : base(nounPhrase ?? $"{fromType.Split('.').Last()} methods") { this.FromType = fromType; @@ -39,14 +38,14 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// The type whose methods to remap. /// The type with methods to map to. /// A brief noun phrase indicating what the instruction finder matches (or null to generate one). - public MethodParentRewriter(Type fromType, Type toType, string nounPhrase = null) - : this(fromType.FullName, toType, nounPhrase) { } + public MethodParentRewriter(Type fromType, Type toType, string? nounPhrase = null) + : this(fromType.FullName!, toType, nounPhrase) { } /// public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { // get method ref - MethodReference methodRef = RewriteHelper.AsMethodReference(instruction); + MethodReference? methodRef = RewriteHelper.AsMethodReference(instruction); if (!this.IsMatch(methodRef)) return false; @@ -61,7 +60,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters *********/ /// Get whether a CIL instruction matches. /// The method reference. - private bool IsMatch(MethodReference methodRef) + private bool IsMatch([NotNullWhen(true)] MethodReference? methodRef) { return methodRef != null diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs index bdc4c4f3..a81cb5be 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/TypeReferenceRewriter.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using Mono.Cecil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -19,7 +17,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters private readonly Type ToType; /// Get whether a matched type should be ignored. - private readonly Func ShouldIgnore; + private readonly Func? ShouldIgnore; /********* @@ -29,7 +27,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// The full type name to which to find references. /// The new type to reference. /// Get whether a matched type should be ignored. - public TypeReferenceRewriter(string fromTypeFullName, Type toType, Func shouldIgnore = null) + public TypeReferenceRewriter(string fromTypeFullName, Type toType, Func? shouldIgnore = null) : base($"{fromTypeFullName} type") { this.FromTypeName = fromTypeFullName; diff --git a/src/SMAPI/Framework/ModLoading/Symbols/SymbolReaderProvider.cs b/src/SMAPI/Framework/ModLoading/Symbols/SymbolReaderProvider.cs index 4af7c1e7..0d3aff9f 100644 --- a/src/SMAPI/Framework/ModLoading/Symbols/SymbolReaderProvider.cs +++ b/src/SMAPI/Framework/ModLoading/Symbols/SymbolReaderProvider.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.IO; @@ -38,7 +36,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Symbols /// The assembly file name. public ISymbolReader GetSymbolReader(ModuleDefinition module, string fileName) { - return this.SymbolsByAssemblyPath.TryGetValue(module.Name, out Stream symbolData) + return this.SymbolsByAssemblyPath.TryGetValue(module.Name, out Stream? symbolData) ? new SymbolReader(module, symbolData) : this.BaseProvider.GetSymbolReader(module, fileName); } @@ -48,7 +46,7 @@ namespace StardewModdingAPI.Framework.ModLoading.Symbols /// The loaded symbol file stream. public ISymbolReader GetSymbolReader(ModuleDefinition module, Stream symbolStream) { - return this.SymbolsByAssemblyPath.TryGetValue(module.Name, out Stream symbolData) + return this.SymbolsByAssemblyPath.TryGetValue(module.Name, out Stream? symbolData) ? new SymbolReader(module, symbolData) : this.BaseProvider.GetSymbolReader(module, symbolStream); } diff --git a/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs b/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs index 248c29fc..d81d763e 100644 --- a/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs +++ b/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -18,7 +16,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// TKey in the above example). If all components are equal after substitution, and the /// tokens can all be mapped to the same generic type, the types are considered equal. /// - internal class TypeReferenceComparer : IEqualityComparer + internal class TypeReferenceComparer : IEqualityComparer { /********* ** Public methods @@ -26,7 +24,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// Get whether the specified objects are equal. /// The first object to compare. /// The second object to compare. - public bool Equals(TypeReference a, TypeReference b) + public bool Equals(TypeReference? a, TypeReference? b) { if (a == null || b == null) return a == b; @@ -54,7 +52,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// The second type to compare. private bool HeuristicallyEquals(TypeReference typeA, TypeReference typeB) { - bool HeuristicallyEquals(string typeNameA, string typeNameB, IDictionary tokenMap) + bool HeuristicallyEqualsImpl(string typeNameA, string typeNameB, IDictionary tokenMap) { // analyze type names bool hasTokensA = typeNameA.Contains("!"); @@ -82,14 +80,14 @@ namespace StardewModdingAPI.Framework.ModLoading for (int i = 0; i < symbolsA.Length; i++) { - if (!HeuristicallyEquals(symbolsA[i], symbolsB[i], tokenMap)) + if (!HeuristicallyEqualsImpl(symbolsA[i], symbolsB[i], tokenMap)) return false; } return true; } - return HeuristicallyEquals(typeA.FullName, typeB.FullName, new Dictionary()); + return HeuristicallyEqualsImpl(typeA.FullName, typeB.FullName, new Dictionary()); } /// Map a generic type placeholder (like !0) to its actual type. @@ -99,7 +97,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// Returns the previously-mapped type if applicable, else the . private string MapPlaceholder(string placeholder, string type, IDictionary map) { - if (map.TryGetValue(placeholder, out string result)) + if (map.TryGetValue(placeholder, out string? result)) return result; map[placeholder] = type; diff --git a/src/SMAPI/Framework/ModRegistry.cs b/src/SMAPI/Framework/ModRegistry.cs index cae38637..de16a780 100644 --- a/src/SMAPI/Framework/ModRegistry.cs +++ b/src/SMAPI/Framework/ModRegistry.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics; @@ -42,7 +40,7 @@ namespace StardewModdingAPI.Framework /// The mod assembly. public void TrackAssemblies(IModMetadata metadata, Assembly modAssembly) { - this.ModNamesByAssembly[modAssembly.FullName] = metadata; + this.ModNamesByAssembly[modAssembly.FullName!] = metadata; } /// Get metadata for all loaded mods. @@ -62,7 +60,7 @@ namespace StardewModdingAPI.Framework /// Get metadata for a loaded mod. /// The mod's unique ID. /// Returns the matching mod's metadata, or null if not found. - public IModMetadata Get(string uniqueID) + public IModMetadata? Get(string uniqueID) { // normalize search ID if (string.IsNullOrWhiteSpace(uniqueID)) @@ -76,14 +74,14 @@ namespace StardewModdingAPI.Framework /// Get the mod metadata from one of its assemblies. /// The type to check. /// Returns the mod name, or null if the type isn't part of a known mod. - public IModMetadata GetFrom(Type type) + public IModMetadata? GetFrom(Type? type) { // null if (type == null) return null; // known type - string assemblyName = type.Assembly.FullName; + string assemblyName = type.Assembly.FullName!; if (this.ModNamesByAssembly.ContainsKey(assemblyName)) return this.ModNamesByAssembly[assemblyName]; @@ -93,7 +91,7 @@ namespace StardewModdingAPI.Framework /// Get the friendly name for the closest assembly registered as a source of deprecation warnings. /// Returns the source name, or null if no registered assemblies were found. - public IModMetadata GetFromStack() + public IModMetadata? GetFromStack() { // get stack frames StackTrace stack = new(); @@ -102,8 +100,8 @@ namespace StardewModdingAPI.Framework // search stack for a source assembly foreach (StackFrame frame in frames) { - MethodBase method = frame.GetMethod(); - IModMetadata mod = this.GetFrom(method.ReflectedType); + MethodBase? method = frame.GetMethod(); + IModMetadata? mod = this.GetFrom(method?.ReflectedType); if (mod != null) return mod; } diff --git a/src/SMAPI/Framework/Reflection/ReflectedField.cs b/src/SMAPI/Framework/Reflection/ReflectedField.cs index 921876b9..197a246e 100644 --- a/src/SMAPI/Framework/Reflection/ReflectedField.cs +++ b/src/SMAPI/Framework/Reflection/ReflectedField.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Reflection; @@ -15,8 +13,8 @@ namespace StardewModdingAPI.Framework.Reflection /// The type that has the field. private readonly Type ParentType; - /// The object that has the instance field (if applicable). - private readonly object Parent; + /// The object that has the instance field, or null for a static field. + private readonly object? Parent; /// The display name shown in error messages. private string DisplayName => $"{this.ParentType.FullName}::{this.FieldInfo.Name}"; @@ -34,12 +32,12 @@ namespace StardewModdingAPI.Framework.Reflection *********/ /// Construct an instance. /// The type that has the field. - /// The object that has the instance field (if applicable). + /// The object that has the instance field, or null for a static field. /// The reflection metadata. /// Whether the field is static. /// The or is null. /// The is null for a non-static field, or not null for a static field. - public ReflectedField(Type parentType, object obj, FieldInfo field, bool isStatic) + public ReflectedField(Type parentType, object? obj, FieldInfo field, bool isStatic) { // validate if (parentType == null) @@ -58,11 +56,11 @@ namespace StardewModdingAPI.Framework.Reflection } /// - public TValue GetValue() + public TValue? GetValue() { try { - return (TValue)this.FieldInfo.GetValue(this.Parent); + return (TValue?)this.FieldInfo.GetValue(this.Parent); } catch (InvalidCastException) { @@ -75,7 +73,7 @@ namespace StardewModdingAPI.Framework.Reflection } /// - public void SetValue(TValue value) + public void SetValue(TValue? value) { try { diff --git a/src/SMAPI/Framework/Reflection/ReflectedMethod.cs b/src/SMAPI/Framework/Reflection/ReflectedMethod.cs index 50f89b40..c245e843 100644 --- a/src/SMAPI/Framework/Reflection/ReflectedMethod.cs +++ b/src/SMAPI/Framework/Reflection/ReflectedMethod.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Reflection; @@ -14,8 +12,8 @@ namespace StardewModdingAPI.Framework.Reflection /// The type that has the method. private readonly Type ParentType; - /// The object that has the instance method (if applicable). - private readonly object Parent; + /// The object that has the instance method, or null for a static method. + private readonly object? Parent; /// The display name shown in error messages. private string DisplayName => $"{this.ParentType.FullName}::{this.MethodInfo.Name}"; @@ -33,12 +31,12 @@ namespace StardewModdingAPI.Framework.Reflection *********/ /// Construct an instance. /// The type that has the method. - /// The object that has the instance method(if applicable). + /// The object that has the instance method, or null for a static method. /// The reflection metadata. /// Whether the method is static. /// The or is null. /// The is null for a non-static method, or not null for a static method. - public ReflectedMethod(Type parentType, object obj, MethodInfo method, bool isStatic) + public ReflectedMethod(Type parentType, object? obj, MethodInfo method, bool isStatic) { // validate if (parentType == null) @@ -57,10 +55,10 @@ namespace StardewModdingAPI.Framework.Reflection } /// - public TValue Invoke(params object[] arguments) + public TValue? Invoke(params object?[] arguments) { // invoke method - object result; + object? result; try { result = this.MethodInfo.Invoke(this.Parent, arguments); @@ -77,7 +75,7 @@ namespace StardewModdingAPI.Framework.Reflection // cast return value try { - return (TValue)result; + return (TValue?)result; } catch (InvalidCastException) { @@ -86,7 +84,7 @@ namespace StardewModdingAPI.Framework.Reflection } /// - public void Invoke(params object[] arguments) + public void Invoke(params object?[] arguments) { // invoke method try diff --git a/src/SMAPI/Framework/Reflection/ReflectedProperty.cs b/src/SMAPI/Framework/Reflection/ReflectedProperty.cs index a6d8c75c..638953a3 100644 --- a/src/SMAPI/Framework/Reflection/ReflectedProperty.cs +++ b/src/SMAPI/Framework/Reflection/ReflectedProperty.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Reflection; @@ -16,10 +14,10 @@ namespace StardewModdingAPI.Framework.Reflection private readonly string DisplayName; /// The underlying property getter. - private readonly Func GetMethod; + private readonly Func? GetMethod; /// The underlying property setter. - private readonly Action SetMethod; + private readonly Action? SetMethod; /********* @@ -34,12 +32,12 @@ namespace StardewModdingAPI.Framework.Reflection *********/ /// Construct an instance. /// The type that has the property. - /// The object that has the instance property (if applicable). + /// The object that has the instance property, or null for a static property. /// The reflection metadata. /// Whether the property is static. /// The or is null. /// The is null for a non-static property, or not null for a static property. - public ReflectedProperty(Type parentType, object obj, PropertyInfo property, bool isStatic) + public ReflectedProperty(Type parentType, object? obj, PropertyInfo property, bool isStatic) { // validate input if (parentType == null) @@ -58,13 +56,13 @@ namespace StardewModdingAPI.Framework.Reflection this.PropertyInfo = property; if (this.PropertyInfo.GetMethod != null) - this.GetMethod = (Func)Delegate.CreateDelegate(typeof(Func), obj, this.PropertyInfo.GetMethod); + this.GetMethod = (Func)Delegate.CreateDelegate(typeof(Func), obj, this.PropertyInfo.GetMethod); if (this.PropertyInfo.SetMethod != null) - this.SetMethod = (Action)Delegate.CreateDelegate(typeof(Action), obj, this.PropertyInfo.SetMethod); + this.SetMethod = (Action)Delegate.CreateDelegate(typeof(Action), obj, this.PropertyInfo.SetMethod); } /// - public TValue GetValue() + public TValue? GetValue() { if (this.GetMethod == null) throw new InvalidOperationException($"The {this.DisplayName} property has no get method."); @@ -84,7 +82,7 @@ namespace StardewModdingAPI.Framework.Reflection } /// - public void SetValue(TValue value) + public void SetValue(TValue? value) { if (this.SetMethod == null) throw new InvalidOperationException($"The {this.DisplayName} property has no set method."); diff --git a/src/SMAPI/Framework/Rendering/SDisplayDevice.cs b/src/SMAPI/Framework/Rendering/SDisplayDevice.cs index 8718bcb1..37996b0f 100644 --- a/src/SMAPI/Framework/Rendering/SDisplayDevice.cs +++ b/src/SMAPI/Framework/Rendering/SDisplayDevice.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; @@ -27,7 +25,7 @@ namespace StardewModdingAPI.Framework.Rendering /// The tile to draw. /// The tile position to draw. /// The layer depth at which to draw. - public override void DrawTile(Tile tile, Location location, float layerDepth) + public override void DrawTile(Tile? tile, Location location, float layerDepth) { // identical to XnaDisplayDevice if (tile == null) @@ -58,7 +56,7 @@ namespace StardewModdingAPI.Framework.Rendering /// The tile being drawn. private SpriteEffects GetSpriteEffects(Tile tile) { - return tile.Properties.TryGetValue("@Flip", out PropertyValue propertyValue) && int.TryParse(propertyValue, out int value) + return tile.Properties.TryGetValue("@Flip", out PropertyValue? propertyValue) && int.TryParse(propertyValue, out int value) ? (SpriteEffects)value : SpriteEffects.None; } @@ -67,7 +65,7 @@ namespace StardewModdingAPI.Framework.Rendering /// The tile being drawn. private float GetRotation(Tile tile) { - if (!tile.Properties.TryGetValue("@Rotation", out PropertyValue propertyValue) || !int.TryParse(propertyValue, out int value)) + if (!tile.Properties.TryGetValue("@Rotation", out PropertyValue? propertyValue) || !int.TryParse(propertyValue, out int value)) return 0; value %= 360; diff --git a/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs b/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs index 21edaedd..94b13378 100644 --- a/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs +++ b/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -91,7 +89,7 @@ namespace StardewModdingAPI.Framework.Rendering /// The tile to draw. /// The tile position to draw. /// The layer depth at which to draw. - public virtual void DrawTile(Tile tile, Location location, float layerDepth) + public virtual void DrawTile(Tile? tile, Location location, float layerDepth) { if (tile == null) return; diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index 7ca89eec..4fa7fe7b 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; @@ -48,10 +46,10 @@ namespace StardewModdingAPI.Framework private readonly Action ExitGameImmediately; /// The initial override for . This value is null after initialization. - private SInputState InitialInput; + private SInputState? InitialInput; /// The initial override for . This value is null after initialization. - private SMultiplayer InitialMultiplayer; + private SMultiplayer? InitialMultiplayer; /// Raised when the instance is updating its state (roughly 60 times per second). private readonly Action OnUpdating; @@ -70,7 +68,7 @@ namespace StardewModdingAPI.Framework public Task NewDayTask => Game1._newDayTask; /// Monitors the entire game state for changes. - public WatcherCore Watchers { get; private set; } + public WatcherCore Watchers { get; private set; } = null!; // initialized on first update tick /// A snapshot of the current state. public WatcherSnapshot WatcherSnapshot { get; } = new(); @@ -94,7 +92,7 @@ namespace StardewModdingAPI.Framework /// Construct a content manager to read game content files. /// This must be static because the game accesses it before the constructor is called. [NonInstancedStatic] - public static Func CreateContentManagerImpl; + public static Func? CreateContentManagerImpl; /********* @@ -138,11 +136,10 @@ namespace StardewModdingAPI.Framework /// This is intended for use by and shouldn't be used directly in most cases. internal static SButtonState GetInputState(SButton button) { - SInputState input = Game1.input as SInputState; - if (input == null) + if (Game1.input is not SInputState inputHandler) throw new InvalidOperationException("SMAPI's input state is not in a ready state yet."); - return input.GetState(button); + return inputHandler.GetState(button); } /// @@ -172,13 +169,11 @@ namespace StardewModdingAPI.Framework { base.Initialize(); - // The game resets public static fields after the class is constructed (see - // GameRunner.SetInstanceDefaults), so SMAPI needs to re-override them here. + // The game resets public static fields after the class is constructed (see GameRunner.SetInstanceDefaults), so SMAPI needs to re-override them here. Game1.input = this.InitialInput; Game1.multiplayer = this.InitialMultiplayer; - // The Initial* fields should no longer be used after this point, since mods may - // further override them after initialization. + // The Initial* fields should no longer be used after this point, since mods may further override them after initialization. this.InitialInput = null; this.InitialMultiplayer = null; } @@ -251,6 +246,7 @@ namespace StardewModdingAPI.Framework Context.IsInDrawLoop = false; } +#nullable disable /// Replicate the game's draw logic with some changes for SMAPI. /// A snapshot of the game timing state. /// The render target, if any. @@ -258,6 +254,7 @@ namespace StardewModdingAPI.Framework [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator", Justification = "copied from game code as-is")] [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "copied from game code as-is")] [SuppressMessage("ReSharper", "LocalVariableHidesMember", Justification = "copied from game code as-is")] + [SuppressMessage("ReSharper", "MergeIntoPattern", Justification = "copied from game code as-is")] [SuppressMessage("ReSharper", "PossibleLossOfFraction", Justification = "copied from game code as-is")] [SuppressMessage("ReSharper", "RedundantArgumentDefaultValue", Justification = "copied from game code as-is")] [SuppressMessage("ReSharper", "RedundantBaseQualifier", Justification = "copied from game code as-is")] @@ -265,8 +262,9 @@ namespace StardewModdingAPI.Framework [SuppressMessage("ReSharper", "RedundantExplicitNullableCreation", Justification = "copied from game code as-is")] [SuppressMessage("ReSharper", "RedundantTypeArgumentsOfMethod", Justification = "copied from game code as-is")] [SuppressMessage("ReSharper", "IdentifierTypo", Justification = "copied from game code as-is")] - [SuppressMessage("SMAPI.CommonErrors", "AvoidNetField", Justification = "copied from game code as-is")] + [SuppressMessage("ReSharper", "MergeIntoPattern", Justification = "copied from game code as-is")] [SuppressMessage("SMAPI.CommonErrors", "AvoidImplicitNetFieldCast", Justification = "copied from game code as-is")] + [SuppressMessage("SMAPI.CommonErrors", "AvoidNetField", Justification = "copied from game code as-is")] private void DrawImpl(GameTime gameTime, RenderTarget2D target_screen) { var events = this.Events; @@ -952,5 +950,6 @@ namespace StardewModdingAPI.Framework this.drawOverlays(Game1.spriteBatch); Game1.PopUIMode(); } +#nullable enable } } diff --git a/src/SMAPI/Framework/SModHooks.cs b/src/SMAPI/Framework/SModHooks.cs index 7941e102..a7736c8b 100644 --- a/src/SMAPI/Framework/SModHooks.cs +++ b/src/SMAPI/Framework/SModHooks.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Threading.Tasks; using StardewValley; @@ -35,7 +33,7 @@ namespace StardewModdingAPI.Framework /// The vanilla logic. public override void OnGame1_NewDayAfterFade(Action action) { - this.BeforeNewDayAfterFade?.Invoke(); + this.BeforeNewDayAfterFade(); action(); } diff --git a/src/SMAPI/Framework/SnapshotDiff.cs b/src/SMAPI/Framework/SnapshotDiff.cs index eb2aebe1..d659d2b4 100644 --- a/src/SMAPI/Framework/SnapshotDiff.cs +++ b/src/SMAPI/Framework/SnapshotDiff.cs @@ -1,5 +1,3 @@ -#nullable disable - using StardewModdingAPI.Framework.StateTracking; namespace StardewModdingAPI.Framework @@ -15,10 +13,10 @@ namespace StardewModdingAPI.Framework public bool IsChanged { get; private set; } /// The previous value. - public T Old { get; private set; } + public T? Old { get; private set; } /// The current value. - public T New { get; private set; } + public T? New { get; private set; } /********* diff --git a/src/SMAPI/Framework/SnapshotItemListDiff.cs b/src/SMAPI/Framework/SnapshotItemListDiff.cs index 97942783..76060db2 100644 --- a/src/SMAPI/Framework/SnapshotItemListDiff.cs +++ b/src/SMAPI/Framework/SnapshotItemListDiff.cs @@ -1,6 +1,5 @@ -#nullable disable - using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewModdingAPI.Events; using StardewValley; @@ -48,7 +47,7 @@ namespace StardewModdingAPI.Framework /// The items with their previous stack sizes. /// The inventory changes, or null if nothing changed. /// Returns whether anything changed. - public static bool TryGetChanges(ISet added, ISet removed, IDictionary stackSizes, out SnapshotItemListDiff changes) + public static bool TryGetChanges(ISet added, ISet removed, IDictionary stackSizes, [NotNullWhen(true)] out SnapshotItemListDiff? changes) { KeyValuePair[] sizesChanged = stackSizes.Where(p => p.Key.Stack != p.Value).ToArray(); if (sizesChanged.Any() || added.Any() || removed.Any()) diff --git a/src/SMAPI/Framework/SnapshotListDiff.cs b/src/SMAPI/Framework/SnapshotListDiff.cs index 1d585c15..90066af1 100644 --- a/src/SMAPI/Framework/SnapshotListDiff.cs +++ b/src/SMAPI/Framework/SnapshotListDiff.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using StardewModdingAPI.Framework.StateTracking; @@ -39,7 +37,7 @@ namespace StardewModdingAPI.Framework /// Whether the value changed since the last update. /// The removed values. /// The added values. - public void Update(bool isChanged, IEnumerable removed, IEnumerable added) + public void Update(bool isChanged, IEnumerable? removed, IEnumerable? added) { this.IsChanged = isChanged; diff --git a/src/SMAPI/Framework/StateTracking/ChestTracker.cs b/src/SMAPI/Framework/StateTracking/ChestTracker.cs index 28335200..c33a7498 100644 --- a/src/SMAPI/Framework/StateTracking/ChestTracker.cs +++ b/src/SMAPI/Framework/StateTracking/ChestTracker.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewModdingAPI.Framework.StateTracking.Comparers; using StardewModdingAPI.Framework.StateTracking.FieldWatchers; @@ -86,7 +85,7 @@ namespace StardewModdingAPI.Framework.StateTracking /// Get the inventory changes since the last update, if anything changed. /// The inventory changes, or null if nothing changed. /// Returns whether anything changed. - public bool TryGetInventoryChanges(out SnapshotItemListDiff changes) + public bool TryGetInventoryChanges([NotNullWhen(true)] out SnapshotItemListDiff? changes) { return SnapshotItemListDiff.TryGetChanges(added: this.Added, removed: this.Removed, stackSizes: this.StackSizes, out changes); } diff --git a/src/SMAPI/Framework/StateTracking/Comparers/EquatableComparer.cs b/src/SMAPI/Framework/StateTracking/Comparers/EquatableComparer.cs index 987e1820..9d8559b4 100644 --- a/src/SMAPI/Framework/StateTracking/Comparers/EquatableComparer.cs +++ b/src/SMAPI/Framework/StateTracking/Comparers/EquatableComparer.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -17,7 +15,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Comparers /// true if the specified objects are equal; otherwise, false. /// The first object to compare. /// The second object to compare. - public bool Equals(T x, T y) + public bool Equals(T? x, T? y) { if (x == null) return y == null; diff --git a/src/SMAPI/Framework/StateTracking/Comparers/GenericEqualsComparer.cs b/src/SMAPI/Framework/StateTracking/Comparers/GenericEqualsComparer.cs index f6b04583..41b17e10 100644 --- a/src/SMAPI/Framework/StateTracking/Comparers/GenericEqualsComparer.cs +++ b/src/SMAPI/Framework/StateTracking/Comparers/GenericEqualsComparer.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -16,7 +14,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Comparers /// true if the specified objects are equal; otherwise, false. /// The first object to compare. /// The second object to compare. - public bool Equals(T x, T y) + public bool Equals(T? x, T? y) { if (x == null) return y == null; diff --git a/src/SMAPI/Framework/StateTracking/Comparers/ObjectReferenceComparer.cs b/src/SMAPI/Framework/StateTracking/Comparers/ObjectReferenceComparer.cs index 8d3a7eb9..e6ece854 100644 --- a/src/SMAPI/Framework/StateTracking/Comparers/ObjectReferenceComparer.cs +++ b/src/SMAPI/Framework/StateTracking/Comparers/ObjectReferenceComparer.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -16,7 +14,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Comparers /// true if the specified objects are equal; otherwise, false. /// The first object to compare. /// The second object to compare. - public bool Equals(T x, T y) + public bool Equals(T? x, T? y) { return object.ReferenceEquals(x, y); } diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs index 0d7f2ad2..f55e4cea 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/NetDictionaryWatcher.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using Netcode; @@ -12,6 +10,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers /// The serializable dictionary type that can store the keys and values. /// The net field instance type. internal class NetDictionaryWatcher : BaseDisposableWatcher, IDictionaryWatcher + where TKey : notnull where TField : class, INetObject, new() where TSerialDict : IDictionary, new() where TSelf : NetDictionary diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs index 82e5387e..97aedca8 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; @@ -81,7 +79,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers /// A callback invoked when an entry is added or removed from the collection. /// The event sender. /// The event arguments. - private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Reset) { @@ -90,8 +88,8 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers } else { - TValue[] added = e.NewItems?.Cast().ToArray(); - TValue[] removed = e.OldItems?.Cast().ToArray(); + TValue[]? added = e.NewItems?.Cast().ToArray(); + TValue[]? removed = e.OldItems?.Cast().ToArray(); if (removed != null) { diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs index 0b99914c..c4a4d0b9 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/WatcherFactory.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -20,7 +18,8 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers /// Get a watcher which compares values using their method. This method should only be used when won't work, since this doesn't validate whether they're comparable. /// The value type. /// Get the current value. - public static IValueWatcher ForGenericEquality(Func getValue) where T : struct + public static IValueWatcher ForGenericEquality(Func getValue) + where T : struct { return new ComparableWatcher(getValue, new GenericEqualsComparer()); } @@ -28,7 +27,8 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers /// Get a watcher for an value. /// The value type. /// Get the current value. - public static IValueWatcher ForEquatable(Func getValue) where T : IEquatable + public static IValueWatcher ForEquatable(Func getValue) + where T : IEquatable { return new ComparableWatcher(getValue, new EquatableComparer()); } @@ -79,7 +79,8 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers /// Get a watcher for a net collection. /// The value type. /// The net collection. - public static ICollectionWatcher ForNetCollection(NetCollection collection) where T : class, INetObject + public static ICollectionWatcher ForNetCollection(NetCollection collection) + where T : class, INetObject { return new NetCollectionWatcher(collection); } @@ -87,7 +88,8 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers /// Get a watcher for a net list. /// The value type. /// The net list. - public static ICollectionWatcher ForNetList(NetList> collection) where T : class, INetObject + public static ICollectionWatcher ForNetList(NetList> collection) + where T : class, INetObject { return new NetListWatcher(collection); } @@ -100,6 +102,7 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers /// The net field instance type. /// The net field. public static NetDictionaryWatcher ForNetDictionary(NetDictionary field) + where TKey : notnull where TField : class, INetObject, new() where TSerialDict : IDictionary, new() where TSelf : NetDictionary diff --git a/src/SMAPI/Framework/StateTracking/LocationTracker.cs b/src/SMAPI/Framework/StateTracking/LocationTracker.cs index 9c2ff7f0..ff72a19b 100644 --- a/src/SMAPI/Framework/StateTracking/LocationTracker.cs +++ b/src/SMAPI/Framework/StateTracking/LocationTracker.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -132,20 +130,20 @@ namespace StardewModdingAPI.Framework.StateTracking private void UpdateChestWatcherList(IEnumerable> added, IEnumerable> removed) { // remove unused watchers - foreach (KeyValuePair pair in removed) + foreach ((Vector2 tile, SObject? obj) in removed) { - if (pair.Value is Chest && this.ChestWatchers.TryGetValue(pair.Key, out ChestTracker watcher)) + if (obj is Chest && this.ChestWatchers.TryGetValue(tile, out ChestTracker? watcher)) { watcher.Dispose(); - this.ChestWatchers.Remove(pair.Key); + this.ChestWatchers.Remove(tile); } } // add new watchers - foreach (KeyValuePair pair in added) + foreach ((Vector2 tile, SObject? obj) in added) { - if (pair.Value is Chest chest && !this.ChestWatchers.ContainsKey(pair.Key)) - this.ChestWatchers.Add(pair.Key, new ChestTracker(chest)); + if (obj is Chest chest && !this.ChestWatchers.ContainsKey(tile)) + this.ChestWatchers.Add(tile, new ChestTracker(chest)); } } } diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs index 3d13f92b..0d0469d7 100644 --- a/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs +++ b/src/SMAPI/Framework/StateTracking/Snapshots/LocationSnapshot.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using Microsoft.Xna.Framework; using StardewValley; @@ -70,7 +68,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots this.ChestItems.Clear(); foreach (ChestTracker tracker in watcher.ChestWatchers.Values) { - if (tracker.TryGetInventoryChanges(out SnapshotItemListDiff changes)) + if (tracker.TryGetInventoryChanges(out SnapshotItemListDiff? changes)) this.ChestItems[tracker.Chest] = changes; } } diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/WatcherSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/WatcherSnapshot.cs index 1d43ef26..27a891de 100644 --- a/src/SMAPI/Framework/StateTracking/Snapshots/WatcherSnapshot.cs +++ b/src/SMAPI/Framework/StateTracking/Snapshots/WatcherSnapshot.cs @@ -1,5 +1,3 @@ -#nullable disable - using Microsoft.Xna.Framework; using StardewValley; using StardewValley.Menus; @@ -16,7 +14,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots public SnapshotDiff WindowSize { get; } = new(); /// Tracks changes to the current player. - public PlayerSnapshot CurrentPlayer { get; private set; } + public PlayerSnapshot? CurrentPlayer { get; private set; } /// Tracks changes to the time of day (in 24-hour military format). public SnapshotDiff Time { get; } = new(); @@ -56,7 +54,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots // update snapshots this.WindowSize.Update(watchers.WindowSizeWatcher); this.Locale.Update(watchers.LocaleWatcher); - this.CurrentPlayer?.Update(watchers.CurrentPlayerTracker); + this.CurrentPlayer?.Update(watchers.CurrentPlayerTracker!); this.Time.Update(watchers.TimeWatcher); this.SaveID.Update(watchers.SaveIdWatcher); this.Locations.Update(watchers.LocationsWatcher); diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/WorldLocationsSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/WorldLocationsSnapshot.cs index 88aac0df..59f94942 100644 --- a/src/SMAPI/Framework/StateTracking/Snapshots/WorldLocationsSnapshot.cs +++ b/src/SMAPI/Framework/StateTracking/Snapshots/WorldLocationsSnapshot.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using System.Linq; using StardewModdingAPI.Framework.StateTracking.Comparers; @@ -44,7 +42,7 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots // update locations foreach (LocationTracker locationWatcher in watcher.Locations) { - if (!this.LocationsDict.TryGetValue(locationWatcher.Location, out LocationSnapshot snapshot)) + if (!this.LocationsDict.TryGetValue(locationWatcher.Location, out LocationSnapshot? snapshot)) this.LocationsDict[locationWatcher.Location] = snapshot = new LocationSnapshot(locationWatcher.Location); snapshot.Update(locationWatcher); diff --git a/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs b/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs index ab02d7d5..817a6011 100644 --- a/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs +++ b/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -27,10 +25,10 @@ namespace StardewModdingAPI.Framework.StateTracking private readonly ICollectionWatcher VolcanoLocationListWatcher; /// A lookup of the tracked locations. - private IDictionary LocationDict { get; } = new Dictionary(new ObjectReferenceComparer()); + private Dictionary LocationDict { get; } = new(new ObjectReferenceComparer()); /// A lookup of registered buildings and their indoor location. - private readonly IDictionary BuildingIndoors = new Dictionary(new ObjectReferenceComparer()); + private readonly Dictionary BuildingIndoors = new(new ObjectReferenceComparer()); /********* @@ -101,10 +99,9 @@ namespace StardewModdingAPI.Framework.StateTracking } // detect building interiors changed (e.g. construction completed) - foreach (KeyValuePair pair in this.BuildingIndoors.Where(p => !object.Equals(p.Key.indoors.Value, p.Value))) + foreach ((Building building, GameLocation? oldIndoors) in this.BuildingIndoors.Where(p => !object.Equals(p.Key.indoors.Value, p.Value))) { - GameLocation oldIndoors = pair.Value; - GameLocation newIndoors = pair.Key.indoors.Value; + GameLocation? newIndoors = building.indoors.Value; if (oldIndoors != null) this.Added.Add(oldIndoors); @@ -189,19 +186,19 @@ namespace StardewModdingAPI.Framework.StateTracking ****/ /// Add the given building. /// The building to add. - public void Add(Building building) + public void Add(Building? building) { if (building == null) return; - GameLocation indoors = building.indoors.Value; + GameLocation? indoors = building.indoors.Value; this.BuildingIndoors[building] = indoors; this.Add(indoors); } /// Add the given location. /// The location to add. - public void Add(GameLocation location) + public void Add(GameLocation? location) { if (location == null) return; @@ -220,7 +217,7 @@ namespace StardewModdingAPI.Framework.StateTracking /// Remove the given building. /// The building to remove. - public void Remove(Building building) + public void Remove(Building? building) { if (building == null) return; @@ -231,12 +228,12 @@ namespace StardewModdingAPI.Framework.StateTracking /// Remove the given location. /// The location to remove. - public void Remove(GameLocation location) + public void Remove(GameLocation? location) { if (location == null) return; - if (this.LocationDict.TryGetValue(location, out LocationTracker watcher)) + if (this.LocationDict.TryGetValue(location, out LocationTracker? watcher)) { // track change this.Removed.Add(location); diff --git a/src/SMAPI/Framework/Translator.cs b/src/SMAPI/Framework/Translator.cs index 144b043c..794e3cde 100644 --- a/src/SMAPI/Framework/Translator.cs +++ b/src/SMAPI/Framework/Translator.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewValley; @@ -23,7 +22,7 @@ namespace StardewModdingAPI.Framework /********* ** Accessors *********/ - /// The current locale. + /// The current locale code like fr-FR, or an empty string for English. public string Locale { get; private set; } /// The game's current language code. @@ -39,9 +38,10 @@ namespace StardewModdingAPI.Framework this.SetLocale(string.Empty, LocalizedContentManager.LanguageCode.en); } - /// Set the current locale and precache translations. + /// Set the current locale and pre-cache translations. /// The current locale. /// The game's current language code. + [MemberNotNull(nameof(Translator.ForLocale), nameof(Translator.Locale))] public void SetLocale(string locale, LocalizedContentManager.LanguageCode localeEnum) { this.Locale = locale.ToLower().Trim(); @@ -50,7 +50,7 @@ namespace StardewModdingAPI.Framework this.ForLocale = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (string key in this.GetAllKeysRaw()) { - string text = this.GetRaw(key, locale, withFallback: true); + string? text = this.GetRaw(key, locale, withFallback: true); this.ForLocale.Add(key, new Translation(this.Locale, key, text)); } } @@ -65,7 +65,7 @@ namespace StardewModdingAPI.Framework /// The translation key. public Translation Get(string key) { - this.ForLocale.TryGetValue(key, out Translation translation); + this.ForLocale.TryGetValue(key, out Translation? translation); return translation ?? new Translation(this.Locale, key, null); } @@ -87,7 +87,7 @@ namespace StardewModdingAPI.Framework foreach (var localeSet in this.All) { string locale = localeSet.Key; - string text = this.GetRaw(key, locale, withFallback); + string? text = this.GetRaw(key, locale, withFallback); if (text != null) translations[locale] = new Translation(locale, key, text); @@ -128,13 +128,13 @@ namespace StardewModdingAPI.Framework /// The translation key. /// The locale to get. /// Whether to add duplicate translations for locale fallback. For example, if a translation is defined in default.json but not fr.json, setting this to true will add a fr entry which duplicates the default text. - private string GetRaw(string key, string locale, bool withFallback) + private string? GetRaw(string key, string locale, bool withFallback) { foreach (string next in this.GetRelevantLocales(locale)) { - string translation = null; + string? translation = null; bool hasTranslation = - this.All.TryGetValue(next, out IDictionary translations) + this.All.TryGetValue(next, out IDictionary? translations) && translations.TryGetValue(key, out translation); if (hasTranslation) diff --git a/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs b/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs index 94ce0069..20d206e2 100644 --- a/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs +++ b/src/SMAPI/Framework/Utilities/TickCacheDictionary.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; @@ -9,6 +7,7 @@ namespace StardewModdingAPI.Framework.Utilities /// The dictionary key type. /// The dictionary value type. internal class TickCacheDictionary + where TKey : notnull { /********* ** Fields @@ -36,7 +35,7 @@ namespace StardewModdingAPI.Framework.Utilities } // fetch value - if (!this.Cache.TryGetValue(cacheKey, out TValue cached)) + if (!this.Cache.TryGetValue(cacheKey, out TValue? cached)) this.Cache[cacheKey] = cached = get(); return cached; } diff --git a/src/SMAPI/Framework/WatcherCore.cs b/src/SMAPI/Framework/WatcherCore.cs index bd8d3367..5e20ac7b 100644 --- a/src/SMAPI/Framework/WatcherCore.cs +++ b/src/SMAPI/Framework/WatcherCore.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using System.Collections.ObjectModel; using Microsoft.Xna.Framework; @@ -29,7 +27,7 @@ namespace StardewModdingAPI.Framework public readonly IValueWatcher WindowSizeWatcher; /// Tracks changes to the current player. - public PlayerTracker CurrentPlayerTracker; + public PlayerTracker? CurrentPlayerTracker; /// Tracks changes to the time of day (in 24-hour military format). public readonly IValueWatcher TimeWatcher; diff --git a/src/SMAPI/IAssetData.cs b/src/SMAPI/IAssetData.cs index f07340e4..1ec09d87 100644 --- a/src/SMAPI/IAssetData.cs +++ b/src/SMAPI/IAssetData.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI @@ -7,6 +5,7 @@ namespace StardewModdingAPI /// Generic metadata and methods for a content asset being loaded. /// The expected data type. public interface IAssetData : IAssetInfo + where TValue : notnull { /********* ** Accessors diff --git a/src/SMAPI/IAssetInfo.cs b/src/SMAPI/IAssetInfo.cs index 5b4ac479..4d651a72 100644 --- a/src/SMAPI/IAssetInfo.cs +++ b/src/SMAPI/IAssetInfo.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI @@ -11,7 +9,7 @@ namespace StardewModdingAPI ** Accessors *********/ /// The content's locale code, if the content is localized. - string Locale { get; } + string? Locale { get; } /// The asset name being read. /// NOTE: when reading this field from an or implementation, it's always equivalent to for backwards compatibility. diff --git a/src/SMAPI/IAssetName.cs b/src/SMAPI/IAssetName.cs index 22f5c6b7..71b6ed6b 100644 --- a/src/SMAPI/IAssetName.cs +++ b/src/SMAPI/IAssetName.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewValley; @@ -18,7 +16,7 @@ namespace StardewModdingAPI string BaseName { get; } /// The locale code specified in the , if it's a valid code recognized by the game content. - string LocaleCode { get; } + string? LocaleCode { get; } /// The language code matching the , if applicable. LocalizedContentManager.LanguageCode? LanguageCode { get; } @@ -30,23 +28,23 @@ namespace StardewModdingAPI /// Get whether the given asset name is equivalent, ignoring capitalization and formatting. /// The asset name to compare this instance to. /// Whether to compare the given name with the (if true) or (if false). This has no effect on any locale included in the given . - bool IsEquivalentTo(string assetName, bool useBaseName = false); + bool IsEquivalentTo(string? assetName, bool useBaseName = false); /// Get whether the given asset name is equivalent, ignoring capitalization and formatting. /// The asset name to compare this instance to. /// Whether to compare the given name with the (if true) or (if false). - bool IsEquivalentTo(IAssetName assetName, bool useBaseName = false); + bool IsEquivalentTo(IAssetName? assetName, bool useBaseName = false); /// Get whether the asset name starts with the given value, ignoring capitalization and formatting. This can be used with a trailing slash to test for an asset folder, like Data/. /// The prefix to match. /// Whether to match if the prefix occurs mid-word, so Data/AchievementsToIgnore matches prefix Data/Achievements. If this is false, the prefix only matches if the asset name starts with the prefix followed by a non-alphanumeric character (including ., /, or \\) or the end of string. /// Whether to match the prefix if there's a subfolder path after it, so Data/Achievements/Example matches prefix Data/Achievements. If this is false, the prefix only matches if the asset name has no / or \\ characters after the prefix. - bool StartsWith(string prefix, bool allowPartialWord = true, bool allowSubfolder = true); + bool StartsWith(string? prefix, bool allowPartialWord = true, bool allowSubfolder = true); /// Get whether the asset is directly within the given asset path. /// For example, Characters/Dialogue/Abigail is directly under Characters/Dialogue but not Characters or Characters/Dialogue/Ab. To allow sub-paths, use instead. /// The asset path to check. This doesn't need a trailing slash. - bool IsDirectlyUnderPath(string assetFolder); + bool IsDirectlyUnderPath(string? assetFolder); /// Get an asset name representing the without locale. internal IAssetName GetBaseAssetName(); diff --git a/src/SMAPI/IContentHelper.cs b/src/SMAPI/IContentHelper.cs index 0ad209ab..2cd0c1fc 100644 --- a/src/SMAPI/IContentHelper.cs +++ b/src/SMAPI/IContentHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics.Contracts; @@ -42,12 +40,14 @@ namespace StardewModdingAPI /// Where to search for a matching content asset. /// The is empty or contains invalid characters. /// The content asset couldn't be loaded (e.g. because it doesn't exist). - T Load(string key, ContentSource source = ContentSource.ModFolder); + T Load(string key, ContentSource source = ContentSource.ModFolder) + where T : notnull; /// Normalize an asset name so it's consistent with those generated by the game. This is mainly useful for string comparisons like on generated asset names, and isn't necessary when passing asset names into other content helper methods. /// The asset key. + /// The asset key is empty or contains invalid characters. [Pure] - string NormalizeAssetName(string assetName); + string NormalizeAssetName(string? assetName); /// Get the underlying key in the game's content cache for an asset. This can be used to load custom map tilesheets, but should be avoided when you can use the content API instead. This does not validate whether the asset exists. /// The asset key to fetch (if the is ), or the local path to a content file relative to the mod folder. @@ -64,7 +64,8 @@ namespace StardewModdingAPI /// Remove all assets of the given type from the cache so they're reloaded on the next request. This can be a very expensive operation and should only be used in very specific cases. This will reload core game assets if needed, but references to the former assets will still show the previous content. /// The asset type to remove from the cache. /// Returns whether any assets were invalidated. - bool InvalidateCache(); + bool InvalidateCache() + where T : notnull; /// Remove matching assets from the content cache so they're reloaded on the next request. This will reload core game assets if needed, but references to the former asset will still show the previous content. /// A predicate matching the assets to invalidate. @@ -75,6 +76,7 @@ namespace StardewModdingAPI /// The data type. /// The asset data. /// The asset name. This is only used for tracking purposes and has no effect on the patch helper. - IAssetData GetPatchHelper(T data, string assetName = null); + IAssetData GetPatchHelper(T data, string? assetName = null) + where T : notnull; } } diff --git a/src/SMAPI/IContentPack.cs b/src/SMAPI/IContentPack.cs index f853e2b4..1215fe0b 100644 --- a/src/SMAPI/IContentPack.cs +++ b/src/SMAPI/IContentPack.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; @@ -38,14 +36,16 @@ namespace StardewModdingAPI /// The relative file path within the content pack (case-insensitive). /// Returns the deserialized model, or null if the file doesn't exist or is empty. /// The is not relative or contains directory climbing (../). - TModel ReadJsonFile(string path) where TModel : class; + TModel? ReadJsonFile(string path) + where TModel : class; /// Save data to a JSON file in the content pack's folder. /// The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types. /// The relative file path within the content pack (case-insensitive). /// The arbitrary data to save. /// The is not relative or contains directory climbing (../). - void WriteJsonFile(string path, TModel data) where TModel : class; + void WriteJsonFile(string path, TModel data) + where TModel : class; /// Load content from the content pack folder (if not already cached), and return it. When loading a .png file, this must be called outside the game's draw loop. /// The expected data type. The main supported types are , , and dictionaries; other types may be supported by the game's content pipeline. @@ -53,7 +53,8 @@ namespace StardewModdingAPI /// The is empty or contains invalid characters. /// The content asset couldn't be loaded (e.g. because it doesn't exist). [Obsolete($"Use {nameof(IContentPack.ModContent)}.{nameof(IModContentHelper.Load)} instead. This method will be removed in SMAPI 4.0.0.")] - T LoadAsset(string key); + T LoadAsset(string key) + where T : notnull; /// Get the underlying key in the game's content cache for an asset. This can be used to load custom map tilesheets, but should be avoided when you can use the content API instead. This does not validate whether the asset exists. /// The relative file path within the content pack (case-insensitive). diff --git a/src/SMAPI/IDataHelper.cs b/src/SMAPI/IDataHelper.cs index 4c96367b..7ddf851e 100644 --- a/src/SMAPI/IDataHelper.cs +++ b/src/SMAPI/IDataHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI @@ -18,14 +16,16 @@ namespace StardewModdingAPI /// The file path relative to the mod folder. /// Returns the deserialized model, or null if the file doesn't exist or is empty. /// The is not relative or contains directory climbing (../). - TModel ReadJsonFile(string path) where TModel : class; + TModel? ReadJsonFile(string path) + where TModel : class; /// Save data to a JSON file in the mod's folder. /// The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types. /// The file path relative to the mod folder. /// The arbitrary data to save, or null to delete the file. /// The is not relative or contains directory climbing (../). - void WriteJsonFile(string path, TModel data) where TModel : class; + void WriteJsonFile(string path, TModel? data) + where TModel : class; /**** ** Save file @@ -35,14 +35,16 @@ namespace StardewModdingAPI /// The unique key identifying the data. /// Returns the parsed data, or null if the entry doesn't exist or is empty. /// The player hasn't loaded a save file yet or isn't the main player. - TModel ReadSaveData(string key) where TModel : class; + TModel? ReadSaveData(string key) + where TModel : class; /// Save arbitrary data to the current save slot. This is only possible if a save has been loaded, and the data will be lost if the player exits without saving the current day. /// The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types. /// The unique key identifying the data. /// The arbitrary data to save, or null to remove the entry. /// The player hasn't loaded a save file yet or isn't the main player. - void WriteSaveData(string key, TModel data) where TModel : class; + void WriteSaveData(string key, TModel? data) + where TModel : class; /**** @@ -52,12 +54,14 @@ namespace StardewModdingAPI /// The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types. /// The unique key identifying the data. /// Returns the parsed data, or null if the entry doesn't exist or is empty. - TModel ReadGlobalData(string key) where TModel : class; + TModel? ReadGlobalData(string key) + where TModel : class; /// Save arbitrary data to the local computer, synchronised by GOG/Steam if applicable. /// The model type. This should be a plain class that has public properties for the data you want. The properties can be complex types. /// The unique key identifying the data. /// The arbitrary data to save, or null to delete the file. - void WriteGlobalData(string key, TModel data) where TModel : class; + void WriteGlobalData(string key, TModel? data) + where TModel : class; } } diff --git a/src/SMAPI/IGameContentHelper.cs b/src/SMAPI/IGameContentHelper.cs index 4b967993..d40d0c82 100644 --- a/src/SMAPI/IGameContentHelper.cs +++ b/src/SMAPI/IGameContentHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; @@ -35,14 +33,16 @@ namespace StardewModdingAPI /// The asset name to load. /// The is empty or contains invalid characters. /// The content asset couldn't be loaded (e.g. because it doesn't exist). - T Load(string assetName); + T Load(string assetName) + where T : notnull; /// Load content from the game folder or mod folder (if not already cached), and return it. When loading a .png file, this must be called outside the game's draw loop. /// The expected data type. The main supported types are , , dictionaries, and lists; other types may be supported by the game's content pipeline. /// The asset name to load. /// The is empty or contains invalid characters. /// The content asset couldn't be loaded (e.g. because it doesn't exist). - T Load(IAssetName assetName); + T Load(IAssetName assetName) + where T : notnull; /// Remove an asset from the content cache so it's reloaded on the next request. This will reload core game assets if needed, but references to the former asset will still show the previous content. /// The asset key to invalidate in the content folder. @@ -59,7 +59,8 @@ namespace StardewModdingAPI /// Remove all assets of the given type from the cache so they're reloaded on the next request. This can be a very expensive operation and should only be used in very specific cases. This will reload core game assets if needed, but references to the former assets will still show the previous content. /// The asset type to remove from the cache. /// Returns whether any assets were invalidated. - bool InvalidateCache(); + bool InvalidateCache() + where T : notnull; /// Remove matching assets from the content cache so they're reloaded on the next request. This will reload core game assets if needed, but references to the former asset will still show the previous content. /// A predicate matching the assets to invalidate. @@ -70,6 +71,7 @@ namespace StardewModdingAPI /// The data type. /// The asset data. /// The asset name. This is only used for tracking purposes and has no effect on the patch helper. - IAssetData GetPatchHelper(T data, string assetName = null); + IAssetData GetPatchHelper(T data, string? assetName = null) + where T : notnull; } } diff --git a/src/SMAPI/IMod.cs b/src/SMAPI/IMod.cs index 0de4961e..b81ba0e3 100644 --- a/src/SMAPI/IMod.cs +++ b/src/SMAPI/IMod.cs @@ -1,5 +1,3 @@ -#nullable disable - namespace StardewModdingAPI { /// The implementation for a Stardew Valley mod. @@ -26,6 +24,6 @@ namespace StardewModdingAPI void Entry(IModHelper helper); /// Get an API that other mods can access. This is always called after . - object GetApi(); + object? GetApi(); } } diff --git a/src/SMAPI/IModContentHelper.cs b/src/SMAPI/IModContentHelper.cs index 815d6848..f1f6ce94 100644 --- a/src/SMAPI/IModContentHelper.cs +++ b/src/SMAPI/IModContentHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; @@ -18,7 +16,8 @@ namespace StardewModdingAPI /// The local path to a content file relative to the mod folder. /// The is empty or contains invalid characters. /// The content asset couldn't be loaded (e.g. because it doesn't exist). - T Load(string relativePath); + T Load(string relativePath) + where T : notnull; /// Get the internal asset name which allows loading a mod file through any of the game's content managers. This can be used when passing asset names directly to the game (e.g. for map tilesheets), but should be avoided if you can use instead. This does not validate whether the asset exists. /// The local path to a content file relative to the mod folder. @@ -29,6 +28,7 @@ namespace StardewModdingAPI /// The data type. /// The asset data. /// The local path to the content file being edited relative to the mod folder. This is only used for tracking purposes and has no effect on the patch helper. - IAssetData GetPatchHelper(T data, string relativePath = null); + IAssetData GetPatchHelper(T data, string? relativePath = null) + where T : notnull; } } diff --git a/src/SMAPI/IModRegistry.cs b/src/SMAPI/IModRegistry.cs index 9cab08a1..cf60bc29 100644 --- a/src/SMAPI/IModRegistry.cs +++ b/src/SMAPI/IModRegistry.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; namespace StardewModdingAPI @@ -13,7 +11,7 @@ namespace StardewModdingAPI /// Get metadata for a loaded mod. /// The mod's unique ID. /// Returns the matching mod's metadata, or null if not found. - IModInfo Get(string uniqueID); + IModInfo? Get(string uniqueID); /// Get whether a mod has been loaded. /// The mod's unique ID. @@ -21,11 +19,12 @@ namespace StardewModdingAPI /// Get the API provided by a mod, or null if it has none. This signature requires using the API to access the API's properties and methods. /// The mod's unique ID. - object GetApi(string uniqueID); + object? GetApi(string uniqueID); /// Get the API provided by a mod, mapped to a given interface which specifies the expected properties and methods. If the mod has no API or it's not compatible with the given interface, get null. /// The interface which matches the properties and methods you intend to access. /// The mod's unique ID. - TInterface GetApi(string uniqueID) where TInterface : class; + TInterface? GetApi(string uniqueID) + where TInterface : class; } } diff --git a/src/SMAPI/IMultiplayerHelper.cs b/src/SMAPI/IMultiplayerHelper.cs index 77a0f3f4..bb851410 100644 --- a/src/SMAPI/IMultiplayerHelper.cs +++ b/src/SMAPI/IMultiplayerHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using StardewValley; @@ -18,7 +16,7 @@ namespace StardewModdingAPI /// Get a connected player. /// The player's unique ID. /// Returns the connected player, or null if no such player is connected. - IMultiplayerPeer GetConnectedPlayer(long id); + IMultiplayerPeer? GetConnectedPlayer(long id); /// Get all connected players. IEnumerable GetConnectedPlayers(); @@ -30,6 +28,6 @@ namespace StardewModdingAPI /// The mod IDs which should receive the message on the destination computers, or null for all mods. Specifying mod IDs is recommended to improve performance, unless it's a general-purpose broadcast. /// The values for the players who should receive the message, or null for all players. If you don't need to broadcast to all players, specifying player IDs is recommended to reduce latency. /// The or is null. - void SendMessage(TMessage message, string messageType, string[] modIDs = null, long[] playerIDs = null); + void SendMessage(TMessage message, string messageType, string[]? modIDs = null, long[]? playerIDs = null); } } diff --git a/src/SMAPI/IMultiplayerPeer.cs b/src/SMAPI/IMultiplayerPeer.cs index e487f100..8b0062b5 100644 --- a/src/SMAPI/IMultiplayerPeer.cs +++ b/src/SMAPI/IMultiplayerPeer.cs @@ -1,6 +1,5 @@ -#nullable disable - using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace StardewModdingAPI { @@ -20,6 +19,7 @@ namespace StardewModdingAPI bool IsSplitScreen { get; } /// Whether the player has SMAPI installed. + [MemberNotNullWhen(true, nameof(IMultiplayerPeer.Platform), nameof(IMultiplayerPeer.GameVersion), nameof(IMultiplayerPeer.ApiVersion), nameof(IMultiplayerPeer.Mods))] bool HasSmapi { get; } /// The player's screen ID, if applicable. @@ -30,10 +30,10 @@ namespace StardewModdingAPI GamePlatform? Platform { get; } /// The installed version of Stardew Valley, if is true. - ISemanticVersion GameVersion { get; } + ISemanticVersion? GameVersion { get; } /// The installed version of SMAPI, if is true. - ISemanticVersion ApiVersion { get; } + ISemanticVersion? ApiVersion { get; } /// The installed mods, if is true. IEnumerable Mods { get; } @@ -45,6 +45,6 @@ namespace StardewModdingAPI /// Get metadata for a mod installed by the player. /// The unique mod ID. /// Returns the mod info, or null if the player doesn't have that mod. - IMultiplayerPeerMod GetMod(string id); + IMultiplayerPeerMod? GetMod(string? id); } } diff --git a/src/SMAPI/IReflectedField.cs b/src/SMAPI/IReflectedField.cs index 94dbe6a3..5a24b3dc 100644 --- a/src/SMAPI/IReflectedField.cs +++ b/src/SMAPI/IReflectedField.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Reflection; namespace StardewModdingAPI @@ -19,10 +17,10 @@ namespace StardewModdingAPI ** Public methods *********/ /// Get the field value. - TValue GetValue(); + TValue? GetValue(); /// Set the field value. //// The value to set. - void SetValue(TValue value); + void SetValue(TValue? value); } } diff --git a/src/SMAPI/IReflectedMethod.cs b/src/SMAPI/IReflectedMethod.cs index 78e66cb1..6d87966a 100644 --- a/src/SMAPI/IReflectedMethod.cs +++ b/src/SMAPI/IReflectedMethod.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Reflection; namespace StardewModdingAPI @@ -20,10 +18,10 @@ namespace StardewModdingAPI /// Invoke the method. /// The return type. /// The method arguments to pass in. - TValue Invoke(params object[] arguments); + TValue? Invoke(params object?[] arguments); /// Invoke the method. /// The method arguments to pass in. - void Invoke(params object[] arguments); + void Invoke(params object?[] arguments); } } diff --git a/src/SMAPI/IReflectedProperty.cs b/src/SMAPI/IReflectedProperty.cs index edbf0b21..ad55b9a1 100644 --- a/src/SMAPI/IReflectedProperty.cs +++ b/src/SMAPI/IReflectedProperty.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Reflection; namespace StardewModdingAPI @@ -19,10 +17,10 @@ namespace StardewModdingAPI ** Public methods *********/ /// Get the property value. - TValue GetValue(); + TValue? GetValue(); /// Set the property value. //// The value to set. - void SetValue(TValue value); + void SetValue(TValue? value); } } diff --git a/src/SMAPI/IReflectionHelper.cs b/src/SMAPI/IReflectionHelper.cs index bf7270cf..b8fb877f 100644 --- a/src/SMAPI/IReflectionHelper.cs +++ b/src/SMAPI/IReflectionHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI @@ -14,40 +12,52 @@ namespace StardewModdingAPI /// The field type. /// The object which has the field. /// The field name. - /// Whether to throw an exception if the field is not found. + /// Whether to throw an exception if the field isn't found. Due to limitations with nullable reference types, setting this to false will still mark the return value non-nullable. + /// Returns the method wrapper, or null if is false and the field doesn't exist. + /// The target field doesn't exist, and is true. IReflectedField GetField(object obj, string name, bool required = true); /// Get a static field. /// The field type. /// The type which has the field. /// The field name. - /// Whether to throw an exception if the field is not found. + /// Whether to throw an exception if the field isn't found. Due to limitations with nullable reference types, setting this to false will still mark the return value non-nullable. + /// Returns the method wrapper, or null if is false and the field doesn't exist. + /// The target field doesn't exist, and is true. IReflectedField GetField(Type type, string name, bool required = true); /// Get an instance property. /// The property type. /// The object which has the property. /// The property name. - /// Whether to throw an exception if the property is not found. + /// Whether to throw an exception if the property isn't found. Due to limitations with nullable reference types, setting this to false will still mark the return value non-nullable. + /// Returns the method wrapper, or null if is false and the property doesn't exist. + /// The target property doesn't exist, and is true. IReflectedProperty GetProperty(object obj, string name, bool required = true); /// Get a static property. /// The property type. /// The type which has the property. /// The property name. - /// Whether to throw an exception if the property is not found. + /// Whether to throw an exception if the property isn't found. Due to limitations with nullable reference types, setting this to false will still mark the return value non-nullable. + /// Returns the method wrapper, or null if is false and the property doesn't exist. + /// The target property doesn't exist, and is true. IReflectedProperty GetProperty(Type type, string name, bool required = true); /// Get an instance method. /// The object which has the method. - /// The field name. - /// Whether to throw an exception if the field is not found. + /// The method name. + /// Whether to throw an exception if the method isn't found. Due to limitations with nullable reference types, setting this to false will still mark the return value non-nullable. + /// Returns the method wrapper, or null if is false and the method doesn't exist. + /// The target method doesn't exist, and is true. IReflectedMethod GetMethod(object obj, string name, bool required = true); /// Get a static method. /// The type which has the method. - /// The field name. - /// Whether to throw an exception if the field is not found. + /// The method name. + /// Whether to throw an exception if the method isn't found. Due to limitations with nullable reference types, setting this to false will still mark the return value non-nullable. + /// Returns the method wrapper, or null if is false and the method doesn't exist. + /// The target method doesn't exist, and is true. IReflectedMethod GetMethod(Type type, string name, bool required = true); } } diff --git a/src/SMAPI/Metadata/InstructionMetadata.cs b/src/SMAPI/Metadata/InstructionMetadata.cs index 5617fd13..9b56f963 100644 --- a/src/SMAPI/Metadata/InstructionMetadata.cs +++ b/src/SMAPI/Metadata/InstructionMetadata.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Events; @@ -70,8 +68,8 @@ namespace StardewModdingAPI.Metadata ** detect code which may impact game stability ****/ yield return new TypeFinder("System.Runtime.CompilerServices.CallSite", InstructionHandleResult.DetectedDynamic); - yield return new FieldFinder(typeof(SaveGame).FullName, new[] { nameof(SaveGame.serializer), nameof(SaveGame.farmerSerializer), nameof(SaveGame.locationSerializer) }, InstructionHandleResult.DetectedSaveSerializer); - yield return new EventFinder(typeof(ISpecializedEvents).FullName, new[] { nameof(ISpecializedEvents.UnvalidatedUpdateTicked), nameof(ISpecializedEvents.UnvalidatedUpdateTicking) }, InstructionHandleResult.DetectedUnvalidatedUpdateTick); + yield return new FieldFinder(typeof(SaveGame).FullName!, new[] { nameof(SaveGame.serializer), nameof(SaveGame.farmerSerializer), nameof(SaveGame.locationSerializer) }, InstructionHandleResult.DetectedSaveSerializer); + yield return new EventFinder(typeof(ISpecializedEvents).FullName!, new[] { nameof(ISpecializedEvents.UnvalidatedUpdateTicked), nameof(ISpecializedEvents.UnvalidatedUpdateTicking) }, InstructionHandleResult.DetectedUnvalidatedUpdateTick); /**** ** detect paranoid issues @@ -79,23 +77,23 @@ namespace StardewModdingAPI.Metadata if (paranoidMode) { // filesystem access - yield return new TypeFinder(typeof(System.Console).FullName, InstructionHandleResult.DetectedConsoleAccess); + yield return new TypeFinder(typeof(System.Console).FullName!, InstructionHandleResult.DetectedConsoleAccess); yield return new TypeFinder( new[] { - typeof(System.IO.File).FullName, - typeof(System.IO.FileStream).FullName, - typeof(System.IO.FileInfo).FullName, - typeof(System.IO.Directory).FullName, - typeof(System.IO.DirectoryInfo).FullName, - typeof(System.IO.DriveInfo).FullName, - typeof(System.IO.FileSystemWatcher).FullName + typeof(System.IO.File).FullName!, + typeof(System.IO.FileStream).FullName!, + typeof(System.IO.FileInfo).FullName!, + typeof(System.IO.Directory).FullName!, + typeof(System.IO.DirectoryInfo).FullName!, + typeof(System.IO.DriveInfo).FullName!, + typeof(System.IO.FileSystemWatcher).FullName! }, InstructionHandleResult.DetectedFilesystemAccess ); // shell access - yield return new TypeFinder(typeof(System.Diagnostics.Process).FullName, InstructionHandleResult.DetectedShellAccess); + yield return new TypeFinder(typeof(System.Diagnostics.Process).FullName!, InstructionHandleResult.DetectedShellAccess); } } } diff --git a/src/SMAPI/Mod.cs b/src/SMAPI/Mod.cs index 2b3750d5..cffe2998 100644 --- a/src/SMAPI/Mod.cs +++ b/src/SMAPI/Mod.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI @@ -11,13 +9,13 @@ namespace StardewModdingAPI ** Accessors *********/ /// - public IModHelper Helper { get; internal set; } + public IModHelper Helper { get; internal set; } = null!; /// - public IMonitor Monitor { get; internal set; } + public IMonitor Monitor { get; internal set; } = null!; /// - public IManifest ModManifest { get; internal set; } + public IManifest ModManifest { get; internal set; } = null!; /********* @@ -27,7 +25,7 @@ namespace StardewModdingAPI public abstract void Entry(IModHelper helper); /// - public virtual object GetApi() + public virtual object? GetApi() { return null; } @@ -35,7 +33,7 @@ namespace StardewModdingAPI /// Release or reset unmanaged resources. public void Dispose() { - (this.Helper as IDisposable)?.Dispose(); // deliberate do this outside overridable dispose method so mods don't accidentally suppress it + (this.Helper as IDisposable)?.Dispose(); // deliberately do this outside overridable dispose method so mods don't accidentally suppress it this.Dispose(true); GC.SuppressFinalize(this); } diff --git a/src/SMAPI/Patches/Game1Patcher.cs b/src/SMAPI/Patches/Game1Patcher.cs index c5d98e9e..8f806790 100644 --- a/src/SMAPI/Patches/Game1Patcher.cs +++ b/src/SMAPI/Patches/Game1Patcher.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Diagnostics.CodeAnalysis; using HarmonyLib; @@ -22,10 +20,10 @@ namespace StardewModdingAPI.Patches ** Fields *********/ /// Simplifies access to private code. - private static Reflector Reflection; + private static Reflector Reflection = null!; // initialized in constructor /// A callback to invoke when the load stage changes. - private static Action OnStageChanged; + private static Action OnStageChanged = null!; // initialized in constructor /// Whether the game is running running the code in . private static bool IsInLoadForNewGame; diff --git a/src/SMAPI/Patches/TitleMenuPatcher.cs b/src/SMAPI/Patches/TitleMenuPatcher.cs index 56e5597c..18f1a830 100644 --- a/src/SMAPI/Patches/TitleMenuPatcher.cs +++ b/src/SMAPI/Patches/TitleMenuPatcher.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Diagnostics.CodeAnalysis; using HarmonyLib; @@ -19,7 +17,7 @@ namespace StardewModdingAPI.Patches ** Fields *********/ /// A callback to invoke when the load stage changes. - private static Action OnStageChanged; + private static Action OnStageChanged = null!; // initialized in constructor /********* diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index b2e213fe..a6861bca 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.IO; @@ -22,7 +20,7 @@ namespace StardewModdingAPI private static readonly string DllSearchPath = EarlyConstants.InternalFilesPath; /// The assembly paths in the search folders indexed by assembly name. - private static Dictionary AssemblyPathsByName; + private static Dictionary? AssemblyPathsByName; /********* @@ -61,7 +59,7 @@ namespace StardewModdingAPI /// Method called when assembly resolution fails, which may return a manually resolved assembly. /// The event sender. /// The event arguments. - private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs e) + private static Assembly? CurrentDomain_AssemblyResolve(object? sender, ResolveEventArgs e) { // cache assembly paths by name if (Program.AssemblyPathsByName == null) @@ -74,7 +72,7 @@ namespace StardewModdingAPI { try { - string curName = AssemblyName.GetAssemblyName(dllPath).Name; + string? curName = AssemblyName.GetAssemblyName(dllPath).Name; if (curName != null) Program.AssemblyPathsByName[curName] = dllPath; } @@ -89,8 +87,8 @@ namespace StardewModdingAPI // resolve try { - string searchName = new AssemblyName(e.Name).Name; - return searchName != null && Program.AssemblyPathsByName.TryGetValue(searchName, out string assemblyPath) + string? searchName = new AssemblyName(e.Name).Name; + return searchName != null && Program.AssemblyPathsByName.TryGetValue(searchName, out string? assemblyPath) ? Assembly.LoadFrom(assemblyPath) : null; } @@ -129,7 +127,7 @@ namespace StardewModdingAPI // min version if (Constants.GameVersion.IsOlderThan(Constants.MinimumGameVersion)) { - ISemanticVersion suggestedApiVersion = Constants.GetCompatibleApiVersion(Constants.GameVersion); + ISemanticVersion? suggestedApiVersion = Constants.GetCompatibleApiVersion(Constants.GameVersion); Program.PrintErrorAndExit(suggestedApiVersion != null ? $"Oops! You're running Stardew Valley {Constants.GameVersion}, but the oldest supported version is {Constants.MinimumGameVersion}. You can install SMAPI {suggestedApiVersion} instead to fix this error, or update your game to the latest version." : $"Oops! You're running Stardew Valley {Constants.GameVersion}, but the oldest supported version is {Constants.MinimumGameVersion}. Please update your game before using SMAPI." @@ -152,7 +150,7 @@ namespace StardewModdingAPI foreach (var type in new[] { typeof(IManifest), typeof(Manifest) }) { AssemblyName assemblyName = type.Assembly.GetName(); - ISemanticVersion assemblyVersion = new SemanticVersion(assemblyName.Version); + ISemanticVersion assemblyVersion = new SemanticVersion(assemblyName.Version!); if (!assemblyVersion.Equals(smapiVersion)) Program.PrintErrorAndExit($"Oops! The 'smapi-internal/{assemblyName.Name}.dll' file is version {assemblyVersion} instead of the required {Constants.ApiVersion}. SMAPI doesn't seem to be installed correctly."); } @@ -184,7 +182,7 @@ namespace StardewModdingAPI bool? developerMode = null; string modsPath; { - string rawModsPath = null; + string? rawModsPath = null; // get mods path from command line args int pathIndex = Array.LastIndexOf(args, "--mods-path") + 1; @@ -202,7 +200,7 @@ namespace StardewModdingAPI rawModsPath = Environment.GetEnvironmentVariable("SMAPI_MODS_PATH"); if (developerMode is null) { - string rawDeveloperMode = Environment.GetEnvironmentVariable("SMAPI_DEVELOPER_MODE"); + string? rawDeveloperMode = Environment.GetEnvironmentVariable("SMAPI_DEVELOPER_MODE"); if (rawDeveloperMode != null) developerMode = bool.Parse(rawDeveloperMode); } @@ -221,7 +219,7 @@ namespace StardewModdingAPI /// Write an error directly to the console and exit. /// The error message to display. /// An additional message to log with technical details. - private static void PrintErrorAndExit(string message, string technicalMessage = null) + private static void PrintErrorAndExit(string message, string? technicalMessage = null) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(message); diff --git a/src/SMAPI/Translation.cs b/src/SMAPI/Translation.cs index ef98a00f..2a0d2ce8 100644 --- a/src/SMAPI/Translation.cs +++ b/src/SMAPI/Translation.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections; using System.Collections.Generic; @@ -17,14 +15,14 @@ namespace StardewModdingAPI /// The placeholder text when the translation is null or empty, where {0} is the translation key. internal const string PlaceholderText = "(no translation:{0})"; - /// The locale for which the translation was fetched. + /// The locale for which the translation was fetched like fr-FR, or an empty string for English. private readonly string Locale; /// The underlying translation text. - private readonly string Text; + private readonly string? Text; /// The value to return if the translations is undefined. - private readonly string Placeholder; + private readonly string? Placeholder; /********* @@ -41,12 +39,12 @@ namespace StardewModdingAPI /// The locale for which the translation was fetched. /// The translation key. /// The underlying translation text. - internal Translation(string locale, string key, string text) + internal Translation(string locale, string key, string? text) : this(locale, key, text, string.Format(Translation.PlaceholderText, key)) { } /// Replace the text if it's null or empty. If you set a null or empty value, the translation will show the fallback "no translation" placeholder (see if you want to disable that). Returns a new instance if changed. /// The default value. - public Translation Default(string @default) + public Translation Default(string? @default) { return this.HasValue() ? this @@ -54,7 +52,7 @@ namespace StardewModdingAPI } /// Whether to return a "no translation" placeholder if the translation is null or empty. Returns a new instance. - /// Whether to return a placeholder. + /// Whether to return a placeholder. Due to limitations with nullable reference types, setting this to false will still mark the text non-nullable. public Translation UsePlaceholder(bool use) { return new Translation(this.Locale, this.Key, this.Text, use ? string.Format(Translation.PlaceholderText, this.Key) : null); @@ -63,20 +61,20 @@ namespace StardewModdingAPI /// Replace tokens in the text like {{value}} with the given values. Returns a new instance. /// An object containing token key/value pairs. This can be an anonymous object (like new { value = 42, name = "Cranberries" }), a dictionary, or a class instance. /// The argument is null. - public Translation Tokens(object tokens) + public Translation Tokens(object? tokens) { if (string.IsNullOrWhiteSpace(this.Text) || tokens == null) return this; // get dictionary of tokens - IDictionary tokenLookup = new Dictionary(StringComparer.OrdinalIgnoreCase); + Dictionary tokenLookup = new(StringComparer.OrdinalIgnoreCase); { // from dictionary if (tokens is IDictionary inputLookup) { 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(); } @@ -97,7 +95,7 @@ namespace StardewModdingAPI string text = Regex.Replace(this.Text, @"{{([ \w\.\-]+)}}", match => { string key = match.Groups[1].Value.Trim(); - return tokenLookup.TryGetValue(key, out string value) + return tokenLookup.TryGetValue(key, out string? value) ? value : match.Value; }); @@ -111,18 +109,20 @@ namespace StardewModdingAPI } /// Get the translation text. Calling this method isn't strictly necessary, since you can assign a value directly to a string. + /// Limitation with nullable reference types: if there's no text and you disabled the fallback via , this will return null but the return value will still be marked non-nullable. public override string ToString() { return this.Placeholder != null && !this.HasValue() ? this.Placeholder - : this.Text; + : this.Text!; } /// Get a string representation of the given translation. /// The translation key. + /// Limitation with nullable reference types: if there's no text and you disabled the fallback via , this will return null but the return value will still be marked non-nullable. public static implicit operator string(Translation translation) { - return translation?.ToString(); + return translation.ToString(); } @@ -134,7 +134,7 @@ namespace StardewModdingAPI /// The translation key. /// The underlying translation text. /// The value to return if the translations is undefined. - private Translation(string locale, string key, string text, string placeholder) + private Translation(string locale, string key, string? text, string? placeholder) { this.Locale = locale; this.Key = key; diff --git a/src/SMAPI/Utilities/CaseInsensitivePathCache.cs b/src/SMAPI/Utilities/CaseInsensitivePathCache.cs index 4596fdce..2ac1b9f9 100644 --- a/src/SMAPI/Utilities/CaseInsensitivePathCache.cs +++ b/src/SMAPI/Utilities/CaseInsensitivePathCache.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.IO; @@ -81,7 +79,7 @@ namespace StardewModdingAPI.Utilities return relativePath; // already cached - if (this.RelativePathCache.Value.TryGetValue(relativePath, out string resolved)) + if (this.RelativePathCache.Value.TryGetValue(relativePath, out string? resolved)) return resolved; // file exists but isn't cached for some reason diff --git a/src/SMAPI/Utilities/Keybind.cs b/src/SMAPI/Utilities/Keybind.cs index 7b1acf1d..3455ce77 100644 --- a/src/SMAPI/Utilities/Keybind.cs +++ b/src/SMAPI/Utilities/Keybind.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewModdingAPI.Framework; @@ -44,7 +43,7 @@ namespace StardewModdingAPI.Utilities /// The keybind string. See remarks on for format details. /// The parsed keybind, if valid. /// The parse errors, if any. - public static bool TryParse(string input, out Keybind parsed, out string[] errors) + public static bool TryParse(string input, [NotNullWhen(true)] out Keybind? parsed, out string[] errors) { // empty input if (string.IsNullOrWhiteSpace(input)) diff --git a/src/SMAPI/Utilities/KeybindList.cs b/src/SMAPI/Utilities/KeybindList.cs index 7b2c396b..18eeb9fd 100644 --- a/src/SMAPI/Utilities/KeybindList.cs +++ b/src/SMAPI/Utilities/KeybindList.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewModdingAPI.Toolkit.Serialization; @@ -42,7 +41,7 @@ namespace StardewModdingAPI.Utilities /// The format is invalid. public static KeybindList Parse(string input) { - return KeybindList.TryParse(input, out KeybindList parsed, out string[] errors) + return KeybindList.TryParse(input, out KeybindList? parsed, out string[] errors) ? parsed : throw new SParseException($"Can't parse {nameof(Keybind)} from invalid value '{input}'.\n{string.Join("\n", errors)}"); } @@ -51,7 +50,7 @@ namespace StardewModdingAPI.Utilities /// The keybind string. See remarks on for format details. /// The parsed keybind list, if valid. /// The errors that occurred while parsing the input, if any. - public static bool TryParse(string input, out KeybindList parsed, out string[] errors) + public static bool TryParse(string input, [NotNullWhen(true)] out KeybindList? parsed, out string[] errors) { // empty input if (string.IsNullOrWhiteSpace(input)) @@ -69,7 +68,7 @@ namespace StardewModdingAPI.Utilities if (string.IsNullOrWhiteSpace(rawSet)) continue; - if (!Keybind.TryParse(rawSet, out Keybind keybind, out string[] curErrors)) + if (!Keybind.TryParse(rawSet, out Keybind? keybind, out string[] curErrors)) rawErrors.AddRange(curErrors); else keybinds.Add(keybind); @@ -151,7 +150,7 @@ namespace StardewModdingAPI.Utilities } /// Get the keybind which is currently down, if any. If there are multiple keybinds down, the first one is returned. - public Keybind GetKeybindCurrentlyDown() + public Keybind? GetKeybindCurrentlyDown() { return this.Keybinds.FirstOrDefault(p => p.GetState().IsDown()); } diff --git a/src/SMAPI/Utilities/SDate.cs b/src/SMAPI/Utilities/SDate.cs index b10bc3da..1d4e4489 100644 --- a/src/SMAPI/Utilities/SDate.cs +++ b/src/SMAPI/Utilities/SDate.cs @@ -1,6 +1,5 @@ -#nullable disable - using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Newtonsoft.Json; using StardewModdingAPI.Framework; @@ -24,7 +23,7 @@ namespace StardewModdingAPI.Utilities private readonly int DaysInSeason = 28; /// The core SMAPI translations. - internal static Translator Translations; + internal static Translator? Translations; /********* @@ -94,7 +93,8 @@ namespace StardewModdingAPI.Utilities /// Get a date from a game date instance. /// The world date. - public static SDate From(WorldDate date) + [return: NotNullIfNotNull("date")] + public static SDate? From(WorldDate? date) { if (date == null) return null; @@ -170,14 +170,14 @@ namespace StardewModdingAPI.Utilities ****/ /// Get whether this instance is equal to another. /// The other value to compare. - public bool Equals(SDate other) + public bool Equals(SDate? other) { return this == other; } /// Get whether this instance is equal to another. /// The other value to compare. - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is SDate other && this == other; } @@ -195,7 +195,7 @@ namespace StardewModdingAPI.Utilities /// The base date to compare. /// The other date to compare. /// The equality of the dates - public static bool operator ==(SDate date, SDate other) + public static bool operator ==(SDate? date, SDate? other) { return date?.DaysSinceStart == other?.DaysSinceStart; } @@ -203,7 +203,7 @@ namespace StardewModdingAPI.Utilities /// Get whether one date is not equal to another. /// The base date to compare. /// The other date to compare. - public static bool operator !=(SDate date, SDate other) + public static bool operator !=(SDate? date, SDate? other) { return date?.DaysSinceStart != other?.DaysSinceStart; } @@ -211,7 +211,7 @@ namespace StardewModdingAPI.Utilities /// Get whether one date is more than another. /// The base date to compare. /// The other date to compare. - public static bool operator >(SDate date, SDate other) + public static bool operator >(SDate? date, SDate? other) { return date?.DaysSinceStart > other?.DaysSinceStart; } @@ -219,7 +219,7 @@ namespace StardewModdingAPI.Utilities /// Get whether one date is more than or equal to another. /// The base date to compare. /// The other date to compare. - public static bool operator >=(SDate date, SDate other) + public static bool operator >=(SDate? date, SDate? other) { return date?.DaysSinceStart >= other?.DaysSinceStart; } @@ -227,7 +227,7 @@ namespace StardewModdingAPI.Utilities /// Get whether one date is less than or equal to another. /// The base date to compare. /// The other date to compare. - public static bool operator <=(SDate date, SDate other) + public static bool operator <=(SDate? date, SDate? other) { return date?.DaysSinceStart <= other?.DaysSinceStart; } @@ -235,7 +235,7 @@ namespace StardewModdingAPI.Utilities /// Get whether one date is less than another. /// The base date to compare. /// The other date to compare. - public static bool operator <(SDate date, SDate other) + public static bool operator <(SDate? date, SDate? other) { return date?.DaysSinceStart < other?.DaysSinceStart; } @@ -250,9 +250,10 @@ namespace StardewModdingAPI.Utilities /// The year. /// Whether to allow 0 spring Y1 as a valid date. /// One of the arguments has an invalid value (like day 35). + [SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "The nullability is validated in this constructor.")] private SDate(int day, string season, int year, bool allowDayZero) { - season = season?.Trim().ToLowerInvariant(); + season = season?.Trim().ToLowerInvariant()!; // null-checked below // validate if (season == null) @@ -288,25 +289,17 @@ namespace StardewModdingAPI.Utilities /// The day of month. private DayOfWeek GetDayOfWeek(int day) { - switch (day % 7) + return (day % 7) switch { - case 0: - return DayOfWeek.Sunday; - case 1: - return DayOfWeek.Monday; - case 2: - return DayOfWeek.Tuesday; - case 3: - return DayOfWeek.Wednesday; - case 4: - return DayOfWeek.Thursday; - case 5: - return DayOfWeek.Friday; - case 6: - return DayOfWeek.Saturday; - default: - return 0; - } + 0 => DayOfWeek.Sunday, + 1 => DayOfWeek.Monday, + 2 => DayOfWeek.Tuesday, + 3 => DayOfWeek.Wednesday, + 4 => DayOfWeek.Thursday, + 5 => DayOfWeek.Friday, + 6 => DayOfWeek.Saturday, + _ => 0 + }; } /// Get the number of days since the game began (starting at 1 for the first day of spring in Y1). -- cgit From 4adf8611131a5d86b15f017a42a0366837d14528 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 13 Apr 2022 21:07:43 -0400 Subject: enable nullable annotations in the rest of SMAPI core (#837) --- src/SMAPI.Tests/Utilities/KeybindListTests.cs | 10 +- src/SMAPI/Events/ModMessageReceivedEventArgs.cs | 6 +- src/SMAPI/Framework/CommandManager.cs | 34 ++-- src/SMAPI/Framework/Commands/HelpCommand.cs | 8 +- src/SMAPI/Framework/Content/AssetDataForMap.cs | 23 ++- src/SMAPI/Framework/Content/AssetName.cs | 21 +-- src/SMAPI/Framework/Content/ContentCache.cs | 11 +- .../ContentManagers/BaseContentManager.cs | 21 ++- .../Framework/ContentManagers/ModContentManager.cs | 21 +-- src/SMAPI/Framework/DeprecationManager.cs | 25 ++- src/SMAPI/Framework/Input/GamePadStateBuilder.cs | 9 +- src/SMAPI/Framework/Input/SInputState.cs | 6 +- src/SMAPI/Framework/InternalExtensions.cs | 9 +- .../Framework/Logging/InterceptingTextWriter.cs | 24 ++- src/SMAPI/Framework/Logging/LogManager.cs | 36 +++-- src/SMAPI/Framework/ModHelpers/CommandHelper.cs | 4 +- src/SMAPI/Framework/ModLoading/AssemblyLoader.cs | 35 +++-- .../Framework/ModLoading/AssemblyParseResult.cs | 15 +- src/SMAPI/Framework/ModLoading/ModResolver.cs | 75 +++++---- .../ModLoading/Rewriters/FieldReplaceRewriter.cs | 20 ++- src/SMAPI/Framework/Models/SConfig.cs | 78 +++++++--- src/SMAPI/Framework/Networking/ModMessageModel.cs | 21 ++- src/SMAPI/Framework/Networking/MultiplayerPeer.cs | 16 +- .../Framework/Networking/MultiplayerPeerMod.cs | 5 +- .../Framework/Networking/RemoteContextModModel.cs | 30 +++- .../Framework/Networking/RemoteContextModel.cs | 33 +++- src/SMAPI/Framework/Reflection/CacheEntry.cs | 12 +- src/SMAPI/Framework/Reflection/Reflector.cs | 127 +++++++++------ src/SMAPI/Framework/SCore.cs | 172 +++++++++++---------- src/SMAPI/Framework/SMultiplayer.cs | 89 ++++++----- .../Framework/Serialization/KeybindConverter.cs | 4 +- .../FieldWatchers/ComparableWatcher.cs | 5 +- src/SMAPI/Framework/StateTracking/PlayerTracker.cs | 16 +- .../StateTracking/Snapshots/PlayerSnapshot.cs | 11 +- .../Framework/TemporaryHacks/MiniMonoModHotfix.cs | 122 +++++++-------- src/SMAPI/Metadata/CoreAssetPropagator.cs | 47 +++--- src/SMAPI/Translation.cs | 2 +- src/SMAPI/Utilities/KeybindList.cs | 2 +- src/SMAPI/Utilities/PerScreen.cs | 23 ++- 39 files changed, 679 insertions(+), 549 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI.Tests/Utilities/KeybindListTests.cs b/src/SMAPI.Tests/Utilities/KeybindListTests.cs index 060c93ed..c4c086de 100644 --- a/src/SMAPI.Tests/Utilities/KeybindListTests.cs +++ b/src/SMAPI.Tests/Utilities/KeybindListTests.cs @@ -21,12 +21,12 @@ namespace SMAPI.Tests.Utilities public void TryParse_SimpleValue(SButton button) { // act - bool success = KeybindList.TryParse($"{button}", out KeybindList parsed, out string[] errors); + bool success = KeybindList.TryParse($"{button}", out KeybindList? parsed, out string[] errors); // assert Assert.IsTrue(success, "Parsing unexpectedly failed."); Assert.IsNotNull(parsed, "The parsed result should not be null."); - Assert.AreEqual(parsed.ToString(), $"{button}"); + Assert.AreEqual(parsed!.ToString(), $"{button}"); Assert.IsNotNull(errors, message: "The errors should never be null."); Assert.IsEmpty(errors, message: "The input bindings incorrectly reported errors."); } @@ -47,14 +47,14 @@ namespace SMAPI.Tests.Utilities public string TryParse_MultiValues(string? input) { // act - bool success = KeybindList.TryParse(input, out KeybindList parsed, out string[] errors); + bool success = KeybindList.TryParse(input, out KeybindList? parsed, out string[] errors); // assert Assert.IsTrue(success, "Parsing unexpectedly failed."); Assert.IsNotNull(parsed, "The parsed result should not be null."); Assert.IsNotNull(errors, message: "The errors should never be null."); Assert.IsEmpty(errors, message: "The input bindings incorrectly reported errors."); - return parsed.ToString(); + return parsed!.ToString(); } /// Assert invalid values are rejected. @@ -67,7 +67,7 @@ namespace SMAPI.Tests.Utilities public void TryParse_InvalidValues(string input, string expectedError) { // act - bool success = KeybindList.TryParse(input, out KeybindList parsed, out string[] errors); + bool success = KeybindList.TryParse(input, out KeybindList? parsed, out string[] errors); // assert Assert.IsFalse(success, "Parsing unexpectedly succeeded."); diff --git a/src/SMAPI/Events/ModMessageReceivedEventArgs.cs b/src/SMAPI/Events/ModMessageReceivedEventArgs.cs index 671bdf38..84a27d18 100644 --- a/src/SMAPI/Events/ModMessageReceivedEventArgs.cs +++ b/src/SMAPI/Events/ModMessageReceivedEventArgs.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Framework.Networking; using StardewModdingAPI.Toolkit.Serialization; @@ -47,8 +45,10 @@ namespace StardewModdingAPI.Events /// Read the message data into the given model type. /// The message model type. public TModel ReadAs() + where TModel : notnull { - return this.Message.Data.ToObject(this.JsonHelper.GetSerializer()); + return this.Message.Data.ToObject(this.JsonHelper.GetSerializer()) + ?? throw new InvalidOperationException($"Can't read empty mod message data as a {typeof(TModel).FullName} value."); } } } diff --git a/src/SMAPI/Framework/CommandManager.cs b/src/SMAPI/Framework/CommandManager.cs index 80c08f34..d3b9c8ee 100644 --- a/src/SMAPI/Framework/CommandManager.cs +++ b/src/SMAPI/Framework/CommandManager.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using StardewModdingAPI.Framework.Commands; @@ -39,9 +38,9 @@ namespace StardewModdingAPI.Framework /// The or is null or empty. /// The is not a valid format. /// There's already a command with that name. - public CommandManager Add(IModMetadata mod, string name, string documentation, Action callback) + public CommandManager Add(IModMetadata? mod, string name, string documentation, Action callback) { - name = this.GetNormalizedName(name); + name = this.GetNormalizedName(name)!; // null-checked below // validate format if (string.IsNullOrWhiteSpace(name)) @@ -72,10 +71,13 @@ namespace StardewModdingAPI.Framework /// Get a command by its unique name. /// The command name. /// Returns the matching command, or null if not found. - public Command Get(string name) + public Command? Get(string? name) { - name = this.GetNormalizedName(name); - this.Commands.TryGetValue(name, out Command command); + name = this.GetNormalizedName(name)!; + if (string.IsNullOrWhiteSpace(name)) + return null; + + this.Commands.TryGetValue(name, out Command? command); return command; } @@ -94,7 +96,7 @@ namespace StardewModdingAPI.Framework /// The command which can handle the input. /// The screen ID on which to run the command. /// Returns true if the input was successfully parsed and matched to a command; else false. - public bool TryParse(string input, out string name, out string[] args, out Command command, out int screenId) + public bool TryParse(string? input, [NotNullWhen(true)] out string? name, [NotNullWhen(true)] out string[]? args, [NotNullWhen(true)] out Command? command, out int screenId) { // ignore if blank if (string.IsNullOrWhiteSpace(input)) @@ -108,7 +110,7 @@ namespace StardewModdingAPI.Framework // parse input args = this.ParseArgs(input); - name = this.GetNormalizedName(args[0]); + name = this.GetNormalizedName(args[0])!; args = args.Skip(1).ToArray(); // get screen ID argument @@ -116,7 +118,7 @@ namespace StardewModdingAPI.Framework for (int i = 0; i < args.Length; i++) { // consume arg & set screen ID - if (this.TryParseScreenId(args[i], out int rawScreenId, out string error)) + if (this.TryParseScreenId(args[i], out int rawScreenId, out string? error)) { args = args.Take(i).Concat(args.Skip(i + 1)).ToArray(); screenId = rawScreenId; @@ -140,15 +142,15 @@ namespace StardewModdingAPI.Framework /// The command name. /// The command arguments. /// Returns whether a matching command was triggered. - public bool Trigger(string name, string[] arguments) + public bool Trigger(string? name, string[] arguments) { // get normalized name - name = this.GetNormalizedName(name); - if (name == null) + name = this.GetNormalizedName(name)!; + if (string.IsNullOrWhiteSpace(name)) return false; // get command - if (this.Commands.TryGetValue(name, out Command command)) + if (this.Commands.TryGetValue(name, out Command? command)) { command.Callback.Invoke(name, arguments); return true; @@ -191,7 +193,7 @@ namespace StardewModdingAPI.Framework /// The parsed screen ID, if any. /// The error which indicates an invalid screen ID, if applicable. /// Returns whether the screen ID was parsed successfully. - private bool TryParseScreenId(string arg, out int screen, out string error) + private bool TryParseScreenId(string arg, out int screen, out string? error) { screen = -1; error = null; @@ -220,7 +222,7 @@ namespace StardewModdingAPI.Framework /// Get a normalized command name. /// The command name. - private string GetNormalizedName(string name) + private string? GetNormalizedName(string? name) { name = name?.Trim().ToLower(); return !string.IsNullOrWhiteSpace(name) diff --git a/src/SMAPI/Framework/Commands/HelpCommand.cs b/src/SMAPI/Framework/Commands/HelpCommand.cs index eb6c74f5..65dc3bce 100644 --- a/src/SMAPI/Framework/Commands/HelpCommand.cs +++ b/src/SMAPI/Framework/Commands/HelpCommand.cs @@ -1,5 +1,3 @@ -#nullable disable - using System.Linq; namespace StardewModdingAPI.Framework.Commands @@ -41,7 +39,7 @@ namespace StardewModdingAPI.Framework.Commands { if (args.Any()) { - Command result = this.CommandManager.Get(args[0]); + Command? result = this.CommandManager.Get(args[0]); if (result == null) monitor.Log("There's no command with that name. Type 'help' by itself for more info.", LogLevel.Error); else @@ -63,10 +61,10 @@ namespace StardewModdingAPI.Framework.Commands + "--------------\n" + "The following commands are registered. For more info about a command, type 'help command_name'.\n\n"; - IGrouping[] groups = (from command in this.CommandManager.GetAll() orderby command.Mod?.DisplayName, command.Name group command.Name by command.Mod?.DisplayName).ToArray(); + IGrouping[] groups = (from command in this.CommandManager.GetAll() orderby command.Mod?.DisplayName, command.Name group command.Name by command.Mod?.DisplayName ?? "SMAPI").ToArray(); foreach (var group in groups) { - string modName = group.Key ?? "SMAPI"; + string modName = group.Key; string[] commandNames = group.ToArray(); message += $"{modName}:\n {string.Join("\n ", commandNames)}\n\n"; } diff --git a/src/SMAPI/Framework/Content/AssetDataForMap.cs b/src/SMAPI/Framework/Content/AssetDataForMap.cs index 93148277..133dcc6c 100644 --- a/src/SMAPI/Framework/Content/AssetDataForMap.cs +++ b/src/SMAPI/Framework/Content/AssetDataForMap.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -35,7 +33,7 @@ namespace StardewModdingAPI.Framework.Content /// Normalizes an asset key to match the cache key. /// A callback to invoke when the data is replaced (if any). /// Simplifies access to private code. - public AssetDataForMap(string locale, IAssetName assetName, Map data, Func getNormalizedPath, Action onDataReplaced, Reflector reflection) + public AssetDataForMap(string? locale, IAssetName assetName, Map data, Func getNormalizedPath, Action onDataReplaced, Reflector reflection) : base(locale, assetName, data, getNormalizedPath, onDataReplaced) { this.Reflection = reflection; @@ -126,8 +124,7 @@ namespace StardewModdingAPI.Framework.Content foreach (Layer sourceLayer in source.Layers) { // get layer - Layer targetLayer = sourceToTargetLayers[sourceLayer]; - if (targetLayer == null) + if (!sourceToTargetLayers.TryGetValue(sourceLayer, out Layer? targetLayer)) { target.AddLayer(targetLayer = new Layer(sourceLayer.Id, target, target.Layers[0].LayerSize, Layer.m_tileSize)); sourceToTargetLayers[sourceLayer] = target.GetLayer(sourceLayer.Id); @@ -137,11 +134,13 @@ namespace StardewModdingAPI.Framework.Content targetLayer.Properties.CopyFrom(sourceLayer.Properties); // create new tile - Tile sourceTile = sourceLayer.Tiles[sourcePos.X, sourcePos.Y]; - Tile newTile = sourceTile != null - ? this.CreateTile(sourceTile, targetLayer, tilesheetMap[sourceTile.TileSheet]) - : null; - newTile?.Properties.CopyFrom(sourceTile.Properties); + Tile? sourceTile = sourceLayer.Tiles[sourcePos.X, sourcePos.Y]; + Tile? newTile = null; + if (sourceTile != null) + { + newTile = this.CreateTile(sourceTile, targetLayer, tilesheetMap[sourceTile.TileSheet]); + newTile?.Properties.CopyFrom(sourceTile.Properties); + } // replace tile if (newTile != null || replaceByLayer || replaceAll) @@ -195,7 +194,7 @@ namespace StardewModdingAPI.Framework.Content /// The source tile to copy. /// The target layer. /// The target tilesheet. - private Tile CreateTile(Tile sourceTile, Layer targetLayer, TileSheet targetSheet) + private Tile? CreateTile(Tile sourceTile, Layer targetLayer, TileSheet targetSheet) { switch (sourceTile) { @@ -220,7 +219,7 @@ namespace StardewModdingAPI.Framework.Content } /// Normalize a map tilesheet path for comparison. This value should *not* be used as the actual tilesheet path. /// The path to normalize. - private string NormalizeTilesheetPathForComparison(string path) + private string NormalizeTilesheetPathForComparison(string? path) { if (string.IsNullOrWhiteSpace(path)) return string.Empty; diff --git a/src/SMAPI/Framework/Content/AssetName.cs b/src/SMAPI/Framework/Content/AssetName.cs index 4d583d82..4c691b9a 100644 --- a/src/SMAPI/Framework/Content/AssetName.cs +++ b/src/SMAPI/Framework/Content/AssetName.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using StardewModdingAPI.Toolkit.Utilities; using StardewValley; @@ -26,7 +24,7 @@ namespace StardewModdingAPI.Framework.Content public string BaseName { get; } /// - public string LocaleCode { get; } + public string? LocaleCode { get; } /// public LocalizedContentManager.LanguageCode? LanguageCode { get; } @@ -39,7 +37,7 @@ namespace StardewModdingAPI.Framework.Content /// The base asset name without the locale code. /// The locale code specified in the , if it's a valid code recognized by the game content. /// The language code matching the , if applicable. - public AssetName(string baseName, string localeCode, LocalizedContentManager.LanguageCode? languageCode) + public AssetName(string baseName, string? localeCode, LocalizedContentManager.LanguageCode? languageCode) { // validate if (string.IsNullOrWhiteSpace(baseName)) @@ -69,7 +67,7 @@ namespace StardewModdingAPI.Framework.Content throw new ArgumentException("The asset name can't be null or empty.", nameof(rawName)); string baseName = rawName; - string localeCode = null; + string? localeCode = null; LocalizedContentManager.LanguageCode? languageCode = null; int lastPeriodIndex = rawName.LastIndexOf('.'); @@ -90,7 +88,7 @@ namespace StardewModdingAPI.Framework.Content } /// - public bool IsEquivalentTo(string assetName, bool useBaseName = false) + public bool IsEquivalentTo(string? assetName, bool useBaseName = false) { // empty asset key is never equivalent if (string.IsNullOrWhiteSpace(assetName)) @@ -103,7 +101,7 @@ namespace StardewModdingAPI.Framework.Content } /// - public bool IsEquivalentTo(IAssetName assetName, bool useBaseName = false) + public bool IsEquivalentTo(IAssetName? assetName, bool useBaseName = false) { if (useBaseName) return this.BaseName.Equals(assetName?.BaseName, StringComparison.OrdinalIgnoreCase); @@ -115,7 +113,7 @@ namespace StardewModdingAPI.Framework.Content } /// - public bool StartsWith(string prefix, bool allowPartialWord = true, bool allowSubfolder = true) + public bool StartsWith(string? prefix, bool allowPartialWord = true, bool allowSubfolder = true) { // asset keys never start with null if (prefix is null) @@ -157,8 +155,11 @@ namespace StardewModdingAPI.Framework.Content /// - public bool IsDirectlyUnderPath(string assetFolder) + public bool IsDirectlyUnderPath(string? assetFolder) { + if (assetFolder is null) + return false; + return this.StartsWith(assetFolder + "/", allowPartialWord: false, allowSubfolder: false); } @@ -171,7 +172,7 @@ namespace StardewModdingAPI.Framework.Content } /// - public bool Equals(IAssetName other) + public bool Equals(IAssetName? other) { return other switch { diff --git a/src/SMAPI/Framework/Content/ContentCache.cs b/src/SMAPI/Framework/Content/ContentCache.cs index 4e620d28..d8862eb3 100644 --- a/src/SMAPI/Framework/Content/ContentCache.cs +++ b/src/SMAPI/Framework/Content/ContentCache.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Linq; using StardewModdingAPI.Framework.Reflection; @@ -46,7 +45,8 @@ namespace StardewModdingAPI.Framework.Content /// Simplifies access to private game code. public ContentCache(LocalizedContentManager contentManager, Reflector reflection) { - this.Cache = reflection.GetField>(contentManager, "loadedAssets").GetValue(); + this.Cache = reflection.GetField>(contentManager, "loadedAssets").GetValue() + ?? throw new InvalidOperationException("Can't initialize content cache: required field 'loadedAssets' is missing."); } /**** @@ -66,7 +66,8 @@ namespace StardewModdingAPI.Framework.Content /// Normalize path separators in an asset name. /// The file path to normalize. [Pure] - public string NormalizePathSeparators(string path) + [return: NotNullIfNotNull("path")] + public string? NormalizePathSeparators(string? path) { return PathUtilities.NormalizeAssetName(path); } @@ -93,7 +94,7 @@ namespace StardewModdingAPI.Framework.Content public bool Remove(string key, bool dispose) { // get entry - if (!this.Cache.TryGetValue(key, out object value)) + if (!this.Cache.TryGetValue(key, out object? value)) return false; // dispose & remove entry diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index f1ccab48..5ae5313d 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -99,11 +97,13 @@ namespace StardewModdingAPI.Framework.ContentManagers this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations; // get asset data - this.BaseDisposableReferences = reflection.GetField>(this, "disposableAssets").GetValue(); + this.BaseDisposableReferences = reflection.GetField>(this, "disposableAssets").GetValue() + ?? throw new InvalidOperationException("Can't initialize content manager: the required 'disposableAssets' field wasn't found."); } /// public virtual bool DoesAssetExist(IAssetName assetName) + where T : notnull { return this.Cache.ContainsKey(assetName.Name); } @@ -131,6 +131,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// public T LoadLocalized(IAssetName assetName, LanguageCode language, bool useCache) + where T : notnull { // ignore locale in English (or if disabled) if (!this.TryLocalizeKeys || language == LocalizedContentManager.LanguageCode.en) @@ -172,11 +173,12 @@ namespace StardewModdingAPI.Framework.ContentManagers } /// - public abstract T LoadExact(IAssetName assetName, bool useCache); + public abstract T LoadExact(IAssetName assetName, bool useCache) + where T : notnull; /// [SuppressMessage("ReSharper", "ParameterOnlyUsedForPreconditionCheck.Local", Justification = "Parameter is only used for assertion checks by design.")] - public string AssertAndNormalizeAssetName(string assetName) + public string AssertAndNormalizeAssetName(string? assetName) { // NOTE: the game checks for ContentLoadException to handle invalid keys, so avoid // throwing other types like ArgumentException here. @@ -253,7 +255,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // dispose uncached assets foreach (WeakReference reference in this.Disposables) { - if (reference.TryGetTarget(out IDisposable disposable)) + if (reference.TryGetTarget(out IDisposable? disposable)) { try { @@ -285,7 +287,8 @@ namespace StardewModdingAPI.Framework.ContentManagers *********/ /// Apply initial normalization to a raw asset name before it's parsed. /// The asset name to normalize. - private string PrenormalizeRawAssetName(string assetName) + [return: NotNullIfNotNull("assetName")] + private string? PrenormalizeRawAssetName(string? assetName) { // trim assetName = assetName?.Trim(); @@ -301,7 +304,8 @@ namespace StardewModdingAPI.Framework.ContentManagers /// Normalize path separators in a file path. For asset keys, see instead. /// The file path to normalize. [Pure] - protected string NormalizePathSeparators(string path) + [return: NotNullIfNotNull("path")] + protected string? NormalizePathSeparators(string? path) { return this.Cache.NormalizePathSeparators(path); } @@ -323,6 +327,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The asset value. /// Whether to save the asset to the asset cache. protected virtual void TrackAsset(IAssetName assetName, T value, bool useCache) + where T : notnull { // track asset key if (value is Texture2D texture) diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index 8051c296..f0f4bce9 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Globalization; using System.IO; @@ -92,7 +90,7 @@ namespace StardewModdingAPI.Framework.ContentManagers // resolve managed asset key { - if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string contentManagerID, out IAssetName relativePath)) + if (this.Coordinator.TryParseManagedAssetKey(assetName.Name, out string? contentManagerID, out IAssetName? relativePath)) { if (contentManagerID != this.Name) throw this.GetLoadError(assetName, "can't load a different mod's managed asset key through this mod content manager."); @@ -173,7 +171,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The file to load. private T LoadDataFile(IAssetName assetName, FileInfo file) { - if (!this.JsonHelper.ReadJsonFileIfExists(file.FullName, out T asset)) + if (!this.JsonHelper.ReadJsonFileIfExists(file.FullName, out T? asset)) throw this.GetLoadError(assetName, "the JSON file is invalid."); // should never happen since we check for file existence before calling this method return asset; @@ -249,7 +247,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// The asset name that failed to load. /// The reason the file couldn't be loaded. /// The underlying exception, if applicable. - private SContentLoadException GetLoadError(IAssetName assetName, string reasonPhrase, Exception exception = null) + private SContentLoadException GetLoadError(IAssetName assetName, string reasonPhrase, Exception? exception = null) { return new($"Failed loading asset '{assetName}' from {this.Name}: {reasonPhrase}", exception); } @@ -338,13 +336,16 @@ namespace StardewModdingAPI.Framework.ContentManagers // load best match try { - if (!this.TryGetTilesheetAssetName(relativeMapFolder, imageSource, out IAssetName assetName, out string error)) + if (!this.TryGetTilesheetAssetName(relativeMapFolder, imageSource, out IAssetName? assetName, out string? error)) throw new SContentLoadException($"{errorPrefix} {error}"); - if (!assetName.IsEquivalentTo(tilesheet.ImageSource)) - this.Monitor.VerboseLog($" Mapped tilesheet '{tilesheet.ImageSource}' to '{assetName}'."); + if (assetName is not null) + { + if (!assetName.IsEquivalentTo(tilesheet.ImageSource)) + this.Monitor.VerboseLog($" Mapped tilesheet '{tilesheet.ImageSource}' to '{assetName}'."); - tilesheet.ImageSource = assetName.Name; + tilesheet.ImageSource = assetName.Name; + } } catch (Exception ex) when (ex is not SContentLoadException) { @@ -360,7 +361,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// A message indicating why the file couldn't be loaded. /// Returns whether the asset name was found. /// See remarks on . - private bool TryGetTilesheetAssetName(string modRelativeMapFolder, string relativePath, out IAssetName assetName, out string error) + private bool TryGetTilesheetAssetName(string modRelativeMapFolder, string relativePath, out IAssetName? assetName, out string? error) { assetName = null; error = null; diff --git a/src/SMAPI/Framework/DeprecationManager.cs b/src/SMAPI/Framework/DeprecationManager.cs index fe1b623f..44b0ba2f 100644 --- a/src/SMAPI/Framework/DeprecationManager.cs +++ b/src/SMAPI/Framework/DeprecationManager.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -38,14 +36,14 @@ namespace StardewModdingAPI.Framework } /// Get the source name for a mod from its unique ID. - public string GetSourceNameFromStack() + public string? GetSourceNameFromStack() { return this.ModRegistry.GetFromStack()?.DisplayName; } /// Get the source name for a mod from its unique ID. /// The mod's unique ID. - public string GetSourceName(string modId) + public string? GetSourceName(string modId) { return this.ModRegistry.Get(modId)?.DisplayName; } @@ -55,10 +53,12 @@ namespace StardewModdingAPI.Framework /// A noun phrase describing what is deprecated. /// The SMAPI version which deprecated it. /// How deprecated the code is. - public void Warn(string source, string nounPhrase, string version, DeprecationLevel severity) + public void Warn(string? source, string nounPhrase, string version, DeprecationLevel severity) { + source ??= this.GetSourceNameFromStack() ?? ""; + // ignore if already warned - if (!this.MarkWarned(source ?? this.GetSourceNameFromStack() ?? "", nounPhrase, version)) + if (!this.MarkWarned(source, nounPhrase, version)) return; // queue warning @@ -99,17 +99,12 @@ namespace StardewModdingAPI.Framework } // log message - if (warning.ModName != null) - this.Monitor.Log(message, level); + if (level == LogLevel.Trace) + this.Monitor.Log($"{message}\n{warning.StackTrace}", level); else { - if (level == LogLevel.Trace) - this.Monitor.Log($"{message}\n{warning.StackTrace}", level); - else - { - this.Monitor.Log(message, level); - this.Monitor.Log(warning.StackTrace, LogLevel.Debug); - } + this.Monitor.Log(message, level); + this.Monitor.Log(warning.StackTrace, LogLevel.Debug); } } diff --git a/src/SMAPI/Framework/Input/GamePadStateBuilder.cs b/src/SMAPI/Framework/Input/GamePadStateBuilder.cs index 21168b7a..4ac3332c 100644 --- a/src/SMAPI/Framework/Input/GamePadStateBuilder.cs +++ b/src/SMAPI/Framework/Input/GamePadStateBuilder.cs @@ -1,6 +1,5 @@ -#nullable disable - using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; @@ -23,7 +22,7 @@ namespace StardewModdingAPI.Framework.Input private GamePadState? State; /// The current button states. - private readonly IDictionary ButtonStates; + private readonly IDictionary? ButtonStates; /// The left trigger value. private float LeftTrigger; @@ -42,6 +41,7 @@ namespace StardewModdingAPI.Framework.Input ** Accessors *********/ /// Whether the gamepad is currently connected. + [MemberNotNullWhen(true, nameof(GamePadStateBuilder.ButtonStates))] public bool IsConnected { get; } @@ -213,6 +213,9 @@ namespace StardewModdingAPI.Framework.Input /// Get the pressed gamepad buttons. private IEnumerable GetPressedGamePadButtons() { + if (!this.IsConnected) + yield break; + foreach (var pair in this.ButtonStates) { if (pair.Value == ButtonState.Pressed && pair.Key.TryGetController(out Buttons button)) diff --git a/src/SMAPI/Framework/Input/SInputState.cs b/src/SMAPI/Framework/Input/SInputState.cs index 37b3c8ef..fef83af7 100644 --- a/src/SMAPI/Framework/Input/SInputState.cs +++ b/src/SMAPI/Framework/Input/SInputState.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -17,7 +15,7 @@ namespace StardewModdingAPI.Framework.Input ** Accessors *********/ /// The cursor position on the screen adjusted for the zoom level. - private CursorPosition CursorPositionImpl; + private CursorPosition CursorPositionImpl = new(Vector2.Zero, Vector2.Zero, Vector2.Zero, Vector2.Zero); /// The player's last known tile position. private Vector2? LastPlayerTile; @@ -106,7 +104,7 @@ namespace StardewModdingAPI.Framework.Input this.KeyboardState = keyboard.GetState(); this.MouseState = mouse.GetState(); this.ButtonStates = activeButtons; - if (cursorAbsolutePos != this.CursorPositionImpl?.AbsolutePixels || playerTilePos != this.LastPlayerTile) + if (cursorAbsolutePos != this.CursorPositionImpl.AbsolutePixels || playerTilePos != this.LastPlayerTile) { this.LastPlayerTile = playerTilePos; this.CursorPositionImpl = this.GetCursorPosition(this.MouseState, cursorAbsolutePos, zoomMultiplier); diff --git a/src/SMAPI/Framework/InternalExtensions.cs b/src/SMAPI/Framework/InternalExtensions.cs index a1d87487..fd8cc86f 100644 --- a/src/SMAPI/Framework/InternalExtensions.cs +++ b/src/SMAPI/Framework/InternalExtensions.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -43,6 +41,9 @@ namespace StardewModdingAPI.Framework /// The log severity level. public static void LogAsMod(this IModMetadata metadata, string message, LogLevel level = LogLevel.Trace) { + if (metadata.Monitor is null) + throw new InvalidOperationException($"Can't log as mod {metadata.DisplayName}: mod is broken or a content pack. Logged message:\n[{level}] {message}"); + metadata.Monitor.Log(message, level); } @@ -52,7 +53,7 @@ namespace StardewModdingAPI.Framework /// The log severity level. public static void LogAsModOnce(this IModMetadata metadata, string message, LogLevel level = LogLevel.Trace) { - metadata.Monitor.LogOnce(message, level); + metadata.Monitor?.LogOnce(message, level); } /**** @@ -159,7 +160,7 @@ namespace StardewModdingAPI.Framework /// The reflection helper with which to access private fields. public static bool IsOpen(this SpriteBatch spriteBatch, Reflector reflection) { - return reflection.GetField(spriteBatch, "_beginCalled").GetValue(); + return reflection.GetField(spriteBatch, "_beginCalled")!.GetValue(); } } } diff --git a/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs b/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs index a0957b90..9ecc1626 100644 --- a/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs +++ b/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.IO; using System.Text; @@ -9,6 +7,13 @@ namespace StardewModdingAPI.Framework.Logging /// A text writer which allows intercepting output. internal class InterceptingTextWriter : TextWriter { + /********* + ** Fields + *********/ + /// The event raised when a message is written to the console directly. + private readonly Action OnMessageIntercepted; + + /********* ** Accessors *********/ @@ -21,9 +26,6 @@ namespace StardewModdingAPI.Framework.Logging /// public override Encoding Encoding => this.Out.Encoding; - /// The event raised when a message is written to the console directly. - public event Action OnMessageIntercepted; - /// Whether the text writer should ignore the next input if it's a newline. /// This is used when log output is suppressed from the console, since Console.WriteLine writes the trailing newline as a separate call. public bool IgnoreNextIfNewline { get; set; } @@ -34,9 +36,11 @@ namespace StardewModdingAPI.Framework.Logging *********/ /// Construct an instance. /// The underlying output writer. - public InterceptingTextWriter(TextWriter output) + /// The event raised when a message is written to the console directly. + public InterceptingTextWriter(TextWriter output, Action onMessageIntercepted) { this.Out = output; + this.OnMessageIntercepted = onMessageIntercepted; } /// @@ -65,7 +69,7 @@ namespace StardewModdingAPI.Framework.Logging this.Out.Write(buffer, index, count); } else - this.OnMessageIntercepted?.Invoke(new string(buffer, index, count)); + this.OnMessageIntercepted(new string(buffer, index, count)); } /// @@ -74,12 +78,6 @@ namespace StardewModdingAPI.Framework.Logging this.Out.Write(ch); } - /// - protected override void Dispose(bool disposing) - { - this.OnMessageIntercepted = null; - } - /********* ** Private methods diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs index dab7f554..a5989673 100644 --- a/src/SMAPI/Framework/Logging/LogManager.cs +++ b/src/SMAPI/Framework/Logging/LogManager.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -95,6 +93,11 @@ namespace StardewModdingAPI.Framework.Logging /// Get the screen ID that should be logged to distinguish between players in split-screen mode, if any. public LogManager(string logPath, ColorSchemeConfig colorConfig, bool writeToConsole, bool isVerbose, bool isDeveloperMode, Func getScreenIdForLog) { + // init fields + this.LogFile = new LogFileManager(logPath); + this.Monitor = this.GetMonitor("SMAPI"); + this.MonitorForGame = this.GetMonitor("game"); + // init construction logic this.GetMonitorImpl = name => new Monitor(name, LogManager.IgnoreChar, this.LogFile, colorConfig, isVerbose, getScreenIdForLog) { @@ -103,15 +106,13 @@ namespace StardewModdingAPI.Framework.Logging ShowFullStampInConsole = isDeveloperMode }; - // init fields - this.LogFile = new LogFileManager(logPath); - this.Monitor = this.GetMonitor("SMAPI"); - this.MonitorForGame = this.GetMonitor("game"); - // redirect direct console output - this.ConsoleInterceptor = new InterceptingTextWriter(Console.Out); - if (writeToConsole) - this.ConsoleInterceptor.OnMessageIntercepted += message => this.HandleConsoleMessage(this.MonitorForGame, message); + this.ConsoleInterceptor = new InterceptingTextWriter( + output: Console.Out, + onMessageIntercepted: writeToConsole + ? message => this.HandleConsoleMessage(this.MonitorForGame, message) + : _ => { } + ); Console.SetOut(this.ConsoleInterceptor); // enable Unicode handling on Windows @@ -156,7 +157,7 @@ namespace StardewModdingAPI.Framework.Logging while (true) { // get input - string input = Console.ReadLine(); + string? input = Console.ReadLine(); if (string.IsNullOrWhiteSpace(input)) continue; @@ -222,7 +223,7 @@ namespace StardewModdingAPI.Framework.Logging if (File.Exists(Constants.UpdateMarker)) { string[] rawUpdateFound = File.ReadAllText(Constants.UpdateMarker).Split(new[] { '|' }, 2); - if (SemanticVersion.TryParse(rawUpdateFound[0], out ISemanticVersion updateFound)) + if (SemanticVersion.TryParse(rawUpdateFound[0], out ISemanticVersion? updateFound)) { if (Constants.ApiVersion.IsPrerelease() && updateFound.IsNewerThan(Constants.ApiVersion)) { @@ -264,7 +265,7 @@ namespace StardewModdingAPI.Framework.Logging /// Log the initial header with general SMAPI and system details. /// The path from which mods will be loaded. /// The custom SMAPI settings. - public void LogIntro(string modsPath, IDictionary customSettings) + public void LogIntro(string modsPath, IDictionary customSettings) { // log platform this.Monitor.Log($"SMAPI {Constants.ApiVersion} with Stardew Valley {Constants.GameVersion} (build {Constants.GetBuildVersionLabel()}) on {EnvironmentUtility.GetFriendlyPlatformName(Constants.Platform)}", LogLevel.Info); @@ -326,7 +327,7 @@ namespace StardewModdingAPI.Framework.Logging // log loaded content packs if (loadedContentPacks.Any()) { - string GetModDisplayName(string id) => loadedMods.FirstOrDefault(p => p.HasID(id))?.DisplayName; + string? GetModDisplayName(string id) => loadedMods.FirstOrDefault(p => p.HasID(id))?.DisplayName; this.Monitor.Log($"Loaded {loadedContentPacks.Length} content packs:", LogLevel.Info); foreach (IModMetadata metadata in loadedContentPacks.OrderBy(p => p.DisplayName)) @@ -335,7 +336,7 @@ namespace StardewModdingAPI.Framework.Logging this.Monitor.Log( $" {metadata.DisplayName} {manifest.Version}" + (!string.IsNullOrWhiteSpace(manifest.Author) ? $" by {manifest.Author}" : "") - + $" | for {GetModDisplayName(metadata.Manifest.ContentPackFor.UniqueID)}" + + $" | for {GetModDisplayName(metadata.Manifest.ContentPackFor!.UniqueID)}" + (!string.IsNullOrWhiteSpace(manifest.Description) ? $" | {manifest.Description}" : ""), LogLevel.Info ); @@ -398,6 +399,7 @@ namespace StardewModdingAPI.Framework.Logging /// The loaded mods. /// The mods which could not be loaded. /// Whether to log issues for mods which directly use potentially sensitive .NET APIs like file or shell access. + [SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "Manifests aren't guaranteed non-null at this point in the loading process.")] private void LogModWarnings(IEnumerable mods, IModMetadata[] skippedMods, bool logParanoidWarnings) { // get mods with warnings @@ -431,7 +433,7 @@ namespace StardewModdingAPI.Framework.Logging // duplicate mod: log first one only, don't show redundant version if (mod.FailReason == ModFailReason.Duplicate && mod.HasManifest()) { - if (loggedDuplicateIds.Add(mod.Manifest.UniqueID)) + if (loggedDuplicateIds.Add(mod.Manifest!.UniqueID)) continue; // already logged message = $" - {mod.DisplayName} because {mod.Error}"; @@ -610,7 +612,7 @@ namespace StardewModdingAPI.Framework.Logging /// A brief heading label for the group. /// A detailed explanation of the warning, split into lines. /// Formats the mod label, or null to use the . - private void LogModWarningGroup(IModMetadata[] mods, Func match, LogLevel level, string heading, string[] blurb, Func modLabel = null) + private void LogModWarningGroup(IModMetadata[] mods, Func match, LogLevel level, string heading, string[] blurb, Func? modLabel = null) { // get matching mods string[] modLabels = mods diff --git a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs index c2b5092e..7d25979c 100644 --- a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; namespace StardewModdingAPI.Framework.ModHelpers @@ -24,7 +22,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The mod using this instance. /// Manages console commands. public CommandHelper(IModMetadata mod, CommandManager commandManager) - : base(mod?.Manifest?.UniqueID ?? "SMAPI") + : base(mod.Manifest.UniqueID) { this.Mod = mod; this.CommandManager = commandManager; diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index 070ee803..72b547b1 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.IO; @@ -96,7 +94,12 @@ namespace StardewModdingAPI.Framework.ModLoading // get referenced local assemblies AssemblyParseResult[] assemblies; { - HashSet visitedAssemblyNames = new HashSet(AppDomain.CurrentDomain.GetAssemblies().Select(p => p.GetName().Name)); // don't try loading assemblies that are already loaded + HashSet visitedAssemblyNames = new HashSet( // don't try loading assemblies that are already loaded + from assembly in AppDomain.CurrentDomain.GetAssemblies() + let name = assembly.GetName().Name + where name != null + select name + ); assemblies = this.GetReferencedLocalAssemblies(new FileInfo(assemblyPath), visitedAssemblyNames, this.AssemblyDefinitionResolver).ToArray(); } @@ -113,11 +116,11 @@ namespace StardewModdingAPI.Framework.ModLoading // rewrite & load assemblies in leaf-to-root order bool oneAssembly = assemblies.Length == 1; - Assembly lastAssembly = null; + Assembly? lastAssembly = null; HashSet loggedMessages = new HashSet(); foreach (AssemblyParseResult assembly in assemblies) { - if (assembly.Status == AssemblyLoadStatus.AlreadyLoaded) + if (!assembly.HasDefinition) continue; // rewrite assembly @@ -165,7 +168,7 @@ namespace StardewModdingAPI.Framework.ModLoading throw new IncompatibleInstructionException(); // last assembly loaded is the root - return lastAssembly; + return lastAssembly!; } /// Get whether an assembly is loaded. @@ -174,7 +177,8 @@ namespace StardewModdingAPI.Framework.ModLoading { try { - return this.AssemblyDefinitionResolver.Resolve(reference) != null; + _ = this.AssemblyDefinitionResolver.Resolve(reference); + return true; } catch (AssemblyResolutionException) { @@ -190,7 +194,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// assemblies (especially with Mono). Since this is meant to be called on , /// the implicit assumption is that loading the exact assembly failed. /// - public static Assembly ResolveAssembly(string name) + public static Assembly? ResolveAssembly(string name) { string shortName = name.Split(new[] { ',' }, 2).First(); // get simple name (without version and culture) return AppDomain.CurrentDomain @@ -212,7 +216,8 @@ namespace StardewModdingAPI.Framework.ModLoading /// Track an object for disposal as part of the assembly loader. /// The instance type. /// The disposable instance. - private T TrackForDisposal(T instance) where T : IDisposable + private T TrackForDisposal(T instance) + where T : IDisposable { this.Disposables.Add(instance); return instance; @@ -321,9 +326,9 @@ namespace StardewModdingAPI.Framework.ModLoading // rewrite types using custom attributes foreach (TypeDefinition type in module.GetTypes()) { - foreach (var attr in type.CustomAttributes) + foreach (CustomAttribute attr in type.CustomAttributes) { - foreach (var conField in attr.ConstructorArguments) + foreach (CustomAttributeArgument conField in attr.ConstructorArguments) { if (conField.Value is TypeReference typeRef) this.ChangeTypeScope(typeRef); @@ -382,7 +387,7 @@ namespace StardewModdingAPI.Framework.ModLoading { // get message template // ($phrase is replaced with the noun phrase or messages) - string template = null; + string? template = null; switch (result) { case InstructionHandleResult.Rewritten: @@ -441,20 +446,20 @@ namespace StardewModdingAPI.Framework.ModLoading // format messages string phrase = handler.Phrases.Any() ? string.Join(", ", handler.Phrases) - : handler.DefaultPhrase ?? handler.GetType().Name; + : handler.DefaultPhrase; this.Monitor.LogOnce(loggedMessages, template.Replace("$phrase", phrase)); } /// Get the correct reference to use for compatibility with the current platform. /// The type reference to rewrite. - private void ChangeTypeScope(TypeReference type) + private void ChangeTypeScope(TypeReference? type) { // check skip conditions if (type == null || type.FullName.StartsWith("System.")) return; // get assembly - if (!this.TypeAssemblies.TryGetValue(type.FullName, out Assembly assembly)) + if (!this.TypeAssemblies.TryGetValue(type.FullName, out Assembly? assembly)) return; // replace scope diff --git a/src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs b/src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs index 56bd5a8b..b133f8d6 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyParseResult.cs @@ -1,5 +1,5 @@ -#nullable disable - +using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using Mono.Cecil; @@ -15,11 +15,15 @@ namespace StardewModdingAPI.Framework.ModLoading public readonly FileInfo File; /// The assembly definition. - public readonly AssemblyDefinition Definition; + public readonly AssemblyDefinition? Definition; /// The result of the assembly load. public AssemblyLoadStatus Status; + /// Whether the is loaded and ready (i.e. the is not or ). + [MemberNotNullWhen(true, nameof(AssemblyParseResult.Definition))] + public bool HasDefinition => this.Status == AssemblyLoadStatus.Okay; + /********* ** Public methods @@ -28,11 +32,14 @@ namespace StardewModdingAPI.Framework.ModLoading /// The original assembly file. /// The assembly definition. /// The result of the assembly load. - public AssemblyParseResult(FileInfo file, AssemblyDefinition assembly, AssemblyLoadStatus status) + public AssemblyParseResult(FileInfo file, AssemblyDefinition? assembly, AssemblyLoadStatus status) { this.File = file; this.Definition = assembly; this.Status = status; + + if (status == AssemblyLoadStatus.Okay && assembly == null) + throw new InvalidOperationException($"Invalid assembly parse result: load status {status} with a null assembly."); } } } diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index 4fdeefbc..afb388d0 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using StardewModdingAPI.Toolkit; @@ -28,10 +27,10 @@ namespace StardewModdingAPI.Framework.ModLoading { foreach (ModFolder folder in toolkit.GetModFolders(rootPath)) { - Manifest manifest = folder.Manifest; + Manifest? manifest = folder.Manifest; // parse internal data record (if any) - ModDataRecordVersionedFields dataRecord = modDatabase.Get(manifest?.UniqueID)?.GetVersionedFields(manifest); + ModDataRecordVersionedFields? dataRecord = modDatabase.Get(manifest?.UniqueID)?.GetVersionedFields(manifest); // apply defaults if (manifest != null && dataRecord?.UpdateKey is not null) @@ -43,7 +42,7 @@ namespace StardewModdingAPI.Framework.ModLoading ? ModMetadataStatus.Found : ModMetadataStatus.Failed; - var metadata = new ModMetadata(folder.DisplayName, folder.Directory.FullName, rootPath, manifest, dataRecord, isIgnored: shouldIgnore); + IModMetadata metadata = new ModMetadata(folder.DisplayName, folder.Directory.FullName, rootPath, manifest, dataRecord, isIgnored: shouldIgnore); if (shouldIgnore) metadata.SetStatus(status, ModFailReason.DisabledByDotConvention, "disabled by dot convention"); else @@ -57,7 +56,9 @@ namespace StardewModdingAPI.Framework.ModLoading /// The mod manifests to validate. /// The current SMAPI version. /// Get an update URL for an update key (if valid). - public void ValidateManifests(IEnumerable mods, ISemanticVersion apiVersion, Func getUpdateUrl) + [SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "Manifest values may be null before they're validated.")] + [SuppressMessage("ReSharper", "ConditionIsAlwaysTrueOrFalse", Justification = "Manifest values may be null before they're validated.")] + public void ValidateManifests(IEnumerable mods, ISemanticVersion apiVersion, Func getUpdateUrl) { mods = mods.ToArray(); @@ -84,7 +85,7 @@ namespace StardewModdingAPI.Framework.ModLoading List updateUrls = new List(); foreach (UpdateKey key in mod.GetUpdateKeys(validOnly: true)) { - string url = getUpdateUrl(key.ToString()); + string? url = getUpdateUrl(key.ToString()); if (url != null) updateUrls.Add(url); } @@ -94,7 +95,7 @@ namespace StardewModdingAPI.Framework.ModLoading // build error string error = $"{reasonPhrase}. Please check for a "; - if (mod.DataRecord.StatusUpperVersion == null || mod.Manifest.Version.Equals(mod.DataRecord.StatusUpperVersion)) + if (mod.DataRecord.StatusUpperVersion == null || mod.Manifest.Version?.Equals(mod.DataRecord.StatusUpperVersion) == true) error += "newer version"; else error += $"version newer than {mod.DataRecord.StatusUpperVersion}"; @@ -133,21 +134,21 @@ namespace StardewModdingAPI.Framework.ModLoading if (hasDll) { // invalid filename format - if (mod.Manifest.EntryDll.Intersect(Path.GetInvalidFileNameChars()).Any()) + if (mod.Manifest.EntryDll!.Intersect(Path.GetInvalidFileNameChars()).Any()) { mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its manifest has invalid filename '{mod.Manifest.EntryDll}' for the EntryDLL field."); continue; } // invalid path - if (!File.Exists(Path.Combine(mod.DirectoryPath, mod.Manifest.EntryDll))) + if (!File.Exists(Path.Combine(mod.DirectoryPath, mod.Manifest.EntryDll!))) { mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its DLL '{mod.Manifest.EntryDll}' doesn't exist."); continue; } // invalid capitalization - string actualFilename = new DirectoryInfo(mod.DirectoryPath).GetFiles(mod.Manifest.EntryDll).FirstOrDefault()?.Name; + string? actualFilename = new DirectoryInfo(mod.DirectoryPath).GetFiles(mod.Manifest.EntryDll!).FirstOrDefault()?.Name; if (actualFilename != mod.Manifest.EntryDll) { mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its {nameof(IManifest.EntryDll)} value '{mod.Manifest.EntryDll}' doesn't match the actual file capitalization '{actualFilename}'. The capitalization must match for crossplatform compatibility."); @@ -159,7 +160,7 @@ namespace StardewModdingAPI.Framework.ModLoading else { // invalid content pack ID - if (string.IsNullOrWhiteSpace(mod.Manifest.ContentPackFor.UniqueID)) + if (string.IsNullOrWhiteSpace(mod.Manifest.ContentPackFor!.UniqueID)) { mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its manifest declares {nameof(IManifest.ContentPackFor)} without its required {nameof(IManifestContentPackFor.UniqueID)} field."); continue; @@ -190,7 +191,7 @@ namespace StardewModdingAPI.Framework.ModLoading mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, "its manifest specifies an invalid ID (IDs must only contain letters, numbers, underscores, periods, or hyphens)."); // validate dependencies - foreach (var dependency in mod.Manifest.Dependencies) + foreach (IManifestDependency? dependency in mod.Manifest.Dependencies) { // null dependency if (dependency == null) @@ -328,8 +329,11 @@ namespace StardewModdingAPI.Framework.ModLoading string[] failedLabels = ( from entry in dependencies - where entry.Mod != null && entry.MinVersion != null && entry.MinVersion.IsNewerThan(entry.Mod.Manifest.Version) - select $"{entry.Mod.DisplayName} (needs {entry.MinVersion} or later)" + where + entry.Mod != null + && entry.MinVersion != null + && entry.MinVersion.IsNewerThan(entry.Mod.Manifest.Version) + select $"{entry.Mod!.DisplayName} (needs {entry.MinVersion} or later)" ) .ToArray(); if (failedLabels.Any()) @@ -345,16 +349,14 @@ namespace StardewModdingAPI.Framework.ModLoading states[mod] = ModDependencyStatus.Checking; // recursively sort dependencies - foreach (var dependency in dependencies) + foreach (ModDependency dependency in dependencies) { - IModMetadata requiredMod = dependency.Mod; - var subchain = new List(currentChain) { mod }; - - // ignore missing optional dependency - if (!dependency.IsRequired && requiredMod == null) - continue; + IModMetadata? requiredMod = dependency.Mod; + if (requiredMod == null) + continue; // missing dependencies are handled earlier // detect dependency loop + var subchain = new List(currentChain) { mod }; if (states[requiredMod] == ModDependencyStatus.Checking) { sortedMods.Push(mod); @@ -363,8 +365,8 @@ namespace StardewModdingAPI.Framework.ModLoading } // recursively process each dependency - var substatus = this.ProcessDependencies(mods, modDatabase, requiredMod, states, sortedMods, subchain); - switch (substatus) + var subStatus = this.ProcessDependencies(mods, modDatabase, requiredMod, states, sortedMods, subchain); + switch (subStatus) { // sorted successfully case ModDependencyStatus.Sorted: @@ -380,7 +382,7 @@ namespace StardewModdingAPI.Framework.ModLoading // unexpected status case ModDependencyStatus.Queued: case ModDependencyStatus.Checking: - throw new InvalidModStateException($"Something went wrong sorting dependencies: mod '{requiredMod.DisplayName}' unexpectedly stayed in the '{substatus}' status."); + throw new InvalidModStateException($"Something went wrong sorting dependencies: mod '{requiredMod.DisplayName}' unexpectedly stayed in the '{subStatus}' status."); // sanity check default: @@ -399,14 +401,11 @@ namespace StardewModdingAPI.Framework.ModLoading /// The loaded mods. private IEnumerable GetDependenciesFrom(IManifest manifest, IModMetadata[] loadedMods) { - IModMetadata FindMod(string id) => loadedMods.FirstOrDefault(m => m.HasID(id)); + IModMetadata? FindMod(string id) => loadedMods.FirstOrDefault(m => m.HasID(id)); // yield dependencies - if (manifest.Dependencies != null) - { - foreach (var entry in manifest.Dependencies) - yield return new ModDependency(entry.UniqueID, entry.MinimumVersion, FindMod(entry.UniqueID), entry.IsRequired); - } + foreach (IManifestDependency entry in manifest.Dependencies) + yield return new ModDependency(entry.UniqueID, entry.MinimumVersion, FindMod(entry.UniqueID), entry.IsRequired); // yield content pack parent if (manifest.ContentPackFor != null) @@ -415,10 +414,10 @@ namespace StardewModdingAPI.Framework.ModLoading /// Get a technical message indicating why a mod's compatibility status was overridden, if applicable. /// The mod metadata. - private string GetTechnicalReasonForStatusOverride(IModMetadata mod) + private string? GetTechnicalReasonForStatusOverride(IModMetadata mod) { // get compatibility list record - var data = mod.DataRecord; + ModDataRecordVersionedFields? data = mod.DataRecord; if (data == null) return null; @@ -432,14 +431,14 @@ namespace StardewModdingAPI.Framework.ModLoading }; // get reason - string[] reasons = new[] { mod.DataRecord.StatusReasonPhrase, mod.DataRecord.StatusReasonDetails } + string?[] reasons = new[] { data.StatusReasonPhrase, data.StatusReasonDetails } .Where(p => !string.IsNullOrWhiteSpace(p)) .ToArray(); // build message return $"marked {statusLabel} in SMAPI's internal compatibility list for " - + (mod.DataRecord.StatusUpperVersion != null ? $"versions up to {mod.DataRecord.StatusUpperVersion}" : "all versions") + + (data.StatusUpperVersion != null ? $"versions up to {data.StatusUpperVersion}" : "all versions") + ": " + (reasons.Any() ? string.Join(": ", reasons) : "no reason given") + "."; @@ -459,13 +458,13 @@ namespace StardewModdingAPI.Framework.ModLoading public string ID { get; } /// The minimum required version (if any). - public ISemanticVersion MinVersion { get; } + public ISemanticVersion? MinVersion { get; } /// Whether the mod shouldn't be loaded if the dependency isn't available. public bool IsRequired { get; } /// The loaded mod that fulfills the dependency (if available). - public IModMetadata Mod { get; } + public IModMetadata? Mod { get; } /********* @@ -476,7 +475,7 @@ namespace StardewModdingAPI.Framework.ModLoading /// The minimum required version (if any). /// The loaded mod that fulfills the dependency (if available). /// Whether the mod shouldn't be loaded if the dependency isn't available. - public ModDependency(string id, ISemanticVersion minVersion, IModMetadata mod, bool isRequired) + public ModDependency(string id, ISemanticVersion? minVersion, IModMetadata? mod, bool isRequired) { this.ID = id; this.MinVersion = minVersion; diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs index 806fca62..d5f4cf4a 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/FieldReplaceRewriter.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Reflection; @@ -33,13 +31,19 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// The new field name to reference. public FieldReplaceRewriter AddField(Type fromType, string fromFieldName, Type toType, string toFieldName) { + // validate parameters + if (fromType == null) + throw new InvalidOperationException("Can't replace a field on a null source type."); + if (toType == null) + throw new InvalidOperationException("Can't replace a field on a null target type."); + // get full type name - string fromTypeName = fromType?.FullName; + string? fromTypeName = fromType.FullName; if (fromTypeName == null) throw new InvalidOperationException($"Can't replace field for invalid type reference {toType}."); // get target field - FieldInfo toField = toType.GetField(toFieldName); + FieldInfo? toField = toType.GetField(toFieldName); if (toField == null) throw new InvalidOperationException($"The {toType.FullName} class doesn't have a {toFieldName} field."); @@ -54,15 +58,15 @@ namespace StardewModdingAPI.Framework.ModLoading.Rewriters /// public override bool Handle(ModuleDefinition module, ILProcessor cil, Instruction instruction) { - FieldReference fieldRef = RewriteHelper.AsFieldReference(instruction); - string declaringType = fieldRef?.DeclaringType?.FullName; + FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); + string? declaringType = fieldRef?.DeclaringType?.FullName; // get mapped field - if (declaringType == null || !this.FieldMaps.TryGetValue(declaringType, out var fieldMap) || !fieldMap.TryGetValue(fieldRef.Name, out FieldInfo toField)) + if (declaringType == null || !this.FieldMaps.TryGetValue(declaringType, out var fieldMap) || !fieldMap.TryGetValue(fieldRef!.Name, out FieldInfo? toField)) return false; // replace with new field - this.Phrases.Add($"{fieldRef.DeclaringType.Name}.{fieldRef.Name} field"); + this.Phrases.Add($"{fieldRef.DeclaringType!.Name}.{fieldRef.Name} field"); instruction.Operand = module.ImportReference(toField); return this.MarkRewritten(); } diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index e74d73b5..d626ab4d 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -40,60 +38,96 @@ namespace StardewModdingAPI.Framework.Models ** Accessors ********/ /// Whether to enable development features. - public bool DeveloperMode { get; set; } + public bool DeveloperMode { get; private set; } /// Whether to check for newer versions of SMAPI and mods on startup. - public bool CheckForUpdates { get; set; } + public bool CheckForUpdates { get; } /// Whether to add a section to the 'mod issues' list for mods which which directly use potentially sensitive .NET APIs like file or shell access. - public bool ParanoidWarnings { get; set; } = (bool)SConfig.DefaultValues[nameof(SConfig.ParanoidWarnings)]; + public bool ParanoidWarnings { get; } /// Whether to show beta versions as valid updates. - public bool UseBetaChannel { get; set; } = (bool)SConfig.DefaultValues[nameof(SConfig.UseBetaChannel)]; + public bool UseBetaChannel { get; } /// SMAPI's GitHub project name, used to perform update checks. - public string GitHubProjectName { get; set; } + public string GitHubProjectName { get; } /// The base URL for SMAPI's web API, used to perform update checks. - public string WebApiBaseUrl { get; set; } + public string WebApiBaseUrl { get; } /// Whether SMAPI should log more information about the game context. - public bool VerboseLogging { get; set; } + public bool VerboseLogging { get; } /// Whether SMAPI should rewrite mods for compatibility. - public bool RewriteMods { get; set; } = (bool)SConfig.DefaultValues[nameof(SConfig.RewriteMods)]; + public bool RewriteMods { get; } /// Whether to enable more aggressive memory optimizations. - public bool AggressiveMemoryOptimizations { get; set; } = (bool)SConfig.DefaultValues[nameof(SConfig.AggressiveMemoryOptimizations)]; + public bool AggressiveMemoryOptimizations { get; } /// Whether SMAPI should log network traffic. Best combined with , which includes network metadata. - public bool LogNetworkTraffic { get; set; } + public bool LogNetworkTraffic { get; } /// The colors to use for text written to the SMAPI console. - public ColorSchemeConfig ConsoleColors { get; set; } + public ColorSchemeConfig ConsoleColors { get; } /// The mod IDs SMAPI should ignore when performing update checks or validating update keys. - public string[] SuppressUpdateChecks { get; set; } + public string[] SuppressUpdateChecks { get; } /******** ** Public methods ********/ + /// Construct an instance. + /// Whether to enable development features. + /// Whether to check for newer versions of SMAPI and mods on startup. + /// Whether to add a section to the 'mod issues' list for mods which which directly use potentially sensitive .NET APIs like file or shell access. + /// Whether to show beta versions as valid updates. + /// SMAPI's GitHub project name, used to perform update checks. + /// The base URL for SMAPI's web API, used to perform update checks. + /// Whether SMAPI should log more information about the game context. + /// Whether SMAPI should rewrite mods for compatibility. + /// Whether to enable more aggressive memory optimizations. + /// Whether SMAPI should log network traffic. + /// The colors to use for text written to the SMAPI console. + /// The mod IDs SMAPI should ignore when performing update checks or validating update keys. + public SConfig(bool developerMode, bool checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, bool verboseLogging, bool? rewriteMods, bool? aggressiveMemoryOptimizations, bool logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks) + { + this.DeveloperMode = developerMode; + this.CheckForUpdates = checkForUpdates; + this.ParanoidWarnings = paranoidWarnings ?? (bool)SConfig.DefaultValues[nameof(SConfig.ParanoidWarnings)]; + this.UseBetaChannel = useBetaChannel ?? (bool)SConfig.DefaultValues[nameof(SConfig.UseBetaChannel)]; + this.GitHubProjectName = gitHubProjectName; + this.WebApiBaseUrl = webApiBaseUrl; + this.VerboseLogging = verboseLogging; + this.RewriteMods = rewriteMods ?? (bool)SConfig.DefaultValues[nameof(SConfig.RewriteMods)]; + this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations ?? (bool)SConfig.DefaultValues[nameof(SConfig.AggressiveMemoryOptimizations)]; + this.LogNetworkTraffic = logNetworkTraffic; + this.ConsoleColors = consoleColors; + this.SuppressUpdateChecks = suppressUpdateChecks ?? Array.Empty(); + } + + /// Override the value of . + /// The value to set. + public void OverrideDeveloperMode(bool value) + { + this.DeveloperMode = value; + } + /// Get the settings which have been customized by the player. - public IDictionary GetCustomSettings() + public IDictionary GetCustomSettings() { - IDictionary custom = new Dictionary(); + Dictionary custom = new(); - foreach (var pair in SConfig.DefaultValues) + foreach ((string? name, object defaultValue) in SConfig.DefaultValues) { - object value = typeof(SConfig).GetProperty(pair.Key)?.GetValue(this); - if (!pair.Value.Equals(value)) - custom[pair.Key] = value; + object? value = typeof(SConfig).GetProperty(name)?.GetValue(this); + if (!defaultValue.Equals(value)) + custom[name] = value; } - HashSet curSuppressUpdateChecks = new HashSet(this.SuppressUpdateChecks ?? Array.Empty(), StringComparer.OrdinalIgnoreCase); + HashSet curSuppressUpdateChecks = new(this.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase); if (SConfig.DefaultSuppressUpdateChecks.Count != curSuppressUpdateChecks.Count || SConfig.DefaultSuppressUpdateChecks.Any(p => !curSuppressUpdateChecks.Contains(p))) - custom[nameof(this.SuppressUpdateChecks)] = "[" + string.Join(", ", this.SuppressUpdateChecks ?? Array.Empty()) + "]"; + custom[nameof(this.SuppressUpdateChecks)] = "[" + string.Join(", ", this.SuppressUpdateChecks) + "]"; return custom; } diff --git a/src/SMAPI/Framework/Networking/ModMessageModel.cs b/src/SMAPI/Framework/Networking/ModMessageModel.cs index 4e7d01eb..01672714 100644 --- a/src/SMAPI/Framework/Networking/ModMessageModel.cs +++ b/src/SMAPI/Framework/Networking/ModMessageModel.cs @@ -1,6 +1,5 @@ -#nullable disable - using System.Linq; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace StardewModdingAPI.Framework.Networking @@ -15,33 +14,30 @@ namespace StardewModdingAPI.Framework.Networking ** Origin ****/ /// The unique ID of the player who broadcast the message. - public long FromPlayerID { get; set; } + public long FromPlayerID { get; } /// The unique ID of the mod which broadcast the message. - public string FromModID { get; set; } + public string FromModID { get; } /**** ** Destination ****/ /// The players who should receive the message. - public long[] ToPlayerIDs { get; set; } + public long[]? ToPlayerIDs { get; init; } /// The mods which should receive the message, or null for all mods. - public string[] ToModIDs { get; set; } + public string[]? ToModIDs { get; } /// A message type which receiving mods can use to decide whether it's the one they want to handle, like SetPlayerLocation. This doesn't need to be globally unique, since mods should check the originating mod ID. - public string Type { get; set; } + public string Type { get; } /// The custom mod data being broadcast. - public JToken Data { get; set; } + public JToken Data { get; } /********* ** Public methods *********/ - /// Construct an instance. - public ModMessageModel() { } - /// Construct an instance. /// The unique ID of the player who broadcast the message. /// The unique ID of the mod which broadcast the message. @@ -49,7 +45,8 @@ namespace StardewModdingAPI.Framework.Networking /// The mods which should receive the message, or null for all mods. /// A message type which receiving mods can use to decide whether it's the one they want to handle, like SetPlayerLocation. This doesn't need to be globally unique, since mods should check the originating mod ID. /// The custom mod data being broadcast. - public ModMessageModel(long fromPlayerID, string fromModID, long[] toPlayerIDs, string[] toModIDs, string type, JToken data) + [JsonConstructor] + public ModMessageModel(long fromPlayerID, string fromModID, long[]? toPlayerIDs, string[]? toModIDs, string type, JToken data) { this.FromPlayerID = fromPlayerID; this.FromModID = fromModID; diff --git a/src/SMAPI/Framework/Networking/MultiplayerPeer.cs b/src/SMAPI/Framework/Networking/MultiplayerPeer.cs index 8ee5c309..b37c1e89 100644 --- a/src/SMAPI/Framework/Networking/MultiplayerPeer.cs +++ b/src/SMAPI/Framework/Networking/MultiplayerPeer.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -39,10 +37,10 @@ namespace StardewModdingAPI.Framework.Networking public GamePlatform? Platform { get; } /// - public ISemanticVersion GameVersion { get; } + public ISemanticVersion? GameVersion { get; } /// - public ISemanticVersion ApiVersion { get; } + public ISemanticVersion? ApiVersion { get; } /// public IEnumerable Mods { get; } @@ -57,11 +55,12 @@ namespace StardewModdingAPI.Framework.Networking /// The metadata to copy. /// A method which sends a message to the peer. /// Whether this is a connection to the host player. - public MultiplayerPeer(long playerID, int? screenID, RemoteContextModel model, Action sendMessage, bool isHost) + public MultiplayerPeer(long playerID, int? screenID, RemoteContextModel? model, Action sendMessage, bool isHost) { this.PlayerID = playerID; this.ScreenID = screenID; this.IsHost = isHost; + if (model != null) { this.Platform = model.Platform; @@ -69,13 +68,16 @@ namespace StardewModdingAPI.Framework.Networking this.ApiVersion = model.ApiVersion; this.Mods = model.Mods.Select(mod => new MultiplayerPeerMod(mod)).ToArray(); } + else + this.Mods = Array.Empty(); + this.SendMessageImpl = sendMessage; } /// - public IMultiplayerPeerMod GetMod(string id) + public IMultiplayerPeerMod? GetMod(string? id) { - if (string.IsNullOrWhiteSpace(id) || this.Mods == null || !this.Mods.Any()) + if (string.IsNullOrWhiteSpace(id) || !this.Mods.Any()) return null; id = id.Trim(); diff --git a/src/SMAPI/Framework/Networking/MultiplayerPeerMod.cs b/src/SMAPI/Framework/Networking/MultiplayerPeerMod.cs index 6fdb9e54..1e150508 100644 --- a/src/SMAPI/Framework/Networking/MultiplayerPeerMod.cs +++ b/src/SMAPI/Framework/Networking/MultiplayerPeerMod.cs @@ -1,4 +1,4 @@ -#nullable disable +using System.Diagnostics.CodeAnalysis; namespace StardewModdingAPI.Framework.Networking { @@ -22,10 +22,11 @@ namespace StardewModdingAPI.Framework.Networking *********/ /// Construct an instance. /// The mod metadata. + [SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "The ID shouldn't be null, but we should handle it to avoid an error just in case.")] public MultiplayerPeerMod(RemoteContextModModel mod) { this.Name = mod.Name; - this.ID = mod.ID?.Trim(); + this.ID = mod.ID?.Trim() ?? string.Empty; this.Version = mod.Version; } } diff --git a/src/SMAPI/Framework/Networking/RemoteContextModModel.cs b/src/SMAPI/Framework/Networking/RemoteContextModModel.cs index 0383576c..7571acba 100644 --- a/src/SMAPI/Framework/Networking/RemoteContextModModel.cs +++ b/src/SMAPI/Framework/Networking/RemoteContextModModel.cs @@ -1,17 +1,33 @@ -#nullable disable - namespace StardewModdingAPI.Framework.Networking { /// Metadata about an installed mod exchanged with connected computers. public class RemoteContextModModel { - /// The mod's display name. - public string Name { get; set; } - + /********* + ** Accessors + *********/ /// The unique mod ID. - public string ID { get; set; } + public string ID { get; } + + /// The mod's display name. + public string Name { get; } /// The mod version. - public ISemanticVersion Version { get; set; } + public ISemanticVersion Version { get; } + + + /********* + ** Accessors + *********/ + /// Construct an instance. + /// The unique mod ID. + /// The mod's display name. + /// The mod version. + public RemoteContextModModel(string id, string name, ISemanticVersion version) + { + this.ID = id; + this.Name = name; + this.Version = version; + } } } diff --git a/src/SMAPI/Framework/Networking/RemoteContextModel.cs b/src/SMAPI/Framework/Networking/RemoteContextModel.cs index 37fafa67..7d53e732 100644 --- a/src/SMAPI/Framework/Networking/RemoteContextModel.cs +++ b/src/SMAPI/Framework/Networking/RemoteContextModel.cs @@ -1,4 +1,4 @@ -#nullable disable +using System; namespace StardewModdingAPI.Framework.Networking { @@ -9,18 +9,37 @@ namespace StardewModdingAPI.Framework.Networking ** Accessors *********/ /// Whether this player is the host player. - public bool IsHost { get; set; } + public bool IsHost { get; } - /// The game's platform version. - public GamePlatform Platform { get; set; } + /// The game's platform. + public GamePlatform Platform { get; } /// The installed version of Stardew Valley. - public ISemanticVersion GameVersion { get; set; } + public ISemanticVersion? GameVersion { get; } /// The installed version of SMAPI. - public ISemanticVersion ApiVersion { get; set; } + public ISemanticVersion? ApiVersion { get; } /// The installed mods. - public RemoteContextModModel[] Mods { get; set; } + public RemoteContextModModel[] Mods { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// Whether this player is the host player. + /// The game's platform. + /// The installed version of Stardew Valley. + /// The installed version of SMAPI. + /// The installed mods. + public RemoteContextModel(bool isHost, GamePlatform platform, ISemanticVersion gameVersion, ISemanticVersion apiVersion, RemoteContextModModel[]? mods) + { + this.IsHost = isHost; + this.Platform = platform; + this.GameVersion = gameVersion; + this.ApiVersion = apiVersion; + this.Mods = mods ?? Array.Empty(); + } } } diff --git a/src/SMAPI/Framework/Reflection/CacheEntry.cs b/src/SMAPI/Framework/Reflection/CacheEntry.cs index 6b18d204..27f48a1f 100644 --- a/src/SMAPI/Framework/Reflection/CacheEntry.cs +++ b/src/SMAPI/Framework/Reflection/CacheEntry.cs @@ -1,5 +1,4 @@ -#nullable disable - +using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace StardewModdingAPI.Framework.Reflection @@ -11,21 +10,20 @@ namespace StardewModdingAPI.Framework.Reflection ** Accessors *********/ /// Whether the lookup found a valid match. - public bool IsValid { get; } + [MemberNotNullWhen(true, nameof(CacheEntry.MemberInfo))] + public bool IsValid => this.MemberInfo != null; /// The reflection data for this member (or null if invalid). - public MemberInfo MemberInfo { get; } + public MemberInfo? MemberInfo { get; } /********* ** Public methods *********/ /// Construct an instance. - /// Whether the lookup found a valid match. /// The reflection data for this member (or null if invalid). - public CacheEntry(bool isValid, MemberInfo memberInfo) + public CacheEntry(MemberInfo? memberInfo) { - this.IsValid = isValid; this.MemberInfo = memberInfo; } } diff --git a/src/SMAPI/Framework/Reflection/Reflector.cs b/src/SMAPI/Framework/Reflection/Reflector.cs index c6fc2c79..3490ee97 100644 --- a/src/SMAPI/Framework/Reflection/Reflector.cs +++ b/src/SMAPI/Framework/Reflection/Reflector.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Reflection; using System.Runtime.Caching; @@ -14,7 +12,7 @@ namespace StardewModdingAPI.Framework.Reflection ** Fields *********/ /// The cached fields and methods found via reflection. - private readonly MemoryCache Cache = new(typeof(Reflector).FullName); + private readonly MemoryCache Cache = new(typeof(Reflector).FullName!); /// The sliding cache expiration time. private readonly TimeSpan SlidingCacheExpiry = TimeSpan.FromMinutes(5); @@ -30,8 +28,9 @@ namespace StardewModdingAPI.Framework.Reflection /// The field type. /// The object which has the field. /// The field name. - /// Whether to throw an exception if the field is not found. - /// Returns the field wrapper, or null if the field doesn't exist and is false. + /// Whether to throw an exception if the field isn't found. Due to limitations with nullable reference types, setting this to false will still mark the value non-nullable. + /// Returns the field wrapper, or null if is false and the field doesn't exist. + /// The target field doesn't exist, and is true. public IReflectedField GetField(object obj, string name, bool required = true) { // validate @@ -39,24 +38,26 @@ namespace StardewModdingAPI.Framework.Reflection throw new ArgumentNullException(nameof(obj), "Can't get a instance field from a null object."); // get field from hierarchy - IReflectedField field = this.GetFieldFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + IReflectedField? field = this.GetFieldFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if (required && field == null) throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a '{name}' instance field."); - return field; + return field!; } /// Get a static field. /// The field type. /// The type which has the field. /// The field name. - /// Whether to throw an exception if the field is not found. + /// Whether to throw an exception if the field isn't found. Due to limitations with nullable reference types, setting this to false will still mark the value non-nullable. + /// Returns the field wrapper, or null if is false and the field doesn't exist. + /// The target field doesn't exist, and is true. public IReflectedField GetField(Type type, string name, bool required = true) { // get field from hierarchy - IReflectedField field = this.GetFieldFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public); + IReflectedField? field = this.GetFieldFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Public); if (required && field == null) throw new InvalidOperationException($"The {type.FullName} object doesn't have a '{name}' static field."); - return field; + return field!; } /**** @@ -66,7 +67,9 @@ namespace StardewModdingAPI.Framework.Reflection /// The property type. /// The object which has the property. /// The property name. - /// Whether to throw an exception if the property is not found. + /// Whether to throw an exception if the property isn't found. Due to limitations with nullable reference types, setting this to false will still mark the value non-nullable. + /// Returns the property wrapper, or null if is false and the property doesn't exist. + /// The target property doesn't exist, and is true. public IReflectedProperty GetProperty(object obj, string name, bool required = true) { // validate @@ -74,24 +77,26 @@ namespace StardewModdingAPI.Framework.Reflection throw new ArgumentNullException(nameof(obj), "Can't get a instance property from a null object."); // get property from hierarchy - IReflectedProperty property = this.GetPropertyFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + IReflectedProperty? property = this.GetPropertyFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if (required && property == null) throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a '{name}' instance property."); - return property; + return property!; } /// Get a static property. /// The property type. /// The type which has the property. /// The property name. - /// Whether to throw an exception if the property is not found. + /// Whether to throw an exception if the property isn't found. Due to limitations with nullable reference types, setting this to false will still mark the value non-nullable. + /// Returns the property wrapper, or null if is false and the property doesn't exist. + /// The target property doesn't exist, and is true. public IReflectedProperty GetProperty(Type type, string name, bool required = true) { // get field from hierarchy - IReflectedProperty property = this.GetPropertyFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); + IReflectedProperty? property = this.GetPropertyFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); if (required && property == null) throw new InvalidOperationException($"The {type.FullName} object doesn't have a '{name}' static property."); - return property; + return property!; } /**** @@ -99,8 +104,10 @@ namespace StardewModdingAPI.Framework.Reflection ****/ /// Get a instance method. /// The object which has the method. - /// The field name. - /// Whether to throw an exception if the field is not found. + /// The method name. + /// Whether to throw an exception if the method isn't found. Due to limitations with nullable reference types, setting this to false will still mark the value non-nullable. + /// Returns the method wrapper, or null if is false and the method doesn't exist. + /// The target method doesn't exist, and is true. public IReflectedMethod GetMethod(object obj, string name, bool required = true) { // validate @@ -108,23 +115,25 @@ namespace StardewModdingAPI.Framework.Reflection throw new ArgumentNullException(nameof(obj), "Can't get a instance method from a null object."); // get method from hierarchy - IReflectedMethod method = this.GetMethodFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + IReflectedMethod? method = this.GetMethodFromHierarchy(obj.GetType(), obj, name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if (required && method == null) throw new InvalidOperationException($"The {obj.GetType().FullName} object doesn't have a '{name}' instance method."); - return method; + return method!; } /// Get a static method. /// The type which has the method. - /// The field name. - /// Whether to throw an exception if the field is not found. + /// The method name. + /// Whether to throw an exception if the method isn't found. Due to limitations with nullable reference types, setting this to false will still mark the value non-nullable. + /// Returns the method wrapper, or null if is false and the method doesn't exist. + /// The target method doesn't exist, and is true. public IReflectedMethod GetMethod(Type type, string name, bool required = true) { // get method from hierarchy - IReflectedMethod method = this.GetMethodFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); + IReflectedMethod? method = this.GetMethodFromHierarchy(type, null, name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); if (required && method == null) throw new InvalidOperationException($"The {type.FullName} object doesn't have a '{name}' static method."); - return method; + return method!; } @@ -134,18 +143,25 @@ namespace StardewModdingAPI.Framework.Reflection /// Get a field from the type hierarchy. /// The expected field type. /// The type which has the field. - /// The object which has the field. + /// The object which has the field, or null for a static field. /// The field name. /// The reflection binding which flags which indicates what type of field to find. - private IReflectedField GetFieldFromHierarchy(Type type, object obj, string name, BindingFlags bindingFlags) + private IReflectedField? GetFieldFromHierarchy(Type type, object? obj, string name, BindingFlags bindingFlags) { bool isStatic = bindingFlags.HasFlag(BindingFlags.Static); - FieldInfo field = this.GetCached($"field::{isStatic}::{type.FullName}::{name}", () => + FieldInfo? field = this.GetCached($"field::{isStatic}::{type.FullName}::{name}", () => { - FieldInfo fieldInfo = null; - for (; type != null && fieldInfo == null; type = type.BaseType) - fieldInfo = type.GetField(name, bindingFlags); - return fieldInfo; + for (Type? curType = type; curType != null; curType = curType.BaseType) + { + FieldInfo? fieldInfo = type.GetField(name, bindingFlags); + if (fieldInfo != null) + { + type = curType; + return fieldInfo; + } + } + + return null; }); return field != null @@ -156,18 +172,25 @@ namespace StardewModdingAPI.Framework.Reflection /// Get a property from the type hierarchy. /// The expected property type. /// The type which has the property. - /// The object which has the property. + /// The object which has the property, or null for a static property. /// The property name. /// The reflection binding which flags which indicates what type of property to find. - private IReflectedProperty GetPropertyFromHierarchy(Type type, object obj, string name, BindingFlags bindingFlags) + private IReflectedProperty? GetPropertyFromHierarchy(Type type, object? obj, string name, BindingFlags bindingFlags) { bool isStatic = bindingFlags.HasFlag(BindingFlags.Static); - PropertyInfo property = this.GetCached($"property::{isStatic}::{type.FullName}::{name}", () => + PropertyInfo? property = this.GetCached($"property::{isStatic}::{type.FullName}::{name}", () => { - PropertyInfo propertyInfo = null; - for (; type != null && propertyInfo == null; type = type.BaseType) - propertyInfo = type.GetProperty(name, bindingFlags); - return propertyInfo; + for (Type? curType = type; curType != null; curType = curType.BaseType) + { + PropertyInfo? propertyInfo = type.GetProperty(name, bindingFlags); + if (propertyInfo != null) + { + type = curType; + return propertyInfo; + } + } + + return null; }); return property != null @@ -177,18 +200,25 @@ namespace StardewModdingAPI.Framework.Reflection /// Get a method from the type hierarchy. /// The type which has the method. - /// The object which has the method. + /// The object which has the method, or null for a static method. /// The method name. /// The reflection binding which flags which indicates what type of method to find. - private IReflectedMethod GetMethodFromHierarchy(Type type, object obj, string name, BindingFlags bindingFlags) + private IReflectedMethod? GetMethodFromHierarchy(Type type, object? obj, string name, BindingFlags bindingFlags) { bool isStatic = bindingFlags.HasFlag(BindingFlags.Static); - MethodInfo method = this.GetCached($"method::{isStatic}::{type.FullName}::{name}", () => + MethodInfo? method = this.GetCached($"method::{isStatic}::{type.FullName}::{name}", () => { - MethodInfo methodInfo = null; - for (; type != null && methodInfo == null; type = type.BaseType) - methodInfo = type.GetMethod(name, bindingFlags); - return methodInfo; + for (Type? curType = type; curType != null; curType = curType.BaseType) + { + MethodInfo? methodInfo = type.GetMethod(name, bindingFlags); + if (methodInfo != null) + { + type = curType; + return methodInfo; + } + } + + return null; }); return method != null @@ -200,7 +230,8 @@ namespace StardewModdingAPI.Framework.Reflection /// The expected type. /// The cache key. /// Fetches a new value to cache. - private TMemberInfo GetCached(string key, Func fetch) where TMemberInfo : MemberInfo + private TMemberInfo? GetCached(string key, Func fetch) + where TMemberInfo : MemberInfo { // get from cache if (this.Cache.Contains(key)) @@ -212,8 +243,8 @@ namespace StardewModdingAPI.Framework.Reflection } // fetch & cache new value - TMemberInfo result = fetch(); - CacheEntry cacheEntry = new(result != null, result); + TMemberInfo? result = fetch(); + CacheEntry cacheEntry = new(result); this.Cache.Add(key, cacheEntry, new CacheItemPolicy { SlidingExpiration = this.SlidingCacheExpiry }); return result; } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 364a7632..bce7cffa 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -92,13 +90,13 @@ namespace StardewModdingAPI.Framework private readonly CommandManager CommandManager; /// The underlying game instance. - private SGameRunner Game; + private SGameRunner Game = null!; // initialized very early /// SMAPI's content manager. - private ContentCoordinator ContentCore; + private ContentCoordinator ContentCore = null!; // initialized very early /// The game's core multiplayer utility for the main player. - private SMultiplayer Multiplayer; + private SMultiplayer Multiplayer = null!; // initialized very early /// Tracks the installed mods. /// This is initialized after the game starts. @@ -146,19 +144,18 @@ namespace StardewModdingAPI.Framework private readonly ConcurrentQueue RawCommandQueue = new(); /// A list of commands to execute on each screen. - private readonly PerScreen>> ScreenCommandQueue = new(() => new List>()); - + private readonly PerScreen> ScreenCommandQueue = new(() => new List()); /********* ** Accessors *********/ /// Manages deprecation warnings. /// This is initialized after the game starts. This is accessed directly because it's not part of the normal class model. - internal static DeprecationManager DeprecationManager { get; private set; } + internal static DeprecationManager DeprecationManager { get; private set; } = null!; // initialized in constructor, which happens before other code can access it /// The singleton instance. /// This is only intended for use by external code like the Error Handler mod. - internal static SCore Instance { get; private set; } + internal static SCore Instance { get; private set; } = null!; // initialized in constructor, which happens before other code can access it /// The number of game update ticks which have already executed. This is similar to , but incremented more consistently for every tick. internal static uint TicksElapsed { get; private set; } @@ -191,7 +188,8 @@ namespace StardewModdingAPI.Framework this.Settings = JsonConvert.DeserializeObject(File.ReadAllText(Constants.ApiConfigPath)); if (File.Exists(Constants.ApiUserConfigPath)) JsonConvert.PopulateObject(File.ReadAllText(Constants.ApiUserConfigPath), this.Settings); - this.Settings.DeveloperMode = developerMode ?? this.Settings.DeveloperMode; + if (developerMode.HasValue) + this.Settings.OverrideDeveloperMode(developerMode.Value); this.LogManager = new LogManager(logPath: logPath, colorConfig: this.Settings.ConsoleColors, writeToConsole: writeToConsole, isVerbose: this.Settings.VerboseLogging, isDeveloperMode: this.Settings.DeveloperMode, getScreenIdForLog: this.GetScreenIdForLog); this.CommandManager = new CommandManager(this.Monitor); @@ -331,6 +329,7 @@ namespace StardewModdingAPI.Framework } /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + [SuppressMessage("ReSharper", "ConstantConditionalAccessQualifier", Justification = "May be disposed before SMAPI is fully initialized.")] public void Dispose() { // skip if already disposed @@ -355,9 +354,9 @@ namespace StardewModdingAPI.Framework // dispose core components this.IsGameRunning = false; this.ContentCore?.Dispose(); - this.CancellationToken?.Dispose(); + this.CancellationToken.Dispose(); this.Game?.Dispose(); - this.LogManager?.Dispose(); // dispose last to allow for any last-second log messages + this.LogManager.Dispose(); // dispose last to allow for any last-second log messages // end game (moved from Game1.OnExiting to let us clean up first) Process.GetCurrentProcess().Kill(); @@ -517,12 +516,12 @@ namespace StardewModdingAPI.Framework /********* ** Parse commands *********/ - while (this.RawCommandQueue.TryDequeue(out string rawInput)) + while (this.RawCommandQueue.TryDequeue(out string? rawInput)) { // parse command - string name; - string[] args; - Command command; + string? name; + string[]? args; + Command? command; int screenId; try { @@ -539,7 +538,7 @@ namespace StardewModdingAPI.Framework } // queue command for screen - this.ScreenCommandQueue.GetValueForScreen(screenId).Add(Tuple.Create(command, name, args)); + this.ScreenCommandQueue.GetValueForScreen(screenId).Add(new(command, name, args)); } @@ -556,7 +555,7 @@ namespace StardewModdingAPI.Framework catch (Exception ex) { // log error - this.Monitor.Log($"An error occured in the overridden update loop: {ex.GetLogSummary()}", LogLevel.Error); + this.Monitor.Log($"An error occurred in the overridden update loop: {ex.GetLogSummary()}", LogLevel.Error); // exit if irrecoverable if (!this.UpdateCrashTimer.Decrement()) @@ -575,7 +574,7 @@ namespace StardewModdingAPI.Framework /// Invoke the game's update logic. private void OnPlayerInstanceUpdating(SGame instance, GameTime gameTime, Action runUpdate) { - var events = this.EventManager; + EventManager events = this.EventManager; try { @@ -595,12 +594,8 @@ namespace StardewModdingAPI.Framework *********/ { var commandQueue = this.ScreenCommandQueue.Value; - foreach (var entry in commandQueue) + foreach ((Command? command, string? name, string[]? args) in commandQueue) { - Command command = entry.Item1; - string name = entry.Item2; - string[] args = entry.Item3; - try { command.Callback.Invoke(name, args); @@ -637,6 +632,7 @@ namespace StardewModdingAPI.Framework { this.Monitor.Log("Game loader synchronizing..."); this.Reflection.GetMethod(Game1.game1, "UpdateTitleScreen").Invoke(Game1.currentGameTime); // run game logic to change music on load, etc + // ReSharper disable once ConstantConditionalAccessQualifier -- may become null within the loop while (Game1.currentLoader?.MoveNext() == true) { SCore.ProcessTicksElapsed++; @@ -825,7 +821,7 @@ namespace StardewModdingAPI.Framework // raise cursor moved event if (state.Cursor.IsChanged) - events.CursorMoved.Raise(new CursorMovedEventArgs(state.Cursor.Old, state.Cursor.New)); + events.CursorMoved.Raise(new CursorMovedEventArgs(state.Cursor.Old!, state.Cursor.New!)); // raise mouse wheel scrolled if (state.MouseWheelScroll.IsChanged) @@ -956,7 +952,7 @@ namespace StardewModdingAPI.Framework // raise player events if (raiseWorldEvents) { - PlayerSnapshot playerState = state.CurrentPlayer; + PlayerSnapshot playerState = state.CurrentPlayer!; // not null at this point Farmer player = playerState.Player; // raise current location changed @@ -965,25 +961,25 @@ namespace StardewModdingAPI.Framework if (this.Monitor.IsVerbose) this.Monitor.Log($"Context: set location to {playerState.Location.New}."); - events.Warped.Raise(new WarpedEventArgs(player, playerState.Location.Old, playerState.Location.New)); + events.Warped.Raise(new WarpedEventArgs(player, playerState.Location.Old!, playerState.Location.New!)); } // raise player leveled up a skill - foreach (var pair in playerState.Skills) + foreach ((SkillType skill, var value) in playerState.Skills) { - if (!pair.Value.IsChanged) + if (!value.IsChanged) continue; if (this.Monitor.IsVerbose) - this.Monitor.Log($"Events: player skill '{pair.Key}' changed from {pair.Value.Old} to {pair.Value.New}."); + this.Monitor.Log($"Events: player skill '{skill}' changed from {value.Old} to {value.New}."); - events.LevelChanged.Raise(new LevelChangedEventArgs(player, pair.Key, pair.Value.Old, pair.Value.New)); + events.LevelChanged.Raise(new LevelChangedEventArgs(player, skill, value.Old, value.New)); } // raise player inventory changed if (playerState.Inventory.IsChanged) { - var inventory = playerState.Inventory; + SnapshotItemListDiff inventory = playerState.Inventory; if (this.Monitor.IsVerbose) this.Monitor.Log("Events: player inventory changed."); @@ -1070,7 +1066,8 @@ namespace StardewModdingAPI.Framework // update mod translation helpers foreach (IModMetadata mod in this.ModRegistry.GetAll()) { - mod.Translations.SetLocale(locale, languageCode); + TranslationHelper translations = mod.Translations!; // not null at this point + translations.SetLocale(locale, languageCode); foreach (ContentPack contentPack in mod.GetFakeContentPacks()) contentPack.TranslationImpl.SetLocale(locale, languageCode); @@ -1117,7 +1114,7 @@ namespace StardewModdingAPI.Framework break; case LoadStage.Loaded: - // override chatbox + // override chat box Game1.onScreenMenus.Remove(Game1.chatBox); Game1.onScreenMenus.Add(Game1.chatBox = new SChatBox(this.LogManager.MonitorForGame)); break; @@ -1182,7 +1179,7 @@ namespace StardewModdingAPI.Framework /// The content pack ID. /// The verb phrase indicating what action will be performed, like 'load assets' or 'edit assets'. /// Returns the content pack metadata if valid, else null. - private IModMetadata GetOnBehalfOfContentPack(IModMetadata mod, string id, string verb) + private IModMetadata? GetOnBehalfOfContentPack(IModMetadata mod, string? id, string verb) { if (id == null) return null; @@ -1190,7 +1187,7 @@ namespace StardewModdingAPI.Framework string errorPrefix = $"Can't {verb} on behalf of content pack ID '{id}'"; // get target mod - IModMetadata onBehalfOf = this.ModRegistry.Get(id); + IModMetadata? onBehalfOf = this.ModRegistry.Get(id); if (onBehalfOf == null) { mod.LogAsModOnce($"{errorPrefix}: there's no content pack installed with that ID.", LogLevel.Warn); @@ -1198,7 +1195,7 @@ namespace StardewModdingAPI.Framework } // make sure it's a content pack for the requesting mod - if (!onBehalfOf.IsContentPack || !string.Equals(onBehalfOf.Manifest?.ContentPackFor?.UniqueID, mod.Manifest.UniqueID)) + if (!onBehalfOf.IsContentPack || !string.Equals(onBehalfOf.Manifest.ContentPackFor?.UniqueID, mod.Manifest.UniqueID)) { mod.LogAsModOnce($"{errorPrefix}: that isn't a content pack for this mod.", LogLevel.Warn); return null; @@ -1232,7 +1229,7 @@ namespace StardewModdingAPI.Framework modIDs.Remove(message.FromModID); // don't send a broadcast back to the sender // raise events - this.EventManager.ModMessageReceived.Raise(new ModMessageReceivedEventArgs(message, this.Toolkit.JsonHelper), mod => mod != null && modIDs.Contains(mod.Manifest.UniqueID)); + this.EventManager.ModMessageReceived.Raise(new ModMessageReceivedEventArgs(message, this.Toolkit.JsonHelper), mod => modIDs.Contains(mod.Manifest.UniqueID)); } /// Constructor a content manager to read game content files. @@ -1241,6 +1238,7 @@ namespace StardewModdingAPI.Framework private LocalizedContentManager CreateContentManager(IServiceProvider serviceProvider, string rootDirectory) { // Game1._temporaryContent initializing from SGame constructor + // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- this is the method that initializes it if (this.ContentCore == null) { this.ContentCore = new ContentCoordinator( @@ -1293,21 +1291,21 @@ namespace StardewModdingAPI.Framework // detect issues bool hasObjectIssues = false; void LogIssue(int id, string issue) => this.Monitor.Log($@"Detected issue: item #{id} in Content\Data\ObjectInformation.xnb is invalid ({issue})."); - foreach (KeyValuePair entry in Game1.objectInformation) + foreach ((int id, string? fieldsStr) in Game1.objectInformation) { // must not be empty - if (string.IsNullOrWhiteSpace(entry.Value)) + if (string.IsNullOrWhiteSpace(fieldsStr)) { - LogIssue(entry.Key, "entry is empty"); + LogIssue(id, "entry is empty"); hasObjectIssues = true; continue; } // require core fields - string[] fields = entry.Value.Split('/'); + string[] fields = fieldsStr.Split('/'); if (fields.Length < SObject.objectInfoDescriptionIndex + 1) { - LogIssue(entry.Key, "too few fields for an object"); + LogIssue(id, "too few fields for an object"); hasObjectIssues = true; continue; } @@ -1318,7 +1316,7 @@ namespace StardewModdingAPI.Framework case "Cooking": if (fields.Length < SObject.objectInfoBuffDurationIndex + 1) { - LogIssue(entry.Key, "too few fields for a cooking item"); + LogIssue(id, "too few fields for a cooking item"); hasObjectIssues = true; } break; @@ -1366,7 +1364,7 @@ namespace StardewModdingAPI.Framework string[] installedNames = registryKeys .SelectMany(registryKey => { - using RegistryKey key = Registry.LocalMachine.OpenSubKey(registryKey); + using RegistryKey? key = Registry.LocalMachine.OpenSubKey(registryKey); if (key == null) return Array.Empty(); @@ -1374,9 +1372,9 @@ namespace StardewModdingAPI.Framework .GetSubKeyNames() .Select(subkeyName => { - using RegistryKey subkey = key.OpenSubKey(subkeyName); - string displayName = (string)subkey?.GetValue("DisplayName"); - string displayVersion = (string)subkey?.GetValue("DisplayVersion"); + using RegistryKey? subkey = key.OpenSubKey(subkeyName); + string? displayName = (string?)subkey?.GetValue("DisplayName"); + string? displayVersion = (string?)subkey?.GetValue("DisplayVersion"); if (displayName != null && displayVersion != null && displayName.EndsWith($" {displayVersion}")) displayName = displayName.Substring(0, displayName.Length - displayVersion.Length - 1); @@ -1386,6 +1384,7 @@ namespace StardewModdingAPI.Framework .ToArray(); }) .Where(name => name != null && (name.Contains("MSI Afterburner") || name.Contains("RivaTuner"))) + .Select(name => name!) .Distinct() .OrderBy(name => name) .ToArray(); @@ -1418,14 +1417,14 @@ namespace StardewModdingAPI.Framework // check SMAPI version { - ISemanticVersion updateFound = null; - string updateUrl = null; + ISemanticVersion? updateFound = null; + string? updateUrl = null; try { // fetch update check ModEntryModel response = client.GetModInfo(new[] { new ModSearchEntryModel("Pathoschild.SMAPI", Constants.ApiVersion, new[] { $"GitHub:{this.Settings.GitHubProjectName}" }) }, apiVersion: Constants.ApiVersion, gameVersion: Constants.GameVersion, platform: Constants.Platform).Single().Value; updateFound = response.SuggestedUpdate?.Version; - updateUrl = response.SuggestedUpdate?.Url ?? Constants.HomePageUrl; + updateUrl = response.SuggestedUpdate?.Url; // log message if (updateFound != null) @@ -1451,7 +1450,7 @@ namespace StardewModdingAPI.Framework // show update message on next launch if (updateFound != null) - this.LogManager.WriteUpdateMarker(updateFound.ToString(), updateUrl); + this.LogManager.WriteUpdateMarker(updateFound.ToString(), updateUrl ?? Constants.HomePageUrl); } // check mod versions @@ -1485,12 +1484,12 @@ namespace StardewModdingAPI.Framework foreach (IModMetadata mod in mods.OrderBy(p => p.DisplayName)) { // link to update-check data - if (!mod.HasID() || !results.TryGetValue(mod.Manifest.UniqueID, out ModEntryModel result)) + if (!mod.HasID() || !results.TryGetValue(mod.Manifest.UniqueID, out ModEntryModel? result)) continue; mod.SetUpdateData(result); // handle errors - if (result.Errors != null && result.Errors.Any()) + if (result.Errors.Any()) { errors.AppendLine(result.Errors.Length == 1 ? $" {mod.DisplayName}: {result.Errors[0]}" @@ -1512,13 +1511,8 @@ namespace StardewModdingAPI.Framework { this.Monitor.Newline(); this.Monitor.Log($"You can update {updates.Count} mod{(updates.Count != 1 ? "s" : "")}:", LogLevel.Alert); - foreach (var entry in updates) - { - IModMetadata mod = entry.Item1; - ISemanticVersion newVersion = entry.Item2; - string newUrl = entry.Item3; + foreach ((IModMetadata mod, ISemanticVersion newVersion, string newUrl) in updates) this.Monitor.Log($" {mod.DisplayName} {newVersion}: {newUrl}", LogLevel.Alert); - } } else this.Monitor.Log(" All mods up to date."); @@ -1571,9 +1565,8 @@ namespace StardewModdingAPI.Framework // load mods foreach (IModMetadata mod in mods) { - if (!this.TryLoadMod(mod, mods, modAssemblyLoader, proxyFactory, jsonHelper, contentCore, modDatabase, suppressUpdateChecks, out ModFailReason? failReason, out string errorPhrase, out string errorDetails)) + if (!this.TryLoadMod(mod, mods, modAssemblyLoader, proxyFactory, jsonHelper, contentCore, modDatabase, suppressUpdateChecks, out ModFailReason? failReason, out string? errorPhrase, out string? errorDetails)) { - failReason ??= ModFailReason.LoadFailed; mod.SetStatus(ModMetadataStatus.Failed, failReason.Value, errorPhrase, errorDetails); skippedMods.Add(mod); } @@ -1599,7 +1592,7 @@ namespace StardewModdingAPI.Framework foreach (IModMetadata metadata in loadedMods) { // add interceptors - if (metadata.Mod.Helper is ModHelper helper) + if (metadata.Mod?.Helper is ModHelper helper) { // ReSharper disable SuspiciousTypeConversion.Global if (metadata.Mod is IAssetEditor editor) @@ -1636,8 +1629,8 @@ namespace StardewModdingAPI.Framework // call entry method try { - IMod mod = metadata.Mod; - mod.Entry(mod.Helper); + IMod mod = metadata.Mod!; + mod.Entry(mod.Helper!); } catch (Exception ex) { @@ -1647,7 +1640,7 @@ namespace StardewModdingAPI.Framework // get mod API try { - object api = metadata.Mod.GetApi(); + object? api = metadata.Mod!.GetApi(); if (api != null && !api.GetType().IsPublic) { api = null; @@ -1676,7 +1669,8 @@ namespace StardewModdingAPI.Framework /// The interceptors that were added. /// The interceptors that were removed. /// A list of interceptors to update for the change. - private void OnAssetInterceptorsChanged(IModMetadata mod, IEnumerable added, IEnumerable removed, IList> list) + private void OnAssetInterceptorsChanged(IModMetadata mod, IEnumerable? added, IEnumerable? removed, IList> list) + where T : notnull { foreach (T interceptor in added ?? Array.Empty()) { @@ -1705,7 +1699,7 @@ namespace StardewModdingAPI.Framework /// The user-facing reason phrase explaining why the mod couldn't be loaded (if applicable). /// More detailed details about the error intended for developers (if any). /// Returns whether the mod was successfully loaded. - private bool TryLoadMod(IModMetadata mod, IModMetadata[] mods, AssemblyLoader assemblyLoader, InterfaceProxyFactory proxyFactory, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase, HashSet suppressUpdateChecks, out ModFailReason? failReason, out string errorReasonPhrase, out string errorDetails) + private bool TryLoadMod(IModMetadata mod, IModMetadata[] mods, AssemblyLoader assemblyLoader, InterfaceProxyFactory proxyFactory, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase, HashSet suppressUpdateChecks, [NotNullWhen(false)] out ModFailReason? failReason, out string? errorReasonPhrase, out string? errorDetails) { errorDetails = null; @@ -1714,6 +1708,7 @@ namespace StardewModdingAPI.Framework string relativePath = mod.GetRelativePathWithRoot(); if (mod.IsContentPack) this.Monitor.Log($" {mod.DisplayName} (from {relativePath}) [content pack]..."); + // ReSharper disable once ConstantConditionalAccessQualifier -- mod may be invalid at this point else if (mod.Manifest?.EntryDll != null) this.Monitor.Log($" {mod.DisplayName} (from {relativePath}{Path.DirectorySeparatorChar}{mod.Manifest.EntryDll})..."); // don't use Path.Combine here, since EntryDLL might not be valid else @@ -1721,21 +1716,22 @@ namespace StardewModdingAPI.Framework } // add warning for missing update key - if (mod.HasID() && !suppressUpdateChecks.Contains(mod.Manifest.UniqueID) && !mod.HasValidUpdateKeys()) + if (mod.HasID() && !suppressUpdateChecks.Contains(mod.Manifest!.UniqueID) && !mod.HasValidUpdateKeys()) mod.SetWarning(ModWarning.NoUpdateKeys); // validate status if (mod.Status == ModMetadataStatus.Failed) { this.Monitor.Log($" Failed: {mod.ErrorDetails ?? mod.Error}"); - failReason = mod.FailReason; + failReason = mod.FailReason ?? ModFailReason.LoadFailed; errorReasonPhrase = mod.Error; return false; } + IManifest manifest = mod.Manifest!; // validate dependencies // Although dependencies are validated before mods are loaded, a dependency may have failed to load. - foreach (IManifestDependency dependency in mod.Manifest.Dependencies.Where(p => p.IsRequired)) + foreach (IManifestDependency dependency in manifest.Dependencies.Where(p => p.IsRequired)) { if (this.ModRegistry.Get(dependency.UniqueID) == null) { @@ -1751,7 +1747,6 @@ namespace StardewModdingAPI.Framework // load as content pack if (mod.IsContentPack) { - 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, this.Reflection); @@ -1770,8 +1765,7 @@ namespace StardewModdingAPI.Framework else { // get mod info - IManifest manifest = mod.Manifest; - string assemblyPath = Path.Combine(mod.DirectoryPath, manifest.EntryDll); + string assemblyPath = Path.Combine(mod.DirectoryPath, manifest.EntryDll!); // load mod Assembly modAssembly; @@ -1782,7 +1776,7 @@ namespace StardewModdingAPI.Framework } catch (IncompatibleInstructionException) // details already in trace logs { - string[] updateUrls = new[] { modDatabase.GetModPageUrlFor(manifest.UniqueID), "https://smapi.io/mods" }.Where(p => p != null).ToArray(); + string[] updateUrls = new[] { modDatabase.GetModPageUrlFor(manifest.UniqueID), "https://smapi.io/mods" }.Where(p => p != null).ToArray()!; errorReasonPhrase = $"it's no longer compatible. Please check for a new version at {string.Join(" or ", updateUrls)}"; failReason = ModFailReason.Incompatible; return false; @@ -1808,7 +1802,7 @@ namespace StardewModdingAPI.Framework try { // get mod instance - if (!this.TryLoadModEntry(modAssembly, out Mod modEntry, out errorReasonPhrase)) + if (!this.TryLoadModEntry(modAssembly, out Mod? modEntry, out errorReasonPhrase)) { failReason = ModFailReason.LoadFailed; return false; @@ -1822,8 +1816,8 @@ namespace StardewModdingAPI.Framework return this.ModRegistry .GetAll(assemblyMods: false) - .Where(p => p.IsContentPack && mod.HasID(p.Manifest.ContentPackFor.UniqueID)) - .Select(p => p.ContentPack) + .Where(p => p.IsContentPack && mod.HasID(p.Manifest.ContentPackFor!.UniqueID)) + .Select(p => p.ContentPack!) .ToArray(); } @@ -1890,7 +1884,7 @@ namespace StardewModdingAPI.Framework /// The loaded instance. /// The error indicating why loading failed (if applicable). /// Returns whether the mod entry class was successfully loaded. - private bool TryLoadModEntry(Assembly modAssembly, out Mod mod, out string error) + private bool TryLoadModEntry(Assembly modAssembly, [NotNullWhen(true)] out Mod? mod, [NotNullWhen(false)] out string? error) { mod = null; @@ -1908,7 +1902,7 @@ namespace StardewModdingAPI.Framework } // get implementation - mod = (Mod)modAssembly.CreateInstance(modEntries[0].ToString()); + mod = (Mod?)modAssembly.CreateInstance(modEntries[0].ToString()); if (mod == null) { error = "its entry class couldn't be instantiated."; @@ -1954,7 +1948,7 @@ namespace StardewModdingAPI.Framework metadata.LogAsMod($" - {error}", LogLevel.Warn); } - metadata.Translations.SetTranslations(translations); + metadata.Translations!.SetTranslations(translations); } // fake content packs @@ -1997,7 +1991,7 @@ namespace StardewModdingAPI.Framework string locale = Path.GetFileNameWithoutExtension(file.Name.ToLower().Trim()); try { - if (!jsonHelper.ReadJsonFileIfExists(file.FullName, out IDictionary data) || data == null) + if (!jsonHelper.ReadJsonFileIfExists(file.FullName, out IDictionary? data)) { errors.Add($"{file.Name} file couldn't be read"); // mainly happens when the file is corrupted or empty continue; @@ -2016,8 +2010,8 @@ namespace StardewModdingAPI.Framework foreach (string locale in translations.Keys.ToArray()) { // handle duplicates - HashSet keys = new HashSet(StringComparer.OrdinalIgnoreCase); - HashSet duplicateKeys = new HashSet(StringComparer.OrdinalIgnoreCase); + HashSet keys = new(StringComparer.OrdinalIgnoreCase); + HashSet duplicateKeys = new(StringComparer.OrdinalIgnoreCase); foreach (string key in translations[locale].Keys.ToArray()) { if (!keys.Add(key)) @@ -2107,5 +2101,15 @@ namespace StardewModdingAPI.Framework return null; } + + + /********* + ** Private types + *********/ + /// A queued console command to run during the update loop. + /// The command which can handle the input. + /// The parsed command name. + /// The parsed command arguments. + private readonly record struct QueuedCommand(Command Command, string Name, string[] Args); } } diff --git a/src/SMAPI/Framework/SMultiplayer.cs b/src/SMAPI/Framework/SMultiplayer.cs index de3c25a5..c0e8ee81 100644 --- a/src/SMAPI/Framework/SMultiplayer.cs +++ b/src/SMAPI/Framework/SMultiplayer.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.IO; @@ -61,7 +59,7 @@ namespace StardewModdingAPI.Framework private readonly PerScreen> PeersImpl = new(() => new Dictionary()); /// The backing field for . - private readonly PerScreen HostPeerImpl = new(); + private readonly PerScreen HostPeerImpl = new(); /********* @@ -71,7 +69,7 @@ namespace StardewModdingAPI.Framework public IDictionary Peers => this.PeersImpl.Value; /// The metadata for the host player, if the current player is a farmhand. - public MultiplayerPeer HostPeer + public MultiplayerPeer? HostPeer { get => this.HostPeerImpl.Value; private set => this.HostPeerImpl.Value = value; @@ -115,13 +113,13 @@ namespace StardewModdingAPI.Framework { case LidgrenClient: { - string address = this.Reflection.GetField(client, "address").GetValue(); + string address = this.Reflection.GetField(client, "address").GetValue() ?? throw new InvalidOperationException("Can't initialize base networking client: no valid address found."); return new SLidgrenClient(address, this.OnClientProcessingMessage, this.OnClientSendingMessage); } case GalaxyNetClient: { - GalaxyID address = this.Reflection.GetField(client, "lobbyId").GetValue(); + GalaxyID address = this.Reflection.GetField(client, "lobbyId").GetValue() ?? throw new InvalidOperationException("Can't initialize GOG networking client: no valid address found."); return new SGalaxyNetClient(address, this.OnClientProcessingMessage, this.OnClientSendingMessage); } @@ -139,13 +137,13 @@ namespace StardewModdingAPI.Framework { case LidgrenServer: { - IGameServer gameServer = this.Reflection.GetField(server, "gameServer").GetValue(); + IGameServer gameServer = this.Reflection.GetField(server, "gameServer").GetValue() ?? throw new InvalidOperationException("Can't initialize base networking client: the required 'gameServer' field wasn't found."); return new SLidgrenServer(gameServer, this, this.OnServerProcessingMessage); } case GalaxyNetServer: { - IGameServer gameServer = this.Reflection.GetField(server, "gameServer").GetValue(); + IGameServer gameServer = this.Reflection.GetField(server, "gameServer").GetValue() ?? throw new InvalidOperationException("Can't initialize GOG networking client: the required 'gameServer' field wasn't found."); return new SGalaxyNetServer(gameServer, this, this.OnServerProcessingMessage); } @@ -194,7 +192,7 @@ namespace StardewModdingAPI.Framework case (byte)MessageType.ModContext: { // parse message - RemoteContextModel model = this.ReadContext(message.Reader); + 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")}."); // store peer @@ -290,7 +288,7 @@ namespace StardewModdingAPI.Framework case (byte)MessageType.ModContext: { // parse message - RemoteContextModel model = this.ReadContext(message.Reader); + 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")}."); // store peer @@ -334,7 +332,7 @@ namespace StardewModdingAPI.Framework case (byte)MessageType.PlayerIntroduction: { // store peer - if (!this.Peers.TryGetValue(message.FarmerID, out MultiplayerPeer peer)) + if (!this.Peers.TryGetValue(message.FarmerID, out MultiplayerPeer? peer)) { peer = new MultiplayerPeer( playerID: message.FarmerID, @@ -367,7 +365,7 @@ namespace StardewModdingAPI.Framework { foreach (long playerID in this.disconnectingFarmers) { - if (this.Peers.TryGetValue(playerID, out MultiplayerPeer peer)) + if (this.Peers.TryGetValue(playerID, out MultiplayerPeer? peer)) { this.Monitor.Log($"Player quit: {playerID}"); this.Peers.Remove(playerID); @@ -384,7 +382,7 @@ namespace StardewModdingAPI.Framework /// The unique ID of the mod sending the message. /// The mod IDs which should receive the message on the destination computers, or null for all mods. Specifying mod IDs is recommended to improve performance, unless it's a general-purpose broadcast. /// The values for the players who should receive the message, or null for all players. If you don't need to broadcast to all players, specifying player IDs is recommended to reduce latency. - public void BroadcastModMessage(TMessage message, string messageType, string fromModID, string[] toModIDs, long[] toPlayerIDs) + public void BroadcastModMessage(TMessage message, string messageType, string fromModID, string[]? toModIDs, long[]? toPlayerIDs) { // validate input if (message == null) @@ -488,13 +486,13 @@ namespace StardewModdingAPI.Framework /// Read the metadata context for a player. /// The stream reader. - private RemoteContextModel ReadContext(BinaryReader reader) + private RemoteContextModel? ReadContext(BinaryReader reader) { string data = reader.ReadString(); RemoteContextModel model = this.JsonHelper.Deserialize(data); return model.ApiVersion != null ? model - : null; // no data available for unmodded players + : null; // no data available for vanilla players } /// Receive a mod message sent from another player's mods. @@ -515,12 +513,15 @@ namespace StardewModdingAPI.Framework // forward to other players if (Context.IsMainPlayer && playerIDs.Any(p => p != Game1.player.UniqueMultiplayerID)) { - ModMessageModel newModel = new(model); foreach (long playerID in playerIDs) { - if (playerID != Game1.player.UniqueMultiplayerID && playerID != model.FromPlayerID && this.Peers.TryGetValue(playerID, out MultiplayerPeer peer)) + if (playerID != Game1.player.UniqueMultiplayerID && playerID != model.FromPlayerID && this.Peers.TryGetValue(playerID, out MultiplayerPeer? peer)) { - newModel.ToPlayerIDs = new[] { peer.PlayerID }; + ModMessageModel newModel = new(model) + { + ToPlayerIDs = new[] { peer.PlayerID } + }; + this.Monitor.VerboseLog($" Forwarding message to player {peer.PlayerID}."); peer.SendMessage(new OutgoingMessage((byte)MessageType.ModMessage, peer.PlayerID, this.JsonHelper.Serialize(newModel, Formatting.None))); } @@ -546,22 +547,20 @@ namespace StardewModdingAPI.Framework /// Get the fields to include in a context sync message sent to other players. private object[] GetContextSyncMessageFields() { - RemoteContextModel model = new() - { - IsHost = Context.IsWorldReady && Context.IsMainPlayer, - Platform = Constants.TargetPlatform, - ApiVersion = Constants.ApiVersion, - GameVersion = Constants.GameVersion, - Mods = this.ModRegistry + RemoteContextModel model = new( + isHost: Context.IsWorldReady && Context.IsMainPlayer, + platform: Constants.TargetPlatform, + apiVersion: Constants.ApiVersion, + gameVersion: Constants.GameVersion, + mods: this.ModRegistry .GetAll() - .Select(mod => new RemoteContextModModel - { - ID = mod.Manifest.UniqueID, - Name = mod.Manifest.Name, - Version = mod.Manifest.Version - }) + .Select(mod => new RemoteContextModModel( + id: mod.Manifest.UniqueID, + name: mod.Manifest.Name, + version: mod.Manifest.Version + )) .ToArray() - }; + ); return new object[] { this.JsonHelper.Serialize(model, Formatting.None) }; } @@ -573,21 +572,19 @@ namespace StardewModdingAPI.Framework if (!peer.HasSmapi) return new object[] { "{}" }; - RemoteContextModel model = new() - { - IsHost = peer.IsHost, - Platform = peer.Platform.Value, - ApiVersion = peer.ApiVersion, - GameVersion = peer.GameVersion, - Mods = peer.Mods - .Select(mod => new RemoteContextModModel - { - ID = mod.ID, - Name = mod.Name, - Version = mod.Version - }) + RemoteContextModel model = new( + isHost: peer.IsHost, + platform: peer.Platform.Value, + apiVersion: peer.ApiVersion, + gameVersion: peer.GameVersion, + mods: peer.Mods + .Select(mod => new RemoteContextModModel( + id: mod.ID, + name: mod.Name, + version: mod.Version + )) .ToArray() - }; + ); return new object[] { this.JsonHelper.Serialize(model, Formatting.None) }; } diff --git a/src/SMAPI/Framework/Serialization/KeybindConverter.cs b/src/SMAPI/Framework/Serialization/KeybindConverter.cs index f3bab20d..539f1291 100644 --- a/src/SMAPI/Framework/Serialization/KeybindConverter.cs +++ b/src/SMAPI/Framework/Serialization/KeybindConverter.cs @@ -53,13 +53,13 @@ namespace StardewModdingAPI.Framework.Serialization if (objectType == typeof(Keybind)) { - return Keybind.TryParse(str, out Keybind parsed, out string[] errors) + return Keybind.TryParse(str, out Keybind? parsed, out string[] errors) ? parsed : throw new SParseException($"Can't parse {nameof(Keybind)} from invalid value '{str}' (path: {path}).\n{string.Join("\n", errors)}"); } else { - return KeybindList.TryParse(str, out KeybindList parsed, out string[] errors) + return KeybindList.TryParse(str, out KeybindList? parsed, out string[] errors) ? parsed : throw new SParseException($"Can't parse {nameof(KeybindList)} from invalid value '{str}' (path: {path}).\n{string.Join("\n", errors)}"); } diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs index 4f94294c..5f76fe0a 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableWatcher.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; @@ -42,7 +40,8 @@ namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers { this.GetValue = getValue; this.Comparer = comparer; - this.PreviousValue = getValue(); + this.CurrentValue = getValue(); + this.PreviousValue = this.CurrentValue; } /// Update the current value if needed. diff --git a/src/SMAPI/Framework/StateTracking/PlayerTracker.cs b/src/SMAPI/Framework/StateTracking/PlayerTracker.cs index 367eafea..5433ac8e 100644 --- a/src/SMAPI/Framework/StateTracking/PlayerTracker.cs +++ b/src/SMAPI/Framework/StateTracking/PlayerTracker.cs @@ -1,7 +1,6 @@ -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using StardewModdingAPI.Enums; using StardewModdingAPI.Framework.StateTracking.Comparers; @@ -23,7 +22,7 @@ namespace StardewModdingAPI.Framework.StateTracking private IDictionary CurrentInventory; /// The player's last valid location. - private GameLocation LastValidLocation; + private GameLocation? LastValidLocation; /// The underlying watchers. private readonly List Watchers = new(); @@ -36,7 +35,7 @@ namespace StardewModdingAPI.Framework.StateTracking public Farmer Player { get; } /// The player's current location. - public IValueWatcher LocationWatcher { get; } + public IValueWatcher LocationWatcher { get; } /// Tracks changes to the player's skill levels. public IDictionary> SkillWatchers { get; } @@ -51,7 +50,8 @@ namespace StardewModdingAPI.Framework.StateTracking { // init player data this.Player = player; - this.PreviousInventory = this.GetInventory(); + this.CurrentInventory = this.GetInventory(); + this.PreviousInventory = new Dictionary(this.CurrentInventory); // init trackers this.LocationWatcher = WatcherFactory.ForReference(this.GetCurrentLocation); @@ -95,7 +95,7 @@ namespace StardewModdingAPI.Framework.StateTracking /// Get the player's current location, ignoring temporary null values. /// The game will set to null in some cases, e.g. when they're a secondary player in multiplayer and transition to a location that hasn't been synced yet. While that's happening, this returns the player's last valid location instead. - public GameLocation GetCurrentLocation() + public GameLocation? GetCurrentLocation() { return this.Player.currentLocation ?? this.LastValidLocation; } @@ -103,7 +103,7 @@ namespace StardewModdingAPI.Framework.StateTracking /// Get the inventory changes since the last update, if anything changed. /// The inventory changes, or null if nothing changed. /// Returns whether anything changed. - public bool TryGetInventoryChanges(out SnapshotItemListDiff changes) + public bool TryGetInventoryChanges([NotNullWhen(true)] out SnapshotItemListDiff? changes) { IDictionary current = this.GetInventory(); @@ -124,7 +124,7 @@ namespace StardewModdingAPI.Framework.StateTracking public void Dispose() { this.PreviousInventory.Clear(); - this.CurrentInventory?.Clear(); + this.CurrentInventory.Clear(); foreach (IWatcher watcher in this.Watchers) watcher.Dispose(); diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs index bf81a35e..6a24ec30 100644 --- a/src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs +++ b/src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -47,17 +45,18 @@ namespace StardewModdingAPI.Framework.StateTracking.Snapshots public PlayerSnapshot(Farmer player) { this.Player = player; + this.Inventory = this.EmptyItemListDiff; } /// Update the tracked values. /// The player watcher to snapshot. public void Update(PlayerTracker watcher) { - this.Location.Update(watcher.LocationWatcher); - foreach (var pair in this.Skills) - pair.Value.Update(watcher.SkillWatchers[pair.Key]); + this.Location.Update(watcher.LocationWatcher!); + foreach ((SkillType skill, var value) in this.Skills) + value.Update(watcher.SkillWatchers[skill]); - this.Inventory = watcher.TryGetInventoryChanges(out SnapshotItemListDiff itemChanges) + this.Inventory = watcher.TryGetInventoryChanges(out SnapshotItemListDiff? itemChanges) ? itemChanges : this.EmptyItemListDiff; } diff --git a/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs index 5f0ecfa0..b5fc1f57 100644 --- a/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs +++ b/src/SMAPI/Framework/TemporaryHacks/MiniMonoModHotfix.cs @@ -1,5 +1,3 @@ -#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. @@ -7,15 +5,15 @@ // // This will be removed when Harmony/MonoMod are updated to incorporate the fix. // -// Special thanks to 0x0ade for submitting this worokaround! Copy/pasted and adapted from MonoMod. +// Special thanks to 0x0ade for submitting this workaround! Copy/pasted and adapted from MonoMod. using System; -using System.Reflection; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Reflection.Emit; using System.Runtime.CompilerServices; using HarmonyLib; -using System.Reflection.Emit; // ReSharper disable once CheckNamespace -- Temporary hotfix submitted by the MonoMod author. namespace MonoMod.Utils @@ -26,33 +24,33 @@ namespace MonoMod.Utils { // .NET Framework can break member ordering if using Module.Resolve* on certain members. - private static object[] _NoArgs = Array.Empty(); - private static object[] _CacheGetterArgs = { /* MemberListType.All */ 0, /* name apparently always null? */ null }; + private static readonly object[] _NoArgs = Array.Empty(); + private static readonly object?[] _CacheGetterArgs = { /* MemberListType.All */ 0, /* name apparently always null? */ null }; - private static Type t_RuntimeModule = + private static readonly Type? t_RuntimeModule = typeof(Module).Assembly .GetType("System.Reflection.RuntimeModule"); - private static PropertyInfo p_RuntimeModule_RuntimeType = + private static readonly PropertyInfo? p_RuntimeModule_RuntimeType = typeof(Module).Assembly .GetType("System.Reflection.RuntimeModule") ?.GetProperty("RuntimeType", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - private static Type t_RuntimeType = + private static readonly Type? t_RuntimeType = typeof(Type).Assembly .GetType("System.RuntimeType"); - private static PropertyInfo p_RuntimeType_Cache = + private static readonly PropertyInfo? p_RuntimeType_Cache = typeof(Type).Assembly .GetType("System.RuntimeType") ?.GetProperty("Cache", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - private static MethodInfo m_RuntimeTypeCache_GetFieldList = + private static readonly MethodInfo? m_RuntimeTypeCache_GetFieldList = typeof(Type).Assembly .GetType("System.RuntimeType+RuntimeTypeCache") ?.GetMethod("GetFieldList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - private static MethodInfo m_RuntimeTypeCache_GetPropertyList = + private static readonly MethodInfo? m_RuntimeTypeCache_GetPropertyList = typeof(Type).Assembly .GetType("System.RuntimeType+RuntimeTypeCache") ?.GetMethod("GetPropertyList", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); @@ -65,37 +63,37 @@ namespace MonoMod.Utils harmony.Patch( original: typeof(Harmony).Assembly - .GetType("HarmonyLib.MethodBodyReader") + .GetType("HarmonyLib.MethodBodyReader", throwOnError: true)! .GetMethod("ReadOperand", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance), transpiler: new HarmonyMethod(typeof(MiniMonoModHotfix), nameof(ResolveTokenFix)) ); harmony.Patch( original: typeof(MonoMod.Utils.ReflectionHelper).Assembly - .GetType("MonoMod.Utils.DynamicMethodDefinition+<>c__DisplayClass3_0") + .GetType("MonoMod.Utils.DynamicMethodDefinition+<>c__DisplayClass3_0", throwOnError: true)! .GetMethod("<_CopyMethodToDefinition>g__ResolveTokenAs|1", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance), transpiler: new HarmonyMethod(typeof(MiniMonoModHotfix), nameof(ResolveTokenFix)) ); } - private static IEnumerable ResolveTokenFix(IEnumerable instrs) + private static IEnumerable ResolveTokenFix(IEnumerable instructions) { - MethodInfo getdecl = typeof(MiniMonoModHotfix).GetMethod(nameof(GetRealDeclaringType)); - MethodInfo fixup = typeof(MiniMonoModHotfix).GetMethod(nameof(FixReflectionCache)); + MethodInfo getRealDeclaringType = typeof(MiniMonoModHotfix).GetMethod(nameof(MiniMonoModHotfix.GetRealDeclaringType)) ?? throw new InvalidOperationException($"Can't get required method {nameof(MiniMonoModHotfix)}.{nameof(GetRealDeclaringType)}"); + MethodInfo fixReflectionCache = typeof(MiniMonoModHotfix).GetMethod(nameof(MiniMonoModHotfix.FixReflectionCache)) ?? throw new InvalidOperationException($"Can't get required method {nameof(MiniMonoModHotfix)}.{nameof(FixReflectionCache)}"); - foreach (CodeInstruction instr in instrs) + foreach (CodeInstruction instruction in instructions) { - yield return instr; + yield return instruction; - if (instr.operand is MethodInfo called) + if (instruction.operand is MethodInfo called) { switch (called.Name) { case "ResolveType": // type.FixReflectionCache(); yield return new CodeInstruction(OpCodes.Dup); - yield return new CodeInstruction(OpCodes.Call, fixup); + yield return new CodeInstruction(OpCodes.Call, fixReflectionCache); break; case "ResolveMember": @@ -103,15 +101,15 @@ namespace MonoMod.Utils case "ResolveField": // member.GetRealDeclaringType().FixReflectionCache(); yield return new CodeInstruction(OpCodes.Dup); - yield return new CodeInstruction(OpCodes.Call, getdecl); - yield return new CodeInstruction(OpCodes.Call, fixup); + yield return new CodeInstruction(OpCodes.Call, getRealDeclaringType); + yield return new CodeInstruction(OpCodes.Call, fixReflectionCache); break; } } } } - public static Type GetModuleType(this Module module) + public static Type? GetModuleType(this Module? module) { // Sadly we can't blindly resolve type 0x02000001 as the runtime throws ArgumentException. @@ -120,22 +118,21 @@ namespace MonoMod.Utils // .NET if (p_RuntimeModule_RuntimeType != null) - return (Type)p_RuntimeModule_RuntimeType.GetValue(module, _NoArgs); + return (Type?)p_RuntimeModule_RuntimeType.GetValue(module, _NoArgs); // The hotfix doesn't apply to Mono anyway, thus that's not copied over. return null; } - public static Type GetRealDeclaringType(this MemberInfo member) - => member.DeclaringType ?? member.Module.GetModuleType(); + public static Type? GetRealDeclaringType(this MemberInfo member) + { + return member.DeclaringType ?? member.Module.GetModuleType(); + } - public static void FixReflectionCache(this Type type) + public static void FixReflectionCache(this Type? type) { - if (t_RuntimeType == null || - p_RuntimeType_Cache == null || - m_RuntimeTypeCache_GetFieldList == null || - m_RuntimeTypeCache_GetPropertyList == null) + if (t_RuntimeType == null || p_RuntimeType_Cache == null || m_RuntimeTypeCache_GetFieldList == null || m_RuntimeTypeCache_GetPropertyList == null) return; for (; type != null; type = type.DeclaringType) @@ -145,21 +142,17 @@ namespace MonoMod.Utils if (!t_RuntimeType.IsInstanceOfType(type)) continue; - CacheFixEntry entry = _CacheFixed.GetValue(type, rt => { - CacheFixEntry entryNew = new(); - object cache; - Array properties, fields; - + CacheFixEntry entry = _CacheFixed.GetValue(type, rt => + { // All RuntimeTypes MUST have a cache, the getter is non-virtual, it creates on demand and asserts non-null. - entryNew.Cache = cache = p_RuntimeType_Cache.GetValue(rt, _NoArgs); - entryNew.Properties = properties = _GetArray(cache, m_RuntimeTypeCache_GetPropertyList); - entryNew.Fields = fields = _GetArray(cache, m_RuntimeTypeCache_GetFieldList); + object cache = MiniMonoModHotfix.p_RuntimeType_Cache.GetValue(rt, MiniMonoModHotfix._NoArgs)!; + Array properties = MiniMonoModHotfix._GetArray(cache, MiniMonoModHotfix.m_RuntimeTypeCache_GetPropertyList); + Array fields = MiniMonoModHotfix._GetArray(cache, MiniMonoModHotfix.m_RuntimeTypeCache_GetFieldList); _FixReflectionCacheOrder(properties); _FixReflectionCacheOrder(fields); - entryNew.NeedsVerify = false; - return entryNew; + return new CacheFixEntry(cache, properties, fields, needsVerify: false); }); if (entry.NeedsVerify && !_Verify(entry, type)) @@ -177,44 +170,43 @@ namespace MonoMod.Utils private static bool _Verify(CacheFixEntry entry, Type type) { - object cache; - Array properties, fields; - // The cache can sometimes be invalidated. // TODO: Figure out if only the arrays get replaced or if the entire cache object gets replaced! - if (entry.Cache != (cache = p_RuntimeType_Cache.GetValue(type, _NoArgs))) + object cache = p_RuntimeType_Cache!.GetValue(type, _NoArgs)!; + if (entry.Cache != cache) { entry.Cache = cache; - entry.Properties = _GetArray(cache, m_RuntimeTypeCache_GetPropertyList); - entry.Fields = _GetArray(cache, m_RuntimeTypeCache_GetFieldList); + entry.Properties = _GetArray(cache, m_RuntimeTypeCache_GetPropertyList!); + entry.Fields = _GetArray(cache, m_RuntimeTypeCache_GetFieldList!); return false; } - else if (entry.Properties != (properties = _GetArray(cache, m_RuntimeTypeCache_GetPropertyList))) + + Array properties = _GetArray(cache, m_RuntimeTypeCache_GetPropertyList!); + if (entry.Properties != properties) { entry.Properties = properties; - entry.Fields = _GetArray(cache, m_RuntimeTypeCache_GetFieldList); + entry.Fields = _GetArray(cache, m_RuntimeTypeCache_GetFieldList!); return false; - } - else if (entry.Fields != (fields = _GetArray(cache, m_RuntimeTypeCache_GetFieldList))) + + Array fields = _GetArray(cache, m_RuntimeTypeCache_GetFieldList!); + if (entry.Fields != fields) { entry.Fields = fields; return false; } - else - { - // Cache should still be the same, no re-fix necessary. - return true; - } + + // Cache should still be the same, no re-fix necessary. + return true; } private static Array _GetArray(object cache, MethodInfo getter) { // Get and discard once, otherwise we might not be getting the actual backing array. getter.Invoke(cache, _CacheGetterArgs); - return (Array)getter.Invoke(cache, _CacheGetterArgs); + return (Array)getter.Invoke(cache, _CacheGetterArgs)!; } private static void _FixReflectionCacheOrder(Array orig) where T : MemberInfo @@ -222,7 +214,7 @@ namespace MonoMod.Utils // Sort using a short-lived list. List list = new List(orig.Length); for (int i = 0; i < orig.Length; i++) - list.Add((T)orig.GetValue(i)); + list.Add((T)orig.GetValue(i)!); list.Sort((a, b) => a.MetadataToken - b.MetadataToken); @@ -232,10 +224,18 @@ namespace MonoMod.Utils private class CacheFixEntry { - public object Cache; + public object? Cache; public Array Properties; public Array Fields; public bool NeedsVerify; + + public CacheFixEntry(object? cache, Array properties, Array fields, bool needsVerify) + { + this.Cache = cache; + this.Properties = properties; + this.Fields = fields; + this.NeedsVerify = needsVerify; + } } } } diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index b7cec72c..f1b9af9a 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -1,5 +1,3 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -712,7 +710,7 @@ namespace StardewModdingAPI.Metadata bool isPaintMask = assetName.BaseName.EndsWith(paintMaskSuffix, StringComparison.OrdinalIgnoreCase); // get building type - string type = Path.GetFileName(assetName.BaseName)!; + string type = Path.GetFileName(assetName.BaseName); if (isPaintMask) type = type.Substring(0, type.Length - paintMaskSuffix.Length); @@ -749,7 +747,7 @@ namespace StardewModdingAPI.Metadata if (!ignoreWorld) { - foreach (var location in this.GetLocations()) + foreach (GameLocation location in this.GetLocations()) { foreach (MapSeat seat in location.mapSeats.Where(p => p != null)) { @@ -783,7 +781,7 @@ namespace StardewModdingAPI.Metadata // update sprites Texture2D texture = content.Load(assetName.BaseName); - foreach (var entry in critters) + foreach (Critter entry in critters) entry.sprite.spriteTexture = texture; return critters.Length; @@ -799,16 +797,16 @@ namespace StardewModdingAPI.Metadata foreach (GameLocation location in this.GetLocations()) { - IEnumerable doors = location.interiorDoors?.Doors; + IEnumerable? doors = location.interiorDoors?.Doors; if (doors == null) continue; - foreach (InteriorDoor door in doors) + foreach (InteriorDoor? door in doors) { if (door?.Sprite == null) continue; - string curKey = this.Reflection.GetField(door.Sprite, "textureName").GetValue(); + string? curKey = this.Reflection.GetField(door.Sprite, "textureName").GetValue(); if (this.IsSameBaseName(assetName, curKey)) door.Sprite.texture = texture.Value; } @@ -933,7 +931,7 @@ namespace StardewModdingAPI.Metadata // warping onto the wrong tile (or even off-screen) if a patch changes the farmhouse // map on location change. if (playerPos.HasValue) - Game1.player.Position = playerPos.Value; + Game1.player!.Position = playerPos.Value; } /// Reload the disposition data for matching NPCs. @@ -1003,7 +1001,11 @@ namespace StardewModdingAPI.Metadata { GameLocation adventureGuild = Game1.getLocationFromName("AdventureGuild"); if (adventureGuild != null) - characters.Add(new { Npc = this.Reflection.GetField(adventureGuild, "Gil").GetValue(), AssetName = gilKey }); + { + NPC? gil = this.Reflection.GetField(adventureGuild, "Gil").GetValue(); + if (gil != null) + characters.Add(new { Npc = gil, AssetName = gilKey }); + } } } @@ -1029,7 +1031,7 @@ namespace StardewModdingAPI.Metadata foreach (Farmer player in players) { - this.Reflection.GetField>>>(typeof(FarmerRenderer), "_recolorOffsets").GetValue().Remove(player.getTexture()); + this.Reflection.GetField>>>(typeof(FarmerRenderer), "_recolorOffsets").GetValue()?.Remove(player.getTexture()); player.FarmerRenderer.MarkSpriteDirty(); } @@ -1048,11 +1050,12 @@ namespace StardewModdingAPI.Metadata { // get suspension bridges field var field = this.Reflection.GetField>(location, nameof(IslandNorth.suspensionBridges), required: false); + // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- field is nullable when required: false if (field == null || !typeof(IEnumerable).IsAssignableFrom(field.FieldInfo.FieldType)) continue; // update textures - foreach (SuspensionBridge bridge in field.GetValue()) + foreach (SuspensionBridge bridge in field.GetValue()!) this.Reflection.GetField(bridge, "_texture").SetValue(texture.Value); } @@ -1158,7 +1161,7 @@ namespace StardewModdingAPI.Metadata Game1.samBandName = content.LoadString("Strings/StringsFromCSFiles:Game1.cs.2156"); Game1.elliottBookName = content.LoadString("Strings/StringsFromCSFiles:Game1.cs.2157"); - string[] dayNames = this.Reflection.GetField(typeof(Game1), "_shortDayDisplayName").GetValue(); + string[] dayNames = this.Reflection.GetField(typeof(Game1), "_shortDayDisplayName").GetValue()!; dayNames[0] = content.LoadString("Strings/StringsFromCSFiles:Game1.cs.3042"); dayNames[1] = content.LoadString("Strings/StringsFromCSFiles:Game1.cs.3043"); dayNames[2] = content.LoadString("Strings/StringsFromCSFiles:Game1.cs.3044"); @@ -1227,7 +1230,7 @@ namespace StardewModdingAPI.Metadata { foreach (Building building in buildableLocation.buildings) { - GameLocation indoors = building.indoors.Value; + GameLocation? indoors = building.indoors.Value; if (indoors != null) yield return new LocationInfo(indoors, building); } @@ -1238,19 +1241,19 @@ namespace StardewModdingAPI.Metadata /// Get whether two asset names are equivalent if you ignore the locale code. /// The first value to compare. /// The second value to compare. - private bool IsSameBaseName(IAssetName left, string right) + private bool IsSameBaseName(IAssetName? left, string? right) { if (left is null || right is null) return false; - IAssetName parsedB = this.ParseAssetNameOrNull(right); + IAssetName? parsedB = this.ParseAssetNameOrNull(right); return this.IsSameBaseName(left, parsedB); } /// Get whether two asset names are equivalent if you ignore the locale code. /// The first value to compare. /// The second value to compare. - private bool IsSameBaseName(IAssetName left, IAssetName right) + private bool IsSameBaseName(IAssetName? left, IAssetName? right) { if (left is null || right is null) return false; @@ -1260,7 +1263,7 @@ namespace StardewModdingAPI.Metadata /// Normalize an asset key to match the cache key and assert that it's valid, but don't raise an error for null or empty values. /// The asset key to normalize. - private IAssetName ParseAssetNameOrNull(string path) + private IAssetName? ParseAssetNameOrNull(string? path) { if (string.IsNullOrWhiteSpace(path)) return null; @@ -1270,7 +1273,7 @@ namespace StardewModdingAPI.Metadata /// Get the segments in a path (e.g. 'a/b' is 'a' and 'b'). /// The path to check. - private string[] GetSegments(string path) + private string[] GetSegments(string? path) { return path != null ? PathUtilities.GetSegments(path) @@ -1280,7 +1283,7 @@ namespace StardewModdingAPI.Metadata /// Load a texture, and dispose the old one if is enabled and it's different from the new instance. /// The previous texture to dispose. /// The asset key to load. - private Texture2D LoadAndDisposeIfNeeded(Texture2D oldTexture, string key) + private Texture2D LoadAndDisposeIfNeeded(Texture2D? oldTexture, string key) { // if aggressive memory optimizations are enabled, load the asset from the disposable // content manager and dispose the old instance if needed. @@ -1322,7 +1325,7 @@ namespace StardewModdingAPI.Metadata public GameLocation Location { get; } /// The building which contains the location, if any. - public Building ParentBuilding { get; } + public Building? ParentBuilding { get; } /********* @@ -1331,7 +1334,7 @@ namespace StardewModdingAPI.Metadata /// Construct an instance. /// The location instance. /// The building which contains the location, if any. - public LocationInfo(GameLocation location, Building parentBuilding) + public LocationInfo(GameLocation location, Building? parentBuilding) { this.Location = location; this.ParentBuilding = parentBuilding; diff --git a/src/SMAPI/Translation.cs b/src/SMAPI/Translation.cs index 2a0d2ce8..eb608f15 100644 --- a/src/SMAPI/Translation.cs +++ b/src/SMAPI/Translation.cs @@ -96,7 +96,7 @@ namespace StardewModdingAPI { string key = match.Groups[1].Value.Trim(); return tokenLookup.TryGetValue(key, out string? value) - ? value + ? (value ?? "") : match.Value; }); return new Translation(this.Locale, this.Key, text); diff --git a/src/SMAPI/Utilities/KeybindList.cs b/src/SMAPI/Utilities/KeybindList.cs index 18eeb9fd..aa12a37a 100644 --- a/src/SMAPI/Utilities/KeybindList.cs +++ b/src/SMAPI/Utilities/KeybindList.cs @@ -50,7 +50,7 @@ namespace StardewModdingAPI.Utilities /// The keybind string. See remarks on for format details. /// The parsed keybind list, if valid. /// The errors that occurred while parsing the input, if any. - public static bool TryParse(string input, [NotNullWhen(true)] out KeybindList? parsed, out string[] errors) + public static bool TryParse(string? input, [NotNullWhen(true)] out KeybindList? parsed, out string[] errors) { // empty input if (string.IsNullOrWhiteSpace(input)) diff --git a/src/SMAPI/Utilities/PerScreen.cs b/src/SMAPI/Utilities/PerScreen.cs index afe3ba91..799ff63b 100644 --- a/src/SMAPI/Utilities/PerScreen.cs +++ b/src/SMAPI/Utilities/PerScreen.cs @@ -1,8 +1,7 @@ -#nullable disable - using System; using System.Collections.Generic; using System.Linq; +using StardewModdingAPI.Framework; namespace StardewModdingAPI.Utilities { @@ -39,14 +38,28 @@ namespace StardewModdingAPI.Utilities ** Public methods *********/ /// Construct an instance. + /// Limitation with nullable reference types: when the underlying type is nullable, this sets the default value to null regardless of whether you marked the type parameter nullable. To avoid that, set the default value with the 'createNewState' argument instead. public PerScreen() - : this(null) { } + : this(null!) { } /// Construct an instance. /// Create the initial state for a screen. public PerScreen(Func createNewState) { - this.CreateNewState = createNewState ?? (() => default); + // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- required for backwards compatibility + if (createNewState is null) + { + SCore.DeprecationManager.Warn( + SCore.DeprecationManager.GetSourceNameFromStack(), + $"calling the {nameof(PerScreen)} constructor with null", + "3.14.0", + DeprecationLevel.Notice + ); + + createNewState = (() => default!); + } + + this.CreateNewState = createNewState; } /// Get all active values by screen ID. This doesn't initialize the value for a screen ID if it's not created yet. @@ -61,7 +74,7 @@ namespace StardewModdingAPI.Utilities public T GetValueForScreen(int screenId) { this.RemoveDeadScreens(); - return this.States.TryGetValue(screenId, out T state) + return this.States.TryGetValue(screenId, out T? state) ? state : this.States[screenId] = this.CreateNewState(); } -- cgit From 7c411b29bbe1323145d130eb8771c67617b6d3ee Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 13 Apr 2022 21:07:58 -0400 Subject: fix spelling warnings --- src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs | 2 ++ src/SMAPI.sln.DotSettings | 15 +++++++++++++++ src/SMAPI/Framework/ModHelpers/TranslationHelper.cs | 2 +- src/SMAPI/Framework/SChatBox.cs | 2 +- 4 files changed, 19 insertions(+), 2 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs index 4f872f1c..8e1538a5 100644 --- a/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs +++ b/src/SMAPI.Toolkit/Framework/GameScanning/GameScanner.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Xml.Linq; @@ -13,6 +14,7 @@ using Microsoft.Win32; namespace StardewModdingAPI.Toolkit.Framework.GameScanning { /// Finds installed game folders. + [SuppressMessage("ReSharper", "StringLiteralTypo", Justification = "These are valid game install paths.")] public class GameScanner { /********* diff --git a/src/SMAPI.sln.DotSettings b/src/SMAPI.sln.DotSettings index 866f2ed3..ad546665 100644 --- a/src/SMAPI.sln.DotSettings +++ b/src/SMAPI.sln.DotSettings @@ -25,8 +25,12 @@ True True True + True + True + True True True + True True True True @@ -36,6 +40,7 @@ True True True + True True True True @@ -45,6 +50,8 @@ True True True + True + True True True True @@ -57,8 +64,11 @@ True True True + True True True + True + True True True True @@ -67,15 +77,20 @@ True True True + True True True True + True True True True True True + True True + True True True + True \ No newline at end of file diff --git a/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs b/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs index 869664fe..d00ff279 100644 --- a/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs @@ -69,7 +69,7 @@ namespace StardewModdingAPI.Framework.ModHelpers return this; } - /// Set the current locale and precache translations. + /// Set the current locale and pre-cache translations. /// The current locale. /// The game's current language code. internal void SetLocale(string locale, LocalizedContentManager.LanguageCode localeEnum) diff --git a/src/SMAPI/Framework/SChatBox.cs b/src/SMAPI/Framework/SChatBox.cs index e000d1cd..7d6f2e5f 100644 --- a/src/SMAPI/Framework/SChatBox.cs +++ b/src/SMAPI/Framework/SChatBox.cs @@ -3,7 +3,7 @@ using StardewValley.Menus; namespace StardewModdingAPI.Framework { - /// SMAPI's implementation of the chatbox which intercepts errors for logging. + /// SMAPI's implementation of the chat box which intercepts errors for logging. internal class SChatBox : ChatBox { /********* -- cgit From 4de18b516fb12c3f842e72560b4d41aae7b7eff9 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 14 Apr 2022 18:13:27 -0400 Subject: fix nullable annotation (#837) --- src/SMAPI/Framework/ModHelpers/TranslationHelper.cs | 2 +- src/SMAPI/Framework/Translator.cs | 2 +- src/SMAPI/ITranslationHelper.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs b/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs index d00ff279..684837ff 100644 --- a/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs @@ -50,7 +50,7 @@ namespace StardewModdingAPI.Framework.ModHelpers } /// - public Translation Get(string key, object tokens) + public Translation Get(string key, object? tokens) { return this.Translator.Get(key, tokens); } diff --git a/src/SMAPI/Framework/Translator.cs b/src/SMAPI/Framework/Translator.cs index 794e3cde..b230a727 100644 --- a/src/SMAPI/Framework/Translator.cs +++ b/src/SMAPI/Framework/Translator.cs @@ -72,7 +72,7 @@ namespace StardewModdingAPI.Framework /// Get a translation for the current locale. /// The translation key. /// An object containing token key/value pairs. This can be an anonymous object (like new { value = 42, name = "Cranberries" }), a dictionary, or a class instance. - public Translation Get(string key, object tokens) + public Translation Get(string key, object? tokens) { return this.Get(key).Tokens(tokens); } diff --git a/src/SMAPI/ITranslationHelper.cs b/src/SMAPI/ITranslationHelper.cs index 32199910..8be8d2c1 100644 --- a/src/SMAPI/ITranslationHelper.cs +++ b/src/SMAPI/ITranslationHelper.cs @@ -29,7 +29,7 @@ namespace StardewModdingAPI /// Get a translation for the current locale. /// The translation key. /// An object containing token key/value pairs. This can be an anonymous object (like new { value = 42, name = "Cranberries" }), a dictionary, or a class instance. - Translation Get(string key, object tokens); + Translation Get(string key, object? tokens); /// Get a translation in every locale for which it's defined. /// The translation key. -- cgit From 1a3befa93e073b45e63781cc546cd7c52f316d7c Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 14 Apr 2022 23:00:30 -0400 Subject: track mod metadata reference in APIs for upcoming deprecation changes --- src/SMAPI.Tests/Core/TranslationTests.cs | 32 ++++++++++++++++++-- src/SMAPI/Framework/ModHelpers/BaseHelper.cs | 15 +++++++--- src/SMAPI/Framework/ModHelpers/CommandHelper.cs | 6 +--- src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 8 ++--- .../Framework/ModHelpers/ContentPackHelper.cs | 6 ++-- src/SMAPI/Framework/ModHelpers/DataHelper.cs | 6 ++-- .../Framework/ModHelpers/GameContentHelper.cs | 8 ++--- src/SMAPI/Framework/ModHelpers/InputHelper.cs | 6 ++-- src/SMAPI/Framework/ModHelpers/ModContentHelper.cs | 8 ++--- src/SMAPI/Framework/ModHelpers/ModHelper.cs | 8 ++--- .../Framework/ModHelpers/ModRegistryHelper.cs | 6 ++-- .../Framework/ModHelpers/MultiplayerHelper.cs | 6 ++-- src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs | 6 ++-- .../Framework/ModHelpers/TranslationHelper.cs | 6 ++-- src/SMAPI/Framework/SCore.cs | 34 +++++++++++----------- 15 files changed, 95 insertions(+), 66 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI.Tests/Core/TranslationTests.cs b/src/SMAPI.Tests/Core/TranslationTests.cs index ced1525a..a52df607 100644 --- a/src/SMAPI.Tests/Core/TranslationTests.cs +++ b/src/SMAPI.Tests/Core/TranslationTests.cs @@ -1,10 +1,14 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Linq; using NUnit.Framework; using StardewModdingAPI; +using StardewModdingAPI.Framework; using StardewModdingAPI.Framework.ModHelpers; +using StardewModdingAPI.Framework.ModLoading; +using StardewModdingAPI.Toolkit.Serialization.Models; using StardewValley; namespace SMAPI.Tests.Core @@ -33,7 +37,7 @@ namespace SMAPI.Tests.Core var data = new Dictionary>(); // act - ITranslationHelper helper = new TranslationHelper("ModID", "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data); + ITranslationHelper helper = new TranslationHelper(this.CreateModMetadata(), "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data); Translation translation = helper.Get("key"); Translation[]? translationList = helper.GetTranslations()?.ToArray(); @@ -56,7 +60,7 @@ namespace SMAPI.Tests.Core // act var actual = new Dictionary(); - TranslationHelper helper = new TranslationHelper("ModID", "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data); + TranslationHelper helper = new TranslationHelper(this.CreateModMetadata(), "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data); foreach (string locale in expected.Keys) { this.AssertSetLocale(helper, locale, LocalizedContentManager.LanguageCode.en); @@ -80,7 +84,7 @@ namespace SMAPI.Tests.Core // act var actual = new Dictionary(); - TranslationHelper helper = new TranslationHelper("ModID", "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data); + TranslationHelper helper = new TranslationHelper(this.CreateModMetadata(), "en", LocalizedContentManager.LanguageCode.en).SetTranslations(data); foreach (string locale in expected.Keys) { this.AssertSetLocale(helper, locale, LocalizedContentManager.LanguageCode.en); @@ -325,6 +329,28 @@ namespace SMAPI.Tests.Core return string.Format(Translation.PlaceholderText, key); } + /// Create a fake mod manifest. + private IModMetadata CreateModMetadata() + { + string id = $"smapi.unit-tests.fake-mod-{Guid.NewGuid():N}"; + + string tempPath = Path.Combine(Path.GetTempPath(), id); + return new ModMetadata( + displayName: "Mod Display Name", + directoryPath: tempPath, + rootPath: tempPath, + manifest: new Manifest( + uniqueID: id, + name: "Mod Name", + author: "Mod Author", + description: "Mod Description", + version: new SemanticVersion(1, 0, 0) + ), + dataRecord: null, + isIgnored: false + ); + } + /********* ** Test models diff --git a/src/SMAPI/Framework/ModHelpers/BaseHelper.cs b/src/SMAPI/Framework/ModHelpers/BaseHelper.cs index 5a3d4bed..12390976 100644 --- a/src/SMAPI/Framework/ModHelpers/BaseHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/BaseHelper.cs @@ -3,21 +3,28 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The common base class for mod helpers. internal abstract class BaseHelper : IModLinked { + /********* + ** Fields + *********/ + /// The mod using this instance. + protected readonly IModMetadata Mod; + + /********* ** Accessors *********/ /// - public string ModID { get; } + public string ModID => this.Mod.Manifest.UniqueID; /********* ** Protected methods *********/ /// Construct an instance. - /// The unique ID of the relevant mod. - protected BaseHelper(string modID) + /// The mod using this instance. + protected BaseHelper(IModMetadata mod) { - this.ModID = modID; + this.Mod = mod; } } } diff --git a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs index 7d25979c..319922a9 100644 --- a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs @@ -8,9 +8,6 @@ namespace StardewModdingAPI.Framework.ModHelpers /********* ** Fields *********/ - /// The mod using this instance. - private readonly IModMetadata Mod; - /// Manages console commands. private readonly CommandManager CommandManager; @@ -22,9 +19,8 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The mod using this instance. /// Manages console commands. public CommandHelper(IModMetadata mod, CommandManager commandManager) - : base(mod.Manifest.UniqueID) + : base(mod) { - this.Mod = mod; this.CommandManager = commandManager; } diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index b610b395..7cffcee1 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -93,14 +93,14 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Construct an instance. /// SMAPI's core content logic. /// The absolute path to the mod folder. - /// The unique ID of the relevant mod. + /// The mod using this instance. /// The friendly mod name for use in errors. /// Encapsulates monitoring and logging. /// Simplifies access to private code. - public ContentHelper(ContentCoordinator contentCore, string modFolderPath, string modID, string modName, IMonitor monitor, Reflector reflection) - : base(modID) + public ContentHelper(ContentCoordinator contentCore, string modFolderPath, IModMetadata mod, string modName, IMonitor monitor, Reflector reflection) + : base(mod) { - string managedAssetPrefix = contentCore.GetManagedAssetPrefix(modID); + string managedAssetPrefix = contentCore.GetManagedAssetPrefix(mod.Manifest.UniqueID); this.ContentCore = contentCore; this.GameContentManager = contentCore.CreateGameContentManager(managedAssetPrefix + ".content"); diff --git a/src/SMAPI/Framework/ModHelpers/ContentPackHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentPackHelper.cs index d39abc7d..9f4a7ceb 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentPackHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentPackHelper.cs @@ -22,11 +22,11 @@ namespace StardewModdingAPI.Framework.ModHelpers ** Public methods *********/ /// Construct an instance. - /// The unique ID of the relevant mod. + /// The mod using this instance. /// The content packs loaded for this mod. /// Create a temporary content pack. - public ContentPackHelper(string modID, Lazy contentPacks, Func createContentPack) - : base(modID) + public ContentPackHelper(IModMetadata mod, Lazy contentPacks, Func createContentPack) + : base(mod) { this.ContentPacks = contentPacks; this.CreateContentPack = createContentPack; diff --git a/src/SMAPI/Framework/ModHelpers/DataHelper.cs b/src/SMAPI/Framework/ModHelpers/DataHelper.cs index 92b3b398..2eaa940a 100644 --- a/src/SMAPI/Framework/ModHelpers/DataHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/DataHelper.cs @@ -26,11 +26,11 @@ namespace StardewModdingAPI.Framework.ModHelpers ** Public methods *********/ /// Construct an instance. - /// The unique ID of the relevant mod. + /// The mod using this instance. /// The absolute path to the mod folder. /// The absolute path to the mod folder. - public DataHelper(string modID, string modFolderPath, JsonHelper jsonHelper) - : base(modID) + public DataHelper(IModMetadata mod, string modFolderPath, JsonHelper jsonHelper) + : base(mod) { this.ModFolderPath = modFolderPath; this.JsonHelper = jsonHelper; diff --git a/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs index 4c1cde02..232e9287 100644 --- a/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs @@ -45,14 +45,14 @@ namespace StardewModdingAPI.Framework.ModHelpers *********/ /// Construct an instance. /// SMAPI's core content logic. - /// The unique ID of the relevant mod. + /// The mod using this instance. /// The friendly mod name for use in errors. /// Encapsulates monitoring and logging. /// Simplifies access to private code. - public GameContentHelper(ContentCoordinator contentCore, string modID, string modName, IMonitor monitor, Reflector reflection) - : base(modID) + public GameContentHelper(ContentCoordinator contentCore, IModMetadata mod, string modName, IMonitor monitor, Reflector reflection) + : base(mod) { - string managedAssetPrefix = contentCore.GetManagedAssetPrefix(modID); + string managedAssetPrefix = contentCore.GetManagedAssetPrefix(mod.Manifest.UniqueID); this.ContentCore = contentCore; this.GameContentManager = contentCore.CreateGameContentManager(managedAssetPrefix + ".content"); diff --git a/src/SMAPI/Framework/ModHelpers/InputHelper.cs b/src/SMAPI/Framework/ModHelpers/InputHelper.cs index 88caf4c3..6c158258 100644 --- a/src/SMAPI/Framework/ModHelpers/InputHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/InputHelper.cs @@ -18,10 +18,10 @@ namespace StardewModdingAPI.Framework.ModHelpers ** Public methods *********/ /// Construct an instance. - /// The unique ID of the relevant mod. + /// The mod using this instance. /// Manages the game's input state for the current player instance. That may not be the main player in split-screen mode. - public InputHelper(string modID, Func currentInputState) - : base(modID) + public InputHelper(IModMetadata mod, Func currentInputState) + : base(mod) { this.CurrentInputState = currentInputState; } diff --git a/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs index fc47f269..4a058a48 100644 --- a/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs @@ -36,15 +36,15 @@ namespace StardewModdingAPI.Framework.ModHelpers /// Construct an instance. /// SMAPI's core content logic. /// The absolute path to the mod folder. - /// The unique ID of the relevant mod. + /// The mod using this instance. /// The friendly mod name for use in errors. /// The game content manager used for map tilesheets not provided by the mod. /// A case-insensitive lookup of relative paths within the . /// Simplifies access to private code. - public ModContentHelper(ContentCoordinator contentCore, string modFolderPath, string modID, string modName, IContentManager gameContentManager, CaseInsensitivePathCache relativePathCache, Reflector reflection) - : base(modID) + public ModContentHelper(ContentCoordinator contentCore, string modFolderPath, IModMetadata mod, string modName, IContentManager gameContentManager, CaseInsensitivePathCache relativePathCache, Reflector reflection) + : base(mod) { - string managedAssetPrefix = contentCore.GetManagedAssetPrefix(modID); + string managedAssetPrefix = contentCore.GetManagedAssetPrefix(mod.Manifest.UniqueID); this.ContentCore = contentCore; this.ModContentManager = contentCore.CreateModContentManager(managedAssetPrefix, modName, modFolderPath, gameContentManager); diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index e1529a75..4eb91f05 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -77,7 +77,7 @@ namespace StardewModdingAPI.Framework.ModHelpers ** Public methods *********/ /// Construct an instance. - /// The mod's unique ID. + /// The mod using this instance. /// The full path to the mod's folder. /// Manages the game's input state for the current player instance. That may not be the main player in split-screen mode. /// Manages access to events raised by SMAPI. @@ -94,13 +94,13 @@ namespace StardewModdingAPI.Framework.ModHelpers /// An argument is null or empty. /// The path does not exist on disk. public ModHelper( - string modID, string modDirectory, Func currentInputState, IModEvents events, + IModMetadata mod, string modDirectory, Func 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) + : base(mod) { // validate directory if (string.IsNullOrWhiteSpace(modDirectory)) @@ -117,7 +117,7 @@ namespace StardewModdingAPI.Framework.ModHelpers this.ModContent = modContentHelper ?? throw new ArgumentNullException(nameof(modContentHelper)); this.ContentPacks = contentPackHelper ?? throw new ArgumentNullException(nameof(contentPackHelper)); this.Data = dataHelper ?? throw new ArgumentNullException(nameof(dataHelper)); - this.Input = new InputHelper(modID, currentInputState); + this.Input = new InputHelper(mod, currentInputState); this.ModRegistry = modRegistry ?? throw new ArgumentNullException(nameof(modRegistry)); this.ConsoleCommands = commandHelper ?? throw new ArgumentNullException(nameof(commandHelper)); this.Reflection = reflectionHelper ?? throw new ArgumentNullException(nameof(reflectionHelper)); diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 84899610..39cef758 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -26,12 +26,12 @@ namespace StardewModdingAPI.Framework.ModHelpers ** Public methods *********/ /// Construct an instance. - /// The unique ID of the relevant mod. + /// The mod using this instance. /// The underlying mod registry. /// Generates proxy classes to access mod APIs through an arbitrary interface. /// Encapsulates monitoring and logging for the mod. - public ModRegistryHelper(string modID, ModRegistry registry, InterfaceProxyFactory proxyFactory, IMonitor monitor) - : base(modID) + public ModRegistryHelper(IModMetadata mod, ModRegistry registry, InterfaceProxyFactory proxyFactory, IMonitor monitor) + : base(mod) { this.Registry = registry; this.ProxyFactory = proxyFactory; diff --git a/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs b/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs index a419327e..6900a1d2 100644 --- a/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/MultiplayerHelper.cs @@ -18,10 +18,10 @@ namespace StardewModdingAPI.Framework.ModHelpers ** Public methods *********/ /// Construct an instance. - /// The unique ID of the relevant mod. + /// The mod using this instance. /// SMAPI's core multiplayer utility. - public MultiplayerHelper(string modID, SMultiplayer multiplayer) - : base(modID) + public MultiplayerHelper(IModMetadata mod, SMultiplayer multiplayer) + : base(mod) { this.Multiplayer = multiplayer; } diff --git a/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs b/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs index af018c87..a559906b 100644 --- a/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ReflectionHelper.cs @@ -22,11 +22,11 @@ namespace StardewModdingAPI.Framework.ModHelpers ** Public methods *********/ /// Construct an instance. - /// The unique ID of the relevant mod. + /// The mod using this instance. /// The mod name for error messages. /// The underlying reflection helper. - public ReflectionHelper(string modID, string modName, Reflector reflector) - : base(modID) + public ReflectionHelper(IModMetadata mod, string modName, Reflector reflector) + : base(mod) { this.ModName = modName; this.Reflector = reflector; diff --git a/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs b/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs index 684837ff..ae49d651 100644 --- a/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/TranslationHelper.cs @@ -27,11 +27,11 @@ namespace StardewModdingAPI.Framework.ModHelpers ** Public methods *********/ /// Construct an instance. - /// The unique ID of the relevant mod. + /// The mod using this instance. /// The initial locale. /// The game's current language code. - public TranslationHelper(string modID, string locale, LocalizedContentManager.LanguageCode languageCode) - : base(modID) + public TranslationHelper(IModMetadata mod, string locale, LocalizedContentManager.LanguageCode languageCode) + : base(mod) { this.Translator = new Translator(); this.Translator.SetLocale(locale, languageCode); diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index bce7cffa..814ac56f 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1749,9 +1749,9 @@ namespace StardewModdingAPI.Framework { IMonitor monitor = this.LogManager.GetMonitor(mod.DisplayName); CaseInsensitivePathCache relativePathCache = this.ContentCore.GetCaseInsensitivePathCache(mod.DirectoryPath); - GameContentHelper gameContentHelper = new(this.ContentCore, manifest.UniqueID, mod.DisplayName, monitor, this.Reflection); - IModContentHelper modContentHelper = new ModContentHelper(this.ContentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager(), relativePathCache, this.Reflection); - TranslationHelper translationHelper = new(manifest.UniqueID, contentCore.GetLocale(), contentCore.Language); + GameContentHelper gameContentHelper = new(this.ContentCore, mod, mod.DisplayName, monitor, this.Reflection); + IModContentHelper modContentHelper = new ModContentHelper(this.ContentCore, mod.DirectoryPath, mod, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager(), relativePathCache, this.Reflection); + TranslationHelper translationHelper = new(mod, contentCore.GetLocale(), contentCore.Language); IContentPack contentPack = new ContentPack(mod.DirectoryPath, manifest, modContentHelper, translationHelper, jsonHelper, relativePathCache); mod.SetMod(contentPack, monitor, translationHelper); this.ModRegistry.Add(mod); @@ -1823,7 +1823,7 @@ namespace StardewModdingAPI.Framework // init mod helpers IMonitor monitor = this.LogManager.GetMonitor(mod.DisplayName); - TranslationHelper translationHelper = new(manifest.UniqueID, contentCore.GetLocale(), contentCore.Language); + TranslationHelper translationHelper = new(mod, contentCore.GetLocale(), contentCore.Language); IModHelper modHelper; { IContentPack CreateFakeContentPack(string packDirPath, IManifest packManifest) @@ -1832,9 +1832,9 @@ namespace StardewModdingAPI.Framework CaseInsensitivePathCache relativePathCache = this.ContentCore.GetCaseInsensitivePathCache(packDirPath); - GameContentHelper gameContentHelper = new(contentCore, packManifest.UniqueID, packManifest.Name, packMonitor, this.Reflection); - IModContentHelper packContentHelper = new ModContentHelper(contentCore, packDirPath, packManifest.UniqueID, packManifest.Name, gameContentHelper.GetUnderlyingContentManager(), relativePathCache, this.Reflection); - TranslationHelper packTranslationHelper = new(packManifest.UniqueID, contentCore.GetLocale(), contentCore.Language); + GameContentHelper gameContentHelper = new(contentCore, mod, packManifest.Name, packMonitor, this.Reflection); + IModContentHelper packContentHelper = new ModContentHelper(contentCore, packDirPath, mod, packManifest.Name, gameContentHelper.GetUnderlyingContentManager(), relativePathCache, this.Reflection); + TranslationHelper packTranslationHelper = new(mod, contentCore.GetLocale(), contentCore.Language); ContentPack contentPack = new(packDirPath, packManifest, packContentHelper, packTranslationHelper, this.Toolkit.JsonHelper, relativePathCache); this.ReloadTranslationsForTemporaryContentPack(mod, contentPack); @@ -1846,17 +1846,17 @@ namespace StardewModdingAPI.Framework ICommandHelper commandHelper = new CommandHelper(mod, this.CommandManager); CaseInsensitivePathCache relativePathCache = this.ContentCore.GetCaseInsensitivePathCache(mod.DirectoryPath); #pragma warning disable CS0612 // deprecated code - ContentHelper contentHelper = new(contentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, monitor, this.Reflection); + ContentHelper contentHelper = new(contentCore, mod.DirectoryPath, mod, mod.DisplayName, monitor, this.Reflection); #pragma warning restore CS0612 - GameContentHelper gameContentHelper = new(contentCore, manifest.UniqueID, mod.DisplayName, monitor, this.Reflection); - IModContentHelper modContentHelper = new ModContentHelper(contentCore, mod.DirectoryPath, manifest.UniqueID, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager(), relativePathCache, this.Reflection); - IContentPackHelper contentPackHelper = new ContentPackHelper(manifest.UniqueID, new Lazy(GetContentPacks), CreateFakeContentPack); - IDataHelper dataHelper = new DataHelper(manifest.UniqueID, mod.DirectoryPath, jsonHelper); - IReflectionHelper reflectionHelper = new ReflectionHelper(manifest.UniqueID, mod.DisplayName, this.Reflection); - IModRegistry modRegistryHelper = new ModRegistryHelper(manifest.UniqueID, this.ModRegistry, proxyFactory, monitor); - IMultiplayerHelper multiplayerHelper = new MultiplayerHelper(manifest.UniqueID, this.Multiplayer); - - modHelper = new ModHelper(manifest.UniqueID, mod.DirectoryPath, () => this.GetCurrentGameInstance().Input, events, contentHelper, gameContentHelper, modContentHelper, contentPackHelper, commandHelper, dataHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper); + GameContentHelper gameContentHelper = new(contentCore, mod, mod.DisplayName, monitor, this.Reflection); + IModContentHelper modContentHelper = new ModContentHelper(contentCore, mod.DirectoryPath, mod, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager(), relativePathCache, this.Reflection); + IContentPackHelper contentPackHelper = new ContentPackHelper(mod, new Lazy(GetContentPacks), CreateFakeContentPack); + IDataHelper dataHelper = new DataHelper(mod, mod.DirectoryPath, jsonHelper); + IReflectionHelper reflectionHelper = new ReflectionHelper(mod, mod.DisplayName, this.Reflection); + IModRegistry modRegistryHelper = new ModRegistryHelper(mod, this.ModRegistry, proxyFactory, monitor); + IMultiplayerHelper multiplayerHelper = new MultiplayerHelper(mod, this.Multiplayer); + + modHelper = new ModHelper(mod, mod.DirectoryPath, () => this.GetCurrentGameInstance().Input, events, contentHelper, gameContentHelper, modContentHelper, contentPackHelper, commandHelper, dataHelper, modRegistryHelper, reflectionHelper, multiplayerHelper, translationHelper); } // init mod -- cgit From fd136d34c5d4fbfc708eabf82a5eb9396d8a4756 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 14 Apr 2022 23:11:41 -0400 Subject: track full mod & stack metadata in queued deprecation warnings --- src/SMAPI/Constants.cs | 2 +- src/SMAPI/Framework/Content/AssetInfo.cs | 4 +-- src/SMAPI/Framework/DeprecationManager.cs | 33 +++++++++++-------------- src/SMAPI/Framework/DeprecationWarning.cs | 17 ++++++++----- src/SMAPI/Framework/ModHelpers/CommandHelper.cs | 2 +- src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 17 +++++-------- src/SMAPI/Framework/ModHelpers/ModHelper.cs | 2 +- src/SMAPI/Framework/SCore.cs | 6 ++--- src/SMAPI/Utilities/PerScreen.cs | 2 +- 9 files changed, 41 insertions(+), 44 deletions(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index fd2b813a..d40b97f4 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -83,7 +83,7 @@ namespace StardewModdingAPI get { SCore.DeprecationManager.Warn( - source: SCore.DeprecationManager.GetSourceNameFromStack(), + source: SCore.DeprecationManager.GetModFromStack(), nounPhrase: $"{nameof(Constants)}.{nameof(Constants.ExecutionPath)}", version: "3.14.0", severity: DeprecationLevel.Notice diff --git a/src/SMAPI/Framework/Content/AssetInfo.cs b/src/SMAPI/Framework/Content/AssetInfo.cs index 0f0e9bf3..16b71487 100644 --- a/src/SMAPI/Framework/Content/AssetInfo.cs +++ b/src/SMAPI/Framework/Content/AssetInfo.cs @@ -32,7 +32,7 @@ namespace StardewModdingAPI.Framework.Content get { SCore.DeprecationManager.Warn( - source: SCore.DeprecationManager.GetSourceNameFromStack(), + source: SCore.DeprecationManager.GetModFromStack(), nounPhrase: $"{nameof(IAssetInfo)}.{nameof(IAssetInfo.AssetName)}", version: "3.14.0", severity: DeprecationLevel.Notice @@ -68,7 +68,7 @@ namespace StardewModdingAPI.Framework.Content public bool AssetNameEquals(string path) { SCore.DeprecationManager.Warn( - source: SCore.DeprecationManager.GetSourceNameFromStack(), + source: SCore.DeprecationManager.GetModFromStack(), nounPhrase: $"{nameof(IAssetInfo)}.{nameof(IAssetInfo.AssetNameEquals)}", version: "3.14.0", severity: DeprecationLevel.Notice diff --git a/src/SMAPI/Framework/DeprecationManager.cs b/src/SMAPI/Framework/DeprecationManager.cs index 44b0ba2f..c80fdce7 100644 --- a/src/SMAPI/Framework/DeprecationManager.cs +++ b/src/SMAPI/Framework/DeprecationManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; namespace StardewModdingAPI.Framework @@ -35,34 +36,33 @@ namespace StardewModdingAPI.Framework this.ModRegistry = modRegistry; } - /// Get the source name for a mod from its unique ID. - public string? GetSourceNameFromStack() + /// Get a mod for the closest assembly registered as a source of deprecation warnings. + /// Returns the source name, or null if no registered assemblies were found. + public IModMetadata? GetModFromStack() { - return this.ModRegistry.GetFromStack()?.DisplayName; + return this.ModRegistry.GetFromStack(); } - /// Get the source name for a mod from its unique ID. + /// Get a mod from its unique ID. /// The mod's unique ID. - public string? GetSourceName(string modId) + public IModMetadata? GetMod(string modId) { - return this.ModRegistry.Get(modId)?.DisplayName; + return this.ModRegistry.Get(modId); } /// Log a deprecation warning. - /// The friendly mod name which used the deprecated code. + /// The mod which used the deprecated code, if known. /// A noun phrase describing what is deprecated. /// The SMAPI version which deprecated it. /// How deprecated the code is. - public void Warn(string? source, string nounPhrase, string version, DeprecationLevel severity) + public void Warn(IModMetadata? source, string nounPhrase, string version, DeprecationLevel severity) { - source ??= this.GetSourceNameFromStack() ?? ""; - // ignore if already warned if (!this.MarkWarned(source, nounPhrase, version)) return; // queue warning - this.QueuedWarnings.Add(new DeprecationWarning(source, nounPhrase, version, severity, Environment.StackTrace)); + this.QueuedWarnings.Add(new DeprecationWarning(source, nounPhrase, version, severity, new StackTrace(skipFrames: 1))); } /// A placeholder method used to track deprecated code for which a separate warning will be shown. @@ -104,7 +104,7 @@ namespace StardewModdingAPI.Framework else { this.Monitor.Log(message, level); - this.Monitor.Log(warning.StackTrace, LogLevel.Debug); + this.Monitor.Log(warning.StackTrace.ToString(), LogLevel.Debug); } } @@ -116,16 +116,13 @@ namespace StardewModdingAPI.Framework ** Private methods *********/ /// Mark a deprecation warning as already logged. - /// The friendly name of the assembly which used the deprecated code. + /// The mod which used the deprecated code. /// A noun phrase describing what is deprecated (e.g. "the Extensions.AsInt32 method"). /// The SMAPI version which deprecated it. /// Returns whether the deprecation was successfully marked as warned. Returns false if it was already marked. - private bool MarkWarned(string source, string nounPhrase, string version) + private bool MarkWarned(IModMetadata? source, string nounPhrase, string version) { - if (string.IsNullOrWhiteSpace(source)) - throw new InvalidOperationException("The deprecation source cannot be empty."); - - string key = $"{source}::{nounPhrase}::{version}"; + string key = $"{source?.DisplayName ?? ""}::{nounPhrase}::{version}"; if (this.LoggedDeprecations.Contains(key)) return false; this.LoggedDeprecations.Add(key); diff --git a/src/SMAPI/Framework/DeprecationWarning.cs b/src/SMAPI/Framework/DeprecationWarning.cs index 5201b06c..1e83f679 100644 --- a/src/SMAPI/Framework/DeprecationWarning.cs +++ b/src/SMAPI/Framework/DeprecationWarning.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; + namespace StardewModdingAPI.Framework { /// A deprecation warning for a mod. @@ -6,8 +8,11 @@ namespace StardewModdingAPI.Framework /********* ** Accessors *********/ - /// The affected mod's display name. - public string ModName { get; } + /// The affected mod. + public IModMetadata? Mod { get; } + + /// Get the display name for the affected mod. + public string ModName => this.Mod?.DisplayName ?? ""; /// A noun phrase describing what is deprecated. public string NounPhrase { get; } @@ -19,21 +24,21 @@ namespace StardewModdingAPI.Framework public DeprecationLevel Level { get; } /// The stack trace when the deprecation warning was raised. - public string StackTrace { get; } + public StackTrace StackTrace { get; } /********* ** Public methods *********/ /// Construct an instance. - /// The affected mod's display name. + /// The affected mod. /// A noun phrase describing what is deprecated. /// The SMAPI version which deprecated it. /// The deprecation level for the affected code. /// The stack trace when the deprecation warning was raised. - public DeprecationWarning(string modName, string nounPhrase, string version, DeprecationLevel level, string stackTrace) + public DeprecationWarning(IModMetadata? mod, string nounPhrase, string version, DeprecationLevel level, StackTrace stackTrace) { - this.ModName = modName; + this.Mod = mod; this.NounPhrase = nounPhrase; this.Version = version; this.Level = level; diff --git a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs index 319922a9..e430fb1c 100644 --- a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs @@ -36,7 +36,7 @@ namespace StardewModdingAPI.Framework.ModHelpers public bool Trigger(string name, string[] arguments) { SCore.DeprecationManager.Warn( - source: SCore.DeprecationManager.GetSourceName(this.ModID), + source: SCore.DeprecationManager.GetMod(this.ModID), nounPhrase: $"{nameof(IModHelper)}.{nameof(IModHelper.ConsoleCommands)}.{nameof(ICommandHelper.Trigger)}", version: "3.8.1", severity: DeprecationLevel.Notice diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index 7cffcee1..534ac138 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -29,9 +29,6 @@ namespace StardewModdingAPI.Framework.ModHelpers /// A content manager for this mod which manages files from the mod's folder. private readonly ModContentManager ModContentManager; - /// The friendly mod name for use in errors. - private readonly string ModName; - /// Encapsulates monitoring and logging. private readonly IMonitor Monitor; @@ -60,7 +57,7 @@ namespace StardewModdingAPI.Framework.ModHelpers get { SCore.DeprecationManager.Warn( - source: this.ModName, + source: this.Mod, nounPhrase: $"{nameof(IContentHelper)}.{nameof(IContentHelper.AssetLoaders)}", version: "3.14.0", severity: DeprecationLevel.Notice @@ -76,7 +73,7 @@ namespace StardewModdingAPI.Framework.ModHelpers get { SCore.DeprecationManager.Warn( - source: this.ModName, + source: this.Mod, nounPhrase: $"{nameof(IContentHelper)}.{nameof(IContentHelper.AssetEditors)}", version: "3.14.0", severity: DeprecationLevel.Notice @@ -94,18 +91,16 @@ namespace StardewModdingAPI.Framework.ModHelpers /// SMAPI's core content logic. /// The absolute path to the mod folder. /// The mod using this instance. - /// The friendly mod name for use in errors. /// Encapsulates monitoring and logging. /// Simplifies access to private code. - public ContentHelper(ContentCoordinator contentCore, string modFolderPath, IModMetadata mod, string modName, IMonitor monitor, Reflector reflection) + public ContentHelper(ContentCoordinator contentCore, string modFolderPath, IModMetadata mod, IMonitor monitor, Reflector reflection) : base(mod) { string managedAssetPrefix = contentCore.GetManagedAssetPrefix(mod.Manifest.UniqueID); this.ContentCore = contentCore; this.GameContentManager = contentCore.CreateGameContentManager(managedAssetPrefix + ".content"); - this.ModContentManager = contentCore.CreateModContentManager(managedAssetPrefix, modName, modFolderPath, this.GameContentManager); - this.ModName = modName; + this.ModContentManager = contentCore.CreateModContentManager(managedAssetPrefix, this.Mod.DisplayName, modFolderPath, this.GameContentManager); this.Monitor = monitor; this.Reflection = reflection; } @@ -128,12 +123,12 @@ namespace StardewModdingAPI.Framework.ModHelpers return this.ModContentManager.LoadExact(assetName, useCache: false); default: - throw new SContentLoadException($"{this.ModName} failed loading content asset '{key}' from {source}: unknown content source '{source}'."); + throw new SContentLoadException($"{this.Mod.DisplayName} failed loading content asset '{key}' from {source}: unknown content source '{source}'."); } } catch (Exception ex) when (ex is not SContentLoadException) { - throw new SContentLoadException($"{this.ModName} failed loading content asset '{key}' from {source}.", ex); + throw new SContentLoadException($"{this.Mod.DisplayName} failed loading content asset '{key}' from {source}.", ex); } } diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index 4eb91f05..5b450c36 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -32,7 +32,7 @@ namespace StardewModdingAPI.Framework.ModHelpers get { SCore.DeprecationManager.Warn( - source: SCore.DeprecationManager.GetSourceName(this.ModID), + source: SCore.DeprecationManager.GetMod(this.ModID), nounPhrase: $"{nameof(IModHelper)}.{nameof(IModHelper.Content)}", version: "3.14.0", severity: DeprecationLevel.Notice diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 814ac56f..990fe5ea 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1598,7 +1598,7 @@ namespace StardewModdingAPI.Framework if (metadata.Mod is IAssetEditor editor) { SCore.DeprecationManager.Warn( - source: metadata.DisplayName, + source: metadata, nounPhrase: $"{nameof(IAssetEditor)}", version: "3.14.0", severity: DeprecationLevel.Notice @@ -1610,7 +1610,7 @@ namespace StardewModdingAPI.Framework if (metadata.Mod is IAssetLoader loader) { SCore.DeprecationManager.Warn( - source: metadata.DisplayName, + source: metadata, nounPhrase: $"{nameof(IAssetLoader)}", version: "3.14.0", severity: DeprecationLevel.Notice @@ -1846,7 +1846,7 @@ namespace StardewModdingAPI.Framework ICommandHelper commandHelper = new CommandHelper(mod, this.CommandManager); CaseInsensitivePathCache relativePathCache = this.ContentCore.GetCaseInsensitivePathCache(mod.DirectoryPath); #pragma warning disable CS0612 // deprecated code - ContentHelper contentHelper = new(contentCore, mod.DirectoryPath, mod, mod.DisplayName, monitor, this.Reflection); + ContentHelper contentHelper = new(contentCore, mod.DirectoryPath, mod, monitor, this.Reflection); #pragma warning restore CS0612 GameContentHelper gameContentHelper = new(contentCore, mod, mod.DisplayName, monitor, this.Reflection); IModContentHelper modContentHelper = new ModContentHelper(contentCore, mod.DirectoryPath, mod, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager(), relativePathCache, this.Reflection); diff --git a/src/SMAPI/Utilities/PerScreen.cs b/src/SMAPI/Utilities/PerScreen.cs index ba4f3325..6c2e436b 100644 --- a/src/SMAPI/Utilities/PerScreen.cs +++ b/src/SMAPI/Utilities/PerScreen.cs @@ -96,7 +96,7 @@ namespace StardewModdingAPI.Utilities if (!nullExpected) { SCore.DeprecationManager.Warn( - SCore.DeprecationManager.GetSourceNameFromStack(), + SCore.DeprecationManager.GetModFromStack(), $"calling the {nameof(PerScreen)} constructor with null", "3.14.0", DeprecationLevel.Notice -- cgit From 95d7ba8935ac7214805147e694353206a56bddb7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 16 Apr 2022 14:07:09 -0400 Subject: move case-insensitive path lookup into toolkit for reuse --- .../Utilities/CaseInsensitivePathLookup.cs | 137 +++++++++++++++++++++ src/SMAPI/Framework/ContentCoordinator.cs | 4 +- .../Framework/ContentManagers/ModContentManager.cs | 6 +- src/SMAPI/Framework/ContentPack.cs | 6 +- src/SMAPI/Framework/ModHelpers/ModContentHelper.cs | 6 +- src/SMAPI/Framework/ModLoading/ModResolver.cs | 4 +- src/SMAPI/Framework/SCore.cs | 8 +- src/SMAPI/Utilities/CaseInsensitivePathCache.cs | 137 --------------------- 8 files changed, 154 insertions(+), 154 deletions(-) create mode 100644 src/SMAPI.Toolkit/Utilities/CaseInsensitivePathLookup.cs delete mode 100644 src/SMAPI/Utilities/CaseInsensitivePathCache.cs (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI.Toolkit/Utilities/CaseInsensitivePathLookup.cs b/src/SMAPI.Toolkit/Utilities/CaseInsensitivePathLookup.cs new file mode 100644 index 00000000..2e149e3c --- /dev/null +++ b/src/SMAPI.Toolkit/Utilities/CaseInsensitivePathLookup.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace StardewModdingAPI.Toolkit.Utilities +{ + /// Provides an API for case-insensitive relative path lookups within a root directory. + internal class CaseInsensitivePathLookup + { + /********* + ** Fields + *********/ + /// The root directory path for relative paths. + private readonly string RootPath; + + /// A case-insensitive lookup of file paths within the . 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. + private readonly Lazy> RelativePathCache; + + /// The case-insensitive path caches by root path. + private static readonly Dictionary CachedRoots = new(StringComparer.OrdinalIgnoreCase); + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The root directory path for relative paths. + public CaseInsensitivePathLookup(string rootPath) + { + this.RootPath = rootPath; + this.RelativePathCache = new(this.GetRelativePathCache); + } + + /// Get the exact capitalization for a given relative file path. + /// The relative path. + /// Returns the resolved path in file path format, else the normalized . + public string GetFilePath(string relativePath) + { + return this.GetImpl(PathUtilities.NormalizePath(relativePath)); + } + + /// Get the exact capitalization for a given asset name. + /// The relative path. + /// Returns the resolved path in asset name format, else the normalized . + public string GetAssetName(string relativePath) + { + return this.GetImpl(PathUtilities.NormalizeAssetName(relativePath)); + } + + /// Add a relative path that was just created by a SMAPI API. + /// The relative path. This must already be normalized in asset name or file path format. + 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); + } + + /// Get a cached dictionary of relative paths within a root path, for case-insensitive file lookups. + /// The root path to scan. + public static CaseInsensitivePathLookup GetCachedFor(string rootPath) + { + rootPath = PathUtilities.NormalizePath(rootPath); + + if (!CaseInsensitivePathLookup.CachedRoots.TryGetValue(rootPath, out CaseInsensitivePathLookup? cache)) + CaseInsensitivePathLookup.CachedRoots[rootPath] = cache = new CaseInsensitivePathLookup(rootPath); + + return cache; + } + + + /********* + ** Private methods + *********/ + /// Get the exact capitalization for a given relative path. + /// The relative path. This must already be normalized into asset name or file path format (i.e. using or respectively). + /// Returns the resolved path in the same format if found, else returns the path as-is. + 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; + + // keep capitalization as-is + if (File.Exists(Path.Combine(this.RootPath, relativePath))) + { + // file exists but isn't cached for some reason + // cache it now so any later references to it are case-insensitive + this.CacheRawPath(this.RelativePathCache.Value, relativePath); + } + return relativePath; + } + + /// Get a case-insensitive lookup of file paths (see ). + private Dictionary GetRelativePathCache() + { + Dictionary 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; + } + + /// Add a raw relative path to the cache. + /// The cache to update. + /// The relative path to cache, with its exact filesystem capitalization. + private void CacheRawPath(IDictionary cache, string relativePath) + { + string filePath = PathUtilities.NormalizePath(relativePath); + string assetName = PathUtilities.NormalizeAssetName(relativePath); + + cache[filePath] = filePath; + cache[assetName] = assetName; + } + } +} diff --git a/src/SMAPI/Framework/ContentCoordinator.cs b/src/SMAPI/Framework/ContentCoordinator.cs index f83d4090..aade7b36 100644 --- a/src/SMAPI/Framework/ContentCoordinator.cs +++ b/src/SMAPI/Framework/ContentCoordinator.cs @@ -15,7 +15,7 @@ using StardewModdingAPI.Framework.Utilities; using StardewModdingAPI.Internal; using StardewModdingAPI.Metadata; using StardewModdingAPI.Toolkit.Serialization; -using StardewModdingAPI.Utilities; +using StardewModdingAPI.Toolkit.Utilities; using StardewValley; using StardewValley.GameData; using xTile; @@ -208,7 +208,7 @@ namespace StardewModdingAPI.Framework jsonHelper: this.JsonHelper, onDisposing: this.OnDisposing, aggressiveMemoryOptimizations: this.AggressiveMemoryOptimizations, - relativePathCache: CaseInsensitivePathCache.GetFor(rootDirectory) + relativePathCache: CaseInsensitivePathLookup.GetCachedFor(rootDirectory) ); this.ContentManagers.Add(manager); return manager; diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index f0f4bce9..8f64c5a8 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -9,7 +9,7 @@ using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Toolkit.Serialization; -using StardewModdingAPI.Utilities; +using StardewModdingAPI.Toolkit.Utilities; using StardewValley; using xTile; using xTile.Format; @@ -33,7 +33,7 @@ namespace StardewModdingAPI.Framework.ContentManagers private readonly IContentManager GameContentManager; /// A case-insensitive lookup of relative paths within the . - private readonly CaseInsensitivePathCache RelativePathCache; + private readonly CaseInsensitivePathLookup RelativePathCache; /// If a map tilesheet's image source has no file extensions, the file extensions to check for in the local mod folder. private readonly string[] LocalTilesheetExtensions = { ".png", ".xnb" }; @@ -56,7 +56,7 @@ namespace StardewModdingAPI.Framework.ContentManagers /// A callback to invoke when the content manager is being disposed. /// Whether to enable more aggressive memory optimizations. /// A case-insensitive lookup of relative paths within the . - public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onDisposing, bool aggressiveMemoryOptimizations, CaseInsensitivePathCache relativePathCache) + public ModContentManager(string name, IContentManager gameContentManager, IServiceProvider serviceProvider, string modName, string rootDirectory, CultureInfo currentCulture, ContentCoordinator coordinator, IMonitor monitor, Reflector reflection, JsonHelper jsonHelper, Action onDisposing, bool aggressiveMemoryOptimizations, CaseInsensitivePathLookup relativePathCache) : base(name, serviceProvider, rootDirectory, currentCulture, coordinator, monitor, reflection, onDisposing, isNamespaced: true, aggressiveMemoryOptimizations: aggressiveMemoryOptimizations) { this.GameContentManager = gameContentManager; diff --git a/src/SMAPI/Framework/ContentPack.cs b/src/SMAPI/Framework/ContentPack.cs index 2cfd5cce..dde33c95 100644 --- a/src/SMAPI/Framework/ContentPack.cs +++ b/src/SMAPI/Framework/ContentPack.cs @@ -2,7 +2,7 @@ using System; using System.IO; using StardewModdingAPI.Framework.ModHelpers; using StardewModdingAPI.Toolkit.Serialization; -using StardewModdingAPI.Utilities; +using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingAPI.Framework { @@ -16,7 +16,7 @@ namespace StardewModdingAPI.Framework private readonly JsonHelper JsonHelper; /// A case-insensitive lookup of relative paths within the . - private readonly CaseInsensitivePathCache RelativePathCache; + private readonly CaseInsensitivePathLookup RelativePathCache; /********* @@ -48,7 +48,7 @@ namespace StardewModdingAPI.Framework /// Provides translations stored in the content pack's i18n folder. /// Encapsulates SMAPI's JSON file parsing. /// A case-insensitive lookup of relative paths within the . - public ContentPack(string directoryPath, IManifest manifest, IModContentHelper content, TranslationHelper translation, JsonHelper jsonHelper, CaseInsensitivePathCache relativePathCache) + public ContentPack(string directoryPath, IManifest manifest, IModContentHelper content, TranslationHelper translation, JsonHelper jsonHelper, CaseInsensitivePathLookup relativePathCache) { this.DirectoryPath = directoryPath; this.Manifest = manifest; diff --git a/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs index 4a058a48..def0b728 100644 --- a/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModContentHelper.cs @@ -4,7 +4,7 @@ using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.ContentManagers; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Reflection; -using StardewModdingAPI.Utilities; +using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingAPI.Framework.ModHelpers { @@ -24,7 +24,7 @@ namespace StardewModdingAPI.Framework.ModHelpers private readonly string ModName; /// A case-insensitive lookup of relative paths within the . - private readonly CaseInsensitivePathCache RelativePathCache; + private readonly CaseInsensitivePathLookup RelativePathCache; /// Simplifies access to private code. private readonly Reflector Reflection; @@ -41,7 +41,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The game content manager used for map tilesheets not provided by the mod. /// A case-insensitive lookup of relative paths within the . /// Simplifies access to private code. - public ModContentHelper(ContentCoordinator contentCore, string modFolderPath, IModMetadata mod, string modName, IContentManager gameContentManager, CaseInsensitivePathCache relativePathCache, Reflector reflection) + public ModContentHelper(ContentCoordinator contentCore, string modFolderPath, IModMetadata mod, string modName, IContentManager gameContentManager, CaseInsensitivePathLookup relativePathCache, Reflector reflection) : base(mod) { string managedAssetPrefix = contentCore.GetManagedAssetPrefix(mod.Manifest.UniqueID); diff --git a/src/SMAPI/Framework/ModLoading/ModResolver.cs b/src/SMAPI/Framework/ModLoading/ModResolver.cs index e3c7873c..4a02e90d 100644 --- a/src/SMAPI/Framework/ModLoading/ModResolver.cs +++ b/src/SMAPI/Framework/ModLoading/ModResolver.cs @@ -8,7 +8,7 @@ using StardewModdingAPI.Toolkit.Framework.ModData; using StardewModdingAPI.Toolkit.Framework.ModScanning; using StardewModdingAPI.Toolkit.Framework.UpdateData; using StardewModdingAPI.Toolkit.Serialization.Models; -using StardewModdingAPI.Utilities; +using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingAPI.Framework.ModLoading { @@ -141,7 +141,7 @@ namespace StardewModdingAPI.Framework.ModLoading } // file doesn't exist - string fileName = CaseInsensitivePathCache.GetFor(mod.DirectoryPath).GetFilePath(mod.Manifest.EntryDll!); + string fileName = CaseInsensitivePathLookup.GetCachedFor(mod.DirectoryPath).GetFilePath(mod.Manifest.EntryDll!); if (!File.Exists(Path.Combine(mod.DirectoryPath, fileName))) { mod.SetStatus(ModMetadataStatus.Failed, ModFailReason.InvalidManifest, $"its DLL '{mod.Manifest.EntryDll}' doesn't exist."); diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index d2890c29..b3c9087f 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1748,7 +1748,7 @@ namespace StardewModdingAPI.Framework if (mod.IsContentPack) { IMonitor monitor = this.LogManager.GetMonitor(mod.DisplayName); - CaseInsensitivePathCache relativePathCache = CaseInsensitivePathCache.GetFor(mod.DirectoryPath); + CaseInsensitivePathLookup relativePathCache = CaseInsensitivePathLookup.GetCachedFor(mod.DirectoryPath); GameContentHelper gameContentHelper = new(this.ContentCore, mod, mod.DisplayName, monitor, this.Reflection); IModContentHelper modContentHelper = new ModContentHelper(this.ContentCore, mod.DirectoryPath, mod, mod.DisplayName, gameContentHelper.GetUnderlyingContentManager(), relativePathCache, this.Reflection); TranslationHelper translationHelper = new(mod, contentCore.GetLocale(), contentCore.Language); @@ -1767,7 +1767,7 @@ namespace StardewModdingAPI.Framework // get mod info string assemblyPath = Path.Combine( mod.DirectoryPath, - CaseInsensitivePathCache.GetFor(mod.DirectoryPath).GetFilePath(manifest.EntryDll!) + CaseInsensitivePathLookup.GetCachedFor(mod.DirectoryPath).GetFilePath(manifest.EntryDll!) ); // load mod @@ -1833,7 +1833,7 @@ namespace StardewModdingAPI.Framework { IMonitor packMonitor = this.LogManager.GetMonitor(packManifest.Name); - CaseInsensitivePathCache relativePathCache = CaseInsensitivePathCache.GetFor(packDirPath); + CaseInsensitivePathLookup relativePathCache = CaseInsensitivePathLookup.GetCachedFor(packDirPath); GameContentHelper gameContentHelper = new(contentCore, mod, packManifest.Name, packMonitor, this.Reflection); IModContentHelper packContentHelper = new ModContentHelper(contentCore, packDirPath, mod, packManifest.Name, gameContentHelper.GetUnderlyingContentManager(), relativePathCache, this.Reflection); @@ -1847,7 +1847,7 @@ namespace StardewModdingAPI.Framework IModEvents events = new ModEvents(mod, this.EventManager); ICommandHelper commandHelper = new CommandHelper(mod, this.CommandManager); - CaseInsensitivePathCache relativePathCache = CaseInsensitivePathCache.GetFor(mod.DirectoryPath); + CaseInsensitivePathLookup relativePathCache = CaseInsensitivePathLookup.GetCachedFor(mod.DirectoryPath); #pragma warning disable CS0612 // deprecated code ContentHelper contentHelper = new(contentCore, mod.DirectoryPath, mod, monitor, this.Reflection); #pragma warning restore CS0612 diff --git a/src/SMAPI/Utilities/CaseInsensitivePathCache.cs b/src/SMAPI/Utilities/CaseInsensitivePathCache.cs deleted file mode 100644 index 04fdcfae..00000000 --- a/src/SMAPI/Utilities/CaseInsensitivePathCache.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace StardewModdingAPI.Utilities -{ - /// Provides an API for case-insensitive relative path lookups within a root directory. - internal class CaseInsensitivePathCache - { - /********* - ** Fields - *********/ - /// The root directory path for relative paths. - private readonly string RootPath; - - /// A case-insensitive lookup of file paths within the . 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. - private readonly Lazy> RelativePathCache; - - /// The case-insensitive path caches by root path. - private static readonly Dictionary CachesByRootPath = new(StringComparer.OrdinalIgnoreCase); - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The root directory path for relative paths. - public CaseInsensitivePathCache(string rootPath) - { - this.RootPath = rootPath; - this.RelativePathCache = new(this.GetRelativePathCache); - } - - /// Get the exact capitalization for a given relative file path. - /// The relative path. - /// Returns the resolved path in file path format, else the normalized . - public string GetFilePath(string relativePath) - { - return this.GetImpl(PathUtilities.NormalizePath(relativePath)); - } - - /// Get the exact capitalization for a given asset name. - /// The relative path. - /// Returns the resolved path in asset name format, else the normalized . - public string GetAssetName(string relativePath) - { - return this.GetImpl(PathUtilities.NormalizeAssetName(relativePath)); - } - - /// Add a relative path that was just created by a SMAPI API. - /// The relative path. This must already be normalized in asset name or file path format. - 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); - } - - /// Get a cached dictionary of relative paths within a root path, for case-insensitive file lookups. - /// The root path to scan. - public static CaseInsensitivePathCache GetFor(string rootPath) - { - rootPath = PathUtilities.NormalizePath(rootPath); - - if (!CaseInsensitivePathCache.CachesByRootPath.TryGetValue(rootPath, out CaseInsensitivePathCache? cache)) - CaseInsensitivePathCache.CachesByRootPath[rootPath] = cache = new CaseInsensitivePathCache(rootPath); - - return cache; - } - - - /********* - ** Private methods - *********/ - /// Get the exact capitalization for a given relative path. - /// The relative path. This must already be normalized into asset name or file path format (i.e. using or respectively). - /// Returns the resolved path in the same format if found, else returns the path as-is. - 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; - - // keep capitalization as-is - if (File.Exists(Path.Combine(this.RootPath, relativePath))) - { - // file exists but isn't cached for some reason - // cache it now so any later references to it are case-insensitive - this.CacheRawPath(this.RelativePathCache.Value, relativePath); - } - return relativePath; - } - - /// Get a case-insensitive lookup of file paths (see ). - private Dictionary GetRelativePathCache() - { - Dictionary 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; - } - - /// Add a raw relative path to the cache. - /// The cache to update. - /// The relative path to cache, with its exact filesystem capitalization. - private void CacheRawPath(IDictionary cache, string relativePath) - { - string filePath = PathUtilities.NormalizePath(relativePath); - string assetName = PathUtilities.NormalizeAssetName(relativePath); - - cache[filePath] = filePath; - cache[assetName] = assetName; - } - } -} -- cgit From 0a69cb4bf71e6e822e595141ce5f24009e509246 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 16 Apr 2022 19:15:50 -0400 Subject: allow switching between Pintail & original API proxying --- src/SMAPI.Tests/Core/InterfaceProxyTests.cs | 68 +++++++----- src/SMAPI.sln.DotSettings | 2 + .../Framework/ModHelpers/ModRegistryHelper.cs | 4 +- src/SMAPI/Framework/Models/SConfig.cs | 10 +- .../Framework/Reflection/IInterfaceProxyFactory.cs | 17 +++ .../Framework/Reflection/InterfaceProxyFactory.cs | 10 +- .../Reflection/OriginalInterfaceProxyBuilder.cs | 118 +++++++++++++++++++++ .../Reflection/OriginalInterfaceProxyFactory.cs | 57 ++++++++++ src/SMAPI/Framework/SCore.cs | 6 +- src/SMAPI/SMAPI.config.json | 6 ++ 10 files changed, 259 insertions(+), 39 deletions(-) create mode 100644 src/SMAPI/Framework/Reflection/IInterfaceProxyFactory.cs create mode 100644 src/SMAPI/Framework/Reflection/OriginalInterfaceProxyBuilder.cs create mode 100644 src/SMAPI/Framework/Reflection/OriginalInterfaceProxyFactory.cs (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI.Tests/Core/InterfaceProxyTests.cs b/src/SMAPI.Tests/Core/InterfaceProxyTests.cs index 8d27f6af..6be97526 100644 --- a/src/SMAPI.Tests/Core/InterfaceProxyTests.cs +++ b/src/SMAPI.Tests/Core/InterfaceProxyTests.cs @@ -29,6 +29,12 @@ namespace SMAPI.Tests.Core /// The random number generator with which to create sample values. private readonly Random Random = new(); + /// Sample user inputs for season names. + private static readonly IInterfaceProxyFactory[] ProxyFactories = { + new InterfaceProxyFactory(), + new OriginalInterfaceProxyFactory() + }; + /********* ** Unit tests @@ -37,8 +43,9 @@ namespace SMAPI.Tests.Core ** Events ****/ /// Assert that an event field can be proxied correctly. + /// The proxy factory to test. [Test] - public void CanProxy_EventField() + public void CanProxy_EventField([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory) { // arrange ProviderMod providerMod = new(); @@ -46,7 +53,7 @@ namespace SMAPI.Tests.Core int expectedValue = this.Random.Next(); // act - ISimpleApi proxy = this.GetProxy(implementation); + ISimpleApi proxy = this.GetProxy(proxyFactory, implementation); new ApiConsumer().UseEventField(proxy, out Func<(int timesCalled, int lastValue)> getValues); providerMod.RaiseEvent(expectedValue); (int timesCalled, int lastValue) = getValues(); @@ -57,8 +64,9 @@ namespace SMAPI.Tests.Core } /// Assert that an event property can be proxied correctly. + /// The proxy factory to test. [Test] - public void CanProxy_EventProperty() + public void CanProxy_EventProperty([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory) { // arrange ProviderMod providerMod = new(); @@ -66,7 +74,7 @@ namespace SMAPI.Tests.Core int expectedValue = this.Random.Next(); // act - ISimpleApi proxy = this.GetProxy(implementation); + ISimpleApi proxy = this.GetProxy(proxyFactory, implementation); new ApiConsumer().UseEventProperty(proxy, out Func<(int timesCalled, int lastValue)> getValues); providerMod.RaiseEvent(expectedValue); (int timesCalled, int lastValue) = getValues(); @@ -80,10 +88,10 @@ namespace SMAPI.Tests.Core ** Properties ****/ /// Assert that properties can be proxied correctly. + /// The proxy factory to test. /// Whether to set the properties through the provider mod or proxy interface. - [TestCase("set via provider mod")] - [TestCase("set via proxy interface")] - public void CanProxy_Properties(string setVia) + [Test] + public void CanProxy_Properties([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory, [Values("set via provider mod", "set via proxy interface")] string setVia) { // arrange ProviderMod providerMod = new(); @@ -98,7 +106,7 @@ namespace SMAPI.Tests.Core BindingFlags expectedEnum = BindingFlags.Instance | BindingFlags.Public; // act - ISimpleApi proxy = this.GetProxy(implementation); + ISimpleApi proxy = this.GetProxy(proxyFactory, implementation); switch (setVia) { case "set via provider mod": @@ -198,27 +206,29 @@ namespace SMAPI.Tests.Core } /// Assert that a simple method with no return value can be proxied correctly. + /// The proxy factory to test. [Test] - public void CanProxy_SimpleMethod_Void() + public void CanProxy_SimpleMethod_Void([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory) { // arrange object implementation = new ProviderMod().GetModApi(); // act - ISimpleApi proxy = this.GetProxy(implementation); + ISimpleApi proxy = this.GetProxy(proxyFactory, implementation); proxy.GetNothing(); } /// Assert that a simple int method can be proxied correctly. + /// The proxy factory to test. [Test] - public void CanProxy_SimpleMethod_Int() + public void CanProxy_SimpleMethod_Int([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory) { // arrange object implementation = new ProviderMod().GetModApi(); int expectedValue = this.Random.Next(); // act - ISimpleApi proxy = this.GetProxy(implementation); + ISimpleApi proxy = this.GetProxy(proxyFactory, implementation); int actualValue = proxy.GetInt(expectedValue); // assert @@ -226,15 +236,16 @@ namespace SMAPI.Tests.Core } /// Assert that a simple object method can be proxied correctly. + /// The proxy factory to test. [Test] - public void CanProxy_SimpleMethod_Object() + public void CanProxy_SimpleMethod_Object([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory) { // arrange object implementation = new ProviderMod().GetModApi(); object expectedValue = new(); // act - ISimpleApi proxy = this.GetProxy(implementation); + ISimpleApi proxy = this.GetProxy(proxyFactory, implementation); object actualValue = proxy.GetObject(expectedValue); // assert @@ -242,15 +253,16 @@ namespace SMAPI.Tests.Core } /// Assert that a simple list method can be proxied correctly. + /// The proxy factory to test. [Test] - public void CanProxy_SimpleMethod_List() + public void CanProxy_SimpleMethod_List([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory) { // arrange object implementation = new ProviderMod().GetModApi(); string expectedValue = this.GetRandomString(); // act - ISimpleApi proxy = this.GetProxy(implementation); + ISimpleApi proxy = this.GetProxy(proxyFactory, implementation); IList actualValue = proxy.GetList(expectedValue); // assert @@ -258,15 +270,16 @@ namespace SMAPI.Tests.Core } /// Assert that a simple list with interface method can be proxied correctly. + /// The proxy factory to test. [Test] - public void CanProxy_SimpleMethod_ListWithInterface() + public void CanProxy_SimpleMethod_ListWithInterface([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory) { // arrange object implementation = new ProviderMod().GetModApi(); string expectedValue = this.GetRandomString(); // act - ISimpleApi proxy = this.GetProxy(implementation); + ISimpleApi proxy = this.GetProxy(proxyFactory, implementation); IList actualValue = proxy.GetListWithInterface(expectedValue); // assert @@ -274,8 +287,9 @@ namespace SMAPI.Tests.Core } /// Assert that a simple method which returns generic types can be proxied correctly. + /// The proxy factory to test. [Test] - public void CanProxy_SimpleMethod_GenericTypes() + public void CanProxy_SimpleMethod_GenericTypes([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory) { // arrange object implementation = new ProviderMod().GetModApi(); @@ -283,7 +297,7 @@ namespace SMAPI.Tests.Core string expectedValue = this.GetRandomString(); // act - ISimpleApi proxy = this.GetProxy(implementation); + ISimpleApi proxy = this.GetProxy(proxyFactory, implementation); IDictionary> actualValue = proxy.GetGenerics(expectedKey, expectedValue); // assert @@ -294,16 +308,17 @@ namespace SMAPI.Tests.Core } /// Assert that a simple lambda method can be proxied correctly. + /// The proxy factory to test. [Test] [SuppressMessage("ReSharper", "ConvertToLocalFunction")] - public void CanProxy_SimpleMethod_Lambda() + public void CanProxy_SimpleMethod_Lambda([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory) { // arrange object implementation = new ProviderMod().GetModApi(); Func expectedValue = _ => "test"; // act - ISimpleApi proxy = this.GetProxy(implementation); + ISimpleApi proxy = this.GetProxy(proxyFactory, implementation); object actualValue = proxy.GetObject(expectedValue); // assert @@ -311,16 +326,17 @@ namespace SMAPI.Tests.Core } /// Assert that a method with out parameters can be proxied correctly. + /// The proxy factory to test. [Test] [SuppressMessage("ReSharper", "ConvertToLocalFunction")] - public void CanProxy_Method_OutParameters() + public void CanProxy_Method_OutParameters([ValueSource(nameof(InterfaceProxyTests.ProxyFactories))] IInterfaceProxyFactory proxyFactory) { // arrange object implementation = new ProviderMod().GetModApi(); const int expectedNumber = 42; // act - ISimpleApi proxy = this.GetProxy(implementation); + ISimpleApi proxy = this.GetProxy(proxyFactory, implementation); bool result = proxy.TryGetOutParameter( inputNumber: expectedNumber, @@ -374,10 +390,10 @@ namespace SMAPI.Tests.Core } /// Get a proxy API instance. + /// The proxy factory to use. /// The underlying API instance. - private ISimpleApi GetProxy(object implementation) + private ISimpleApi GetProxy(IInterfaceProxyFactory proxyFactory, object implementation) { - var proxyFactory = new InterfaceProxyFactory(); return proxyFactory.CreateProxy(implementation, this.FromModId, this.ToModId); } } diff --git a/src/SMAPI.sln.DotSettings b/src/SMAPI.sln.DotSettings index ad546665..c8dcdb55 100644 --- a/src/SMAPI.sln.DotSettings +++ b/src/SMAPI.sln.DotSettings @@ -61,11 +61,13 @@ True True True + True True True True True True + True True True True diff --git a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs index 39cef758..348ba225 100644 --- a/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModRegistryHelper.cs @@ -19,7 +19,7 @@ namespace StardewModdingAPI.Framework.ModHelpers private readonly HashSet AccessedModApis = new(); /// Generates proxy classes to access mod APIs through an arbitrary interface. - private readonly InterfaceProxyFactory ProxyFactory; + private readonly IInterfaceProxyFactory ProxyFactory; /********* @@ -30,7 +30,7 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The underlying mod registry. /// Generates proxy classes to access mod APIs through an arbitrary interface. /// Encapsulates monitoring and logging for the mod. - public ModRegistryHelper(IModMetadata mod, ModRegistry registry, InterfaceProxyFactory proxyFactory, IMonitor monitor) + public ModRegistryHelper(IModMetadata mod, ModRegistry registry, IInterfaceProxyFactory proxyFactory, IMonitor monitor) : base(mod) { this.Registry = registry; diff --git a/src/SMAPI/Framework/Models/SConfig.cs b/src/SMAPI/Framework/Models/SConfig.cs index d626ab4d..1a43c1fc 100644 --- a/src/SMAPI/Framework/Models/SConfig.cs +++ b/src/SMAPI/Framework/Models/SConfig.cs @@ -22,7 +22,8 @@ namespace StardewModdingAPI.Framework.Models [nameof(VerboseLogging)] = false, [nameof(LogNetworkTraffic)] = false, [nameof(RewriteMods)] = true, - [nameof(AggressiveMemoryOptimizations)] = false + [nameof(AggressiveMemoryOptimizations)] = false, + [nameof(UsePintail)] = true }; /// The default values for , to log changes if different. @@ -64,6 +65,9 @@ namespace StardewModdingAPI.Framework.Models /// Whether to enable more aggressive memory optimizations. public bool AggressiveMemoryOptimizations { get; } + /// Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself. + public bool UsePintail { get; } + /// Whether SMAPI should log network traffic. Best combined with , which includes network metadata. public bool LogNetworkTraffic { get; } @@ -87,10 +91,11 @@ namespace StardewModdingAPI.Framework.Models /// Whether SMAPI should log more information about the game context. /// Whether SMAPI should rewrite mods for compatibility. /// Whether to enable more aggressive memory optimizations. + /// Whether to use the experimental Pintail API proxying library, instead of the original proxying built into SMAPI itself. /// Whether SMAPI should log network traffic. /// The colors to use for text written to the SMAPI console. /// The mod IDs SMAPI should ignore when performing update checks or validating update keys. - public SConfig(bool developerMode, bool checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, bool verboseLogging, bool? rewriteMods, bool? aggressiveMemoryOptimizations, bool logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks) + public SConfig(bool developerMode, bool checkForUpdates, bool? paranoidWarnings, bool? useBetaChannel, string gitHubProjectName, string webApiBaseUrl, bool verboseLogging, bool? rewriteMods, bool? aggressiveMemoryOptimizations, bool usePintail, bool logNetworkTraffic, ColorSchemeConfig consoleColors, string[]? suppressUpdateChecks) { this.DeveloperMode = developerMode; this.CheckForUpdates = checkForUpdates; @@ -101,6 +106,7 @@ namespace StardewModdingAPI.Framework.Models this.VerboseLogging = verboseLogging; this.RewriteMods = rewriteMods ?? (bool)SConfig.DefaultValues[nameof(SConfig.RewriteMods)]; this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations ?? (bool)SConfig.DefaultValues[nameof(SConfig.AggressiveMemoryOptimizations)]; + this.UsePintail = usePintail; this.LogNetworkTraffic = logNetworkTraffic; this.ConsoleColors = consoleColors; this.SuppressUpdateChecks = suppressUpdateChecks ?? Array.Empty(); diff --git a/src/SMAPI/Framework/Reflection/IInterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/IInterfaceProxyFactory.cs new file mode 100644 index 00000000..6429db58 --- /dev/null +++ b/src/SMAPI/Framework/Reflection/IInterfaceProxyFactory.cs @@ -0,0 +1,17 @@ +namespace StardewModdingAPI.Framework.Reflection +{ + /// Generates proxy classes to access mod APIs through an arbitrary interface. + internal interface IInterfaceProxyFactory + { + /********* + ** Methods + *********/ + /// Create an API proxy. + /// The interface through which to access the API. + /// The API instance to access. + /// The unique ID of the mod consuming the API. + /// The unique ID of the mod providing the API. + TInterface CreateProxy(object instance, string sourceModID, string targetModID) + where TInterface : class; + } +} diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs index 40adde8e..694c563d 100644 --- a/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs +++ b/src/SMAPI/Framework/Reflection/InterfaceProxyFactory.cs @@ -4,8 +4,8 @@ using Nanoray.Pintail; namespace StardewModdingAPI.Framework.Reflection { - /// Generates proxy classes to access mod APIs through an arbitrary interface. - internal class InterfaceProxyFactory + /// + internal class InterfaceProxyFactory : IInterfaceProxyFactory { /********* ** Fields @@ -28,11 +28,7 @@ namespace StardewModdingAPI.Framework.Reflection )); } - /// Create an API proxy. - /// The interface through which to access the API. - /// The API instance to access. - /// The unique ID of the mod consuming the API. - /// The unique ID of the mod providing the API. + /// public TInterface CreateProxy(object instance, string sourceModID, string targetModID) where TInterface : class { diff --git a/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyBuilder.cs new file mode 100644 index 00000000..9576f768 --- /dev/null +++ b/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyBuilder.cs @@ -0,0 +1,118 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; + +namespace StardewModdingAPI.Framework.Reflection +{ + /// Generates a proxy class to access a mod API through an arbitrary interface. + internal class OriginalInterfaceProxyBuilder + { + /********* + ** Fields + *********/ + /// The target class type. + private readonly Type TargetType; + + /// The generated proxy type. + private readonly Type ProxyType; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The type name to generate. + /// The CLR module in which to create proxy classes. + /// The interface type to implement. + /// The target type. + public OriginalInterfaceProxyBuilder(string name, ModuleBuilder moduleBuilder, Type interfaceType, Type targetType) + { + // validate + if (name == null) + throw new ArgumentNullException(nameof(name)); + if (targetType == null) + throw new ArgumentNullException(nameof(targetType)); + + // define proxy type + TypeBuilder proxyBuilder = moduleBuilder.DefineType(name, TypeAttributes.Public | TypeAttributes.Class); + proxyBuilder.AddInterfaceImplementation(interfaceType); + + // create field to store target instance + FieldBuilder targetField = proxyBuilder.DefineField("__Target", targetType, FieldAttributes.Private); + + // create constructor which accepts target instance and sets field + { + ConstructorBuilder constructor = proxyBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, new[] { targetType }); + ILGenerator il = constructor.GetILGenerator(); + + il.Emit(OpCodes.Ldarg_0); // this + // ReSharper disable once AssignNullToNotNullAttribute -- never null + il.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes)!); // call base constructor + il.Emit(OpCodes.Ldarg_0); // this + il.Emit(OpCodes.Ldarg_1); // load argument + il.Emit(OpCodes.Stfld, targetField); // set field to loaded argument + il.Emit(OpCodes.Ret); + } + + // proxy methods + foreach (MethodInfo proxyMethod in interfaceType.GetMethods()) + { + var targetMethod = targetType.GetMethod(proxyMethod.Name, proxyMethod.GetParameters().Select(a => a.ParameterType).ToArray()); + if (targetMethod == null) + throw new InvalidOperationException($"The {interfaceType.FullName} interface defines method {proxyMethod.Name} which doesn't exist in the API."); + + this.ProxyMethod(proxyBuilder, targetMethod, targetField); + } + + // save info + this.TargetType = targetType; + this.ProxyType = proxyBuilder.CreateType()!; + } + + /// Create an instance of the proxy for a target instance. + /// The target instance. + public object CreateInstance(object targetInstance) + { + ConstructorInfo? constructor = this.ProxyType.GetConstructor(new[] { this.TargetType }); + if (constructor == null) + throw new InvalidOperationException($"Couldn't find the constructor for generated proxy type '{this.ProxyType.Name}'."); // should never happen + return constructor.Invoke(new[] { targetInstance }); + } + + + /********* + ** Private methods + *********/ + /// Define a method which proxies access to a method on the target. + /// The proxy type being generated. + /// The target method. + /// The proxy field containing the API instance. + private void ProxyMethod(TypeBuilder proxyBuilder, MethodInfo target, FieldBuilder instanceField) + { + Type[] argTypes = target.GetParameters().Select(a => a.ParameterType).ToArray(); + + // create method + MethodBuilder methodBuilder = proxyBuilder.DefineMethod(target.Name, MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual); + methodBuilder.SetParameters(argTypes); + methodBuilder.SetReturnType(target.ReturnType); + + // create method body + { + ILGenerator il = methodBuilder.GetILGenerator(); + + // load target instance + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldfld, instanceField); + + // invoke target method on instance + for (int i = 0; i < argTypes.Length; i++) + il.Emit(OpCodes.Ldarg, i + 1); + il.Emit(OpCodes.Call, target); + + // return result + il.Emit(OpCodes.Ret); + } + } + } +} diff --git a/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyFactory.cs b/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyFactory.cs new file mode 100644 index 00000000..d6966978 --- /dev/null +++ b/src/SMAPI/Framework/Reflection/OriginalInterfaceProxyFactory.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; + +namespace StardewModdingAPI.Framework.Reflection +{ + /// + internal class OriginalInterfaceProxyFactory : IInterfaceProxyFactory + { + /********* + ** Fields + *********/ + /// The CLR module in which to create proxy classes. + private readonly ModuleBuilder ModuleBuilder; + + /// The generated proxy types. + private readonly IDictionary Builders = new Dictionary(); + + + /********* + ** Public methods + *********/ + /// Construct an instance. + public OriginalInterfaceProxyFactory() + { + AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName($"StardewModdingAPI.Proxies, Version={this.GetType().Assembly.GetName().Version}, Culture=neutral"), AssemblyBuilderAccess.Run); + this.ModuleBuilder = assemblyBuilder.DefineDynamicModule("StardewModdingAPI.Proxies"); + } + + /// + public TInterface CreateProxy(object instance, string sourceModID, string targetModID) + where TInterface : class + { + lock (this.Builders) + { + // validate + if (instance == null) + throw new InvalidOperationException("Can't proxy access to a null API."); + if (!typeof(TInterface).IsInterface) + throw new InvalidOperationException("The proxy type must be an interface, not a class."); + + // get proxy type + Type targetType = instance.GetType(); + string proxyTypeName = $"StardewModdingAPI.Proxies.From<{sourceModID}_{typeof(TInterface).FullName}>_To<{targetModID}_{targetType.FullName}>"; + if (!this.Builders.TryGetValue(proxyTypeName, out OriginalInterfaceProxyBuilder? builder)) + { + builder = new OriginalInterfaceProxyBuilder(proxyTypeName, this.ModuleBuilder, typeof(TInterface), targetType); + this.Builders[proxyTypeName] = builder; + } + + // create instance + return (TInterface)builder.CreateInstance(instance); + } + } + } +} diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index b3c9087f..44853627 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1560,7 +1560,9 @@ namespace StardewModdingAPI.Framework { // init HashSet suppressUpdateChecks = new HashSet(this.Settings.SuppressUpdateChecks, StringComparer.OrdinalIgnoreCase); - InterfaceProxyFactory proxyFactory = new(); + IInterfaceProxyFactory proxyFactory = this.Settings.UsePintail + ? new InterfaceProxyFactory() + : new OriginalInterfaceProxyFactory(); // load mods foreach (IModMetadata mod in mods) @@ -1699,7 +1701,7 @@ namespace StardewModdingAPI.Framework /// The user-facing reason phrase explaining why the mod couldn't be loaded (if applicable). /// More detailed details about the error intended for developers (if any). /// Returns whether the mod was successfully loaded. - private bool TryLoadMod(IModMetadata mod, IModMetadata[] mods, AssemblyLoader assemblyLoader, InterfaceProxyFactory proxyFactory, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase, HashSet suppressUpdateChecks, [NotNullWhen(false)] out ModFailReason? failReason, out string? errorReasonPhrase, out string? errorDetails) + private bool TryLoadMod(IModMetadata mod, IModMetadata[] mods, AssemblyLoader assemblyLoader, IInterfaceProxyFactory proxyFactory, JsonHelper jsonHelper, ContentCoordinator contentCore, ModDatabase modDatabase, HashSet suppressUpdateChecks, [NotNullWhen(false)] out ModFailReason? failReason, out string? errorReasonPhrase, out string? errorDetails) { errorDetails = null; diff --git a/src/SMAPI/SMAPI.config.json b/src/SMAPI/SMAPI.config.json index 49056e83..065dfa8c 100644 --- a/src/SMAPI/SMAPI.config.json +++ b/src/SMAPI/SMAPI.config.json @@ -46,6 +46,12 @@ copy all the settings, or you may cause bugs due to overridden changes in future */ "AggressiveMemoryOptimizations": false, + /** + * Whether to use the experimental Pintail API proxying library, instead of the original + * proxying built into SMAPI itself. + */ + "UsePintail": true, + /** * Whether to add a section to the 'mod issues' list for mods which directly use potentially * sensitive .NET APIs like file or shell access. Note that many mods do this legitimately as -- cgit From 889004f1eba31aa3a5069e1dcbe79896d05720b0 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 19 Apr 2022 19:03:47 -0400 Subject: move deprecation code into namespace --- src/SMAPI/Constants.cs | 1 + src/SMAPI/Framework/Content/AssetInfo.cs | 1 + .../ContentManagers/GameContentManager.cs | 1 + src/SMAPI/Framework/DeprecationLevel.cs | 15 -- src/SMAPI/Framework/DeprecationManager.cs | 171 --------------------- src/SMAPI/Framework/DeprecationWarning.cs | 48 ------ .../Framework/Deprecations/DeprecationLevel.cs | 15 ++ .../Framework/Deprecations/DeprecationManager.cs | 171 +++++++++++++++++++++ .../Framework/Deprecations/DeprecationWarning.cs | 48 ++++++ src/SMAPI/Framework/ModHelpers/CommandHelper.cs | 1 + src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 1 + src/SMAPI/Framework/ModHelpers/ModHelper.cs | 1 + src/SMAPI/Framework/SCore.cs | 1 + src/SMAPI/Utilities/PerScreen.cs | 1 + 14 files changed, 242 insertions(+), 234 deletions(-) delete mode 100644 src/SMAPI/Framework/DeprecationLevel.cs delete mode 100644 src/SMAPI/Framework/DeprecationManager.cs delete mode 100644 src/SMAPI/Framework/DeprecationWarning.cs create mode 100644 src/SMAPI/Framework/Deprecations/DeprecationLevel.cs create mode 100644 src/SMAPI/Framework/Deprecations/DeprecationManager.cs create mode 100644 src/SMAPI/Framework/Deprecations/DeprecationWarning.cs (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index d40b97f4..ddb08435 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -6,6 +6,7 @@ using System.Reflection; using Mono.Cecil; using StardewModdingAPI.Enums; using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Deprecations; using StardewModdingAPI.Framework.ModLoading; using StardewModdingAPI.Toolkit.Framework; using StardewModdingAPI.Toolkit.Utilities; diff --git a/src/SMAPI/Framework/Content/AssetInfo.cs b/src/SMAPI/Framework/Content/AssetInfo.cs index 16b71487..c632249d 100644 --- a/src/SMAPI/Framework/Content/AssetInfo.cs +++ b/src/SMAPI/Framework/Content/AssetInfo.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Microsoft.Xna.Framework.Graphics; +using StardewModdingAPI.Framework.Deprecations; namespace StardewModdingAPI.Framework.Content { diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index 6469fea4..083df454 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -8,6 +8,7 @@ using System.Reflection; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Events; using StardewModdingAPI.Framework.Content; +using StardewModdingAPI.Framework.Deprecations; using StardewModdingAPI.Framework.Reflection; using StardewModdingAPI.Framework.Utilities; using StardewModdingAPI.Internal; diff --git a/src/SMAPI/Framework/DeprecationLevel.cs b/src/SMAPI/Framework/DeprecationLevel.cs deleted file mode 100644 index 12b50952..00000000 --- a/src/SMAPI/Framework/DeprecationLevel.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace StardewModdingAPI.Framework -{ - /// Indicates how deprecated something is. - internal enum DeprecationLevel - { - /// It's deprecated but won't be removed soon. Mod authors have some time to update their mods. Deprecation warnings should be logged, but not written to the console. - Notice, - - /// Mods should no longer be using it. Deprecation messages should be debug entries in the console. - Info, - - /// The code will be removed soon. Deprecation messages should be warnings in the console. - PendingRemoval - } -} diff --git a/src/SMAPI/Framework/DeprecationManager.cs b/src/SMAPI/Framework/DeprecationManager.cs deleted file mode 100644 index 37a5c8ef..00000000 --- a/src/SMAPI/Framework/DeprecationManager.cs +++ /dev/null @@ -1,171 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; - -namespace StardewModdingAPI.Framework -{ - /// Manages deprecation warnings. - internal class DeprecationManager - { - /********* - ** Fields - *********/ - /// The deprecations which have already been logged (as 'mod name::noun phrase::version'). - private readonly HashSet LoggedDeprecations = new(StringComparer.OrdinalIgnoreCase); - - /// Encapsulates monitoring and logging for a given module. - private readonly IMonitor Monitor; - - /// Tracks the installed mods. - private readonly ModRegistry ModRegistry; - - /// The queued deprecation warnings to display. - private readonly IList QueuedWarnings = new List(); - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// Encapsulates monitoring and logging for a given module. - /// Tracks the installed mods. - public DeprecationManager(IMonitor monitor, ModRegistry modRegistry) - { - this.Monitor = monitor; - this.ModRegistry = modRegistry; - } - - /// Get a mod for the closest assembly registered as a source of deprecation warnings. - /// Returns the source name, or null if no registered assemblies were found. - public IModMetadata? GetModFromStack() - { - return this.ModRegistry.GetFromStack(); - } - - /// Get a mod from its unique ID. - /// The mod's unique ID. - public IModMetadata? GetMod(string modId) - { - return this.ModRegistry.Get(modId); - } - - /// Log a deprecation warning. - /// The mod which used the deprecated code, if known. - /// A noun phrase describing what is deprecated. - /// The SMAPI version which deprecated it. - /// How deprecated the code is. - public void Warn(IModMetadata? source, string nounPhrase, string version, DeprecationLevel severity) - { - // ignore if already warned - if (!this.MarkWarned(source, nounPhrase, version)) - return; - - // queue warning - var stack = new StackTrace(skipFrames: 1); // skip this method - this.QueuedWarnings.Add(new DeprecationWarning(source, nounPhrase, version, severity, stack)); - } - - /// A placeholder method used to track deprecated code for which a separate warning will be shown. - /// The SMAPI version which deprecated it. - /// How deprecated the code is. - public void PlaceholderWarn(string version, DeprecationLevel severity) { } - - /// Print any queued messages. - public void PrintQueued() - { - foreach (DeprecationWarning warning in this.QueuedWarnings.OrderBy(p => p.ModName).ThenBy(p => p.NounPhrase)) - { - // build message - string message = $"{warning.ModName} uses deprecated code ({warning.NounPhrase} is deprecated since SMAPI {warning.Version})."; - - // get log level - LogLevel level; - switch (warning.Level) - { - case DeprecationLevel.Notice: - level = LogLevel.Trace; - break; - - case DeprecationLevel.Info: - level = LogLevel.Debug; - break; - - case DeprecationLevel.PendingRemoval: - level = LogLevel.Warn; - break; - - default: - throw new NotSupportedException($"Unknown deprecation level '{warning.Level}'."); - } - - // log message - if (level == LogLevel.Trace) - this.Monitor.Log($"{message}\n{this.GetSimplifiedStackTrace(warning.StackTrace, warning.Mod)}", level); - else - { - this.Monitor.Log(message, level); - this.Monitor.Log(this.GetSimplifiedStackTrace(warning.StackTrace, warning.Mod), LogLevel.Debug); - } - } - - this.QueuedWarnings.Clear(); - } - - - /********* - ** Private methods - *********/ - /// Mark a deprecation warning as already logged. - /// The mod which used the deprecated code. - /// A noun phrase describing what is deprecated (e.g. "the Extensions.AsInt32 method"). - /// The SMAPI version which deprecated it. - /// Returns whether the deprecation was successfully marked as warned. Returns false if it was already marked. - private bool MarkWarned(IModMetadata? source, string nounPhrase, string version) - { - string key = $"{source?.DisplayName ?? ""}::{nounPhrase}::{version}"; - if (this.LoggedDeprecations.Contains(key)) - return false; - this.LoggedDeprecations.Add(key); - return true; - } - - /// Get the simplest stack trace which shows where in the mod the deprecated code was called from. - /// The stack trace. - /// The mod for which to show a stack trace. - private string GetSimplifiedStackTrace(StackTrace stack, IModMetadata? mod) - { - // unknown mod, show entire stack trace - if (mod == null) - return stack.ToString(); - - // get frame info - var frames = stack - .GetFrames() - .Select(frame => (Frame: frame, Mod: this.ModRegistry.GetFrom(frame))) - .ToArray(); - var modIds = new HashSet( - from frame in frames - let id = frame.Mod?.Manifest.UniqueID - where id != null - select id - ); - - // can't filter to the target mod - if (modIds.Count != 1 || !modIds.Contains(mod.Manifest.UniqueID)) - return stack.ToString(); - - // get stack frames for the target mod, plus one for context - var framesStartingAtMod = frames.SkipWhile(p => p.Mod == null).ToArray(); - var displayFrames = framesStartingAtMod.TakeWhile(p => p.Mod != null).ToArray(); - displayFrames = displayFrames.Concat(framesStartingAtMod.Skip(displayFrames.Length).Take(1)).ToArray(); - - // build stack trace - StringBuilder str = new(); - foreach (var frame in displayFrames) - str.Append(new StackTrace(frame.Frame)); - return str.ToString().TrimEnd(); - } - } -} diff --git a/src/SMAPI/Framework/DeprecationWarning.cs b/src/SMAPI/Framework/DeprecationWarning.cs deleted file mode 100644 index 1e83f679..00000000 --- a/src/SMAPI/Framework/DeprecationWarning.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Diagnostics; - -namespace StardewModdingAPI.Framework -{ - /// A deprecation warning for a mod. - internal class DeprecationWarning - { - /********* - ** Accessors - *********/ - /// The affected mod. - public IModMetadata? Mod { get; } - - /// Get the display name for the affected mod. - public string ModName => this.Mod?.DisplayName ?? ""; - - /// A noun phrase describing what is deprecated. - public string NounPhrase { get; } - - /// The SMAPI version which deprecated it. - public string Version { get; } - - /// The deprecation level for the affected code. - public DeprecationLevel Level { get; } - - /// The stack trace when the deprecation warning was raised. - public StackTrace StackTrace { get; } - - - /********* - ** Public methods - *********/ - /// Construct an instance. - /// The affected mod. - /// A noun phrase describing what is deprecated. - /// The SMAPI version which deprecated it. - /// The deprecation level for the affected code. - /// The stack trace when the deprecation warning was raised. - public DeprecationWarning(IModMetadata? mod, string nounPhrase, string version, DeprecationLevel level, StackTrace stackTrace) - { - this.Mod = mod; - this.NounPhrase = nounPhrase; - this.Version = version; - this.Level = level; - this.StackTrace = stackTrace; - } - } -} diff --git a/src/SMAPI/Framework/Deprecations/DeprecationLevel.cs b/src/SMAPI/Framework/Deprecations/DeprecationLevel.cs new file mode 100644 index 00000000..8b15b59a --- /dev/null +++ b/src/SMAPI/Framework/Deprecations/DeprecationLevel.cs @@ -0,0 +1,15 @@ +namespace StardewModdingAPI.Framework.Deprecations +{ + /// Indicates how deprecated something is. + internal enum DeprecationLevel + { + /// It's deprecated but won't be removed soon. Mod authors have some time to update their mods. Deprecation warnings should be logged, but not written to the console. + Notice, + + /// Mods should no longer be using it. Deprecation messages should be debug entries in the console. + Info, + + /// The code will be removed soon. Deprecation messages should be warnings in the console. + PendingRemoval + } +} diff --git a/src/SMAPI/Framework/Deprecations/DeprecationManager.cs b/src/SMAPI/Framework/Deprecations/DeprecationManager.cs new file mode 100644 index 00000000..da17ce7e --- /dev/null +++ b/src/SMAPI/Framework/Deprecations/DeprecationManager.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace StardewModdingAPI.Framework.Deprecations +{ + /// Manages deprecation warnings. + internal class DeprecationManager + { + /********* + ** Fields + *********/ + /// The deprecations which have already been logged (as 'mod name::noun phrase::version'). + private readonly HashSet LoggedDeprecations = new(StringComparer.OrdinalIgnoreCase); + + /// Encapsulates monitoring and logging for a given module. + private readonly IMonitor Monitor; + + /// Tracks the installed mods. + private readonly ModRegistry ModRegistry; + + /// The queued deprecation warnings to display. + private readonly IList QueuedWarnings = new List(); + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// Encapsulates monitoring and logging for a given module. + /// Tracks the installed mods. + public DeprecationManager(IMonitor monitor, ModRegistry modRegistry) + { + this.Monitor = monitor; + this.ModRegistry = modRegistry; + } + + /// Get a mod for the closest assembly registered as a source of deprecation warnings. + /// Returns the source name, or null if no registered assemblies were found. + public IModMetadata? GetModFromStack() + { + return this.ModRegistry.GetFromStack(); + } + + /// Get a mod from its unique ID. + /// The mod's unique ID. + public IModMetadata? GetMod(string modId) + { + return this.ModRegistry.Get(modId); + } + + /// Log a deprecation warning. + /// The mod which used the deprecated code, if known. + /// A noun phrase describing what is deprecated. + /// The SMAPI version which deprecated it. + /// How deprecated the code is. + public void Warn(IModMetadata? source, string nounPhrase, string version, DeprecationLevel severity) + { + // ignore if already warned + if (!this.MarkWarned(source, nounPhrase, version)) + return; + + // queue warning + var stack = new StackTrace(skipFrames: 1); // skip this method + this.QueuedWarnings.Add(new DeprecationWarning(source, nounPhrase, version, severity, stack)); + } + + /// A placeholder method used to track deprecated code for which a separate warning will be shown. + /// The SMAPI version which deprecated it. + /// How deprecated the code is. + public void PlaceholderWarn(string version, DeprecationLevel severity) { } + + /// Print any queued messages. + public void PrintQueued() + { + foreach (DeprecationWarning warning in this.QueuedWarnings.OrderBy(p => p.ModName).ThenBy(p => p.NounPhrase)) + { + // build message + string message = $"{warning.ModName} uses deprecated code ({warning.NounPhrase} is deprecated since SMAPI {warning.Version})."; + + // get log level + LogLevel level; + switch (warning.Level) + { + case DeprecationLevel.Notice: + level = LogLevel.Trace; + break; + + case DeprecationLevel.Info: + level = LogLevel.Debug; + break; + + case DeprecationLevel.PendingRemoval: + level = LogLevel.Warn; + break; + + default: + throw new NotSupportedException($"Unknown deprecation level '{warning.Level}'."); + } + + // log message + if (level == LogLevel.Trace) + this.Monitor.Log($"{message}\n{this.GetSimplifiedStackTrace(warning.StackTrace, warning.Mod)}", level); + else + { + this.Monitor.Log(message, level); + this.Monitor.Log(this.GetSimplifiedStackTrace(warning.StackTrace, warning.Mod), LogLevel.Debug); + } + } + + this.QueuedWarnings.Clear(); + } + + + /********* + ** Private methods + *********/ + /// Mark a deprecation warning as already logged. + /// The mod which used the deprecated code. + /// A noun phrase describing what is deprecated (e.g. "the Extensions.AsInt32 method"). + /// The SMAPI version which deprecated it. + /// Returns whether the deprecation was successfully marked as warned. Returns false if it was already marked. + private bool MarkWarned(IModMetadata? source, string nounPhrase, string version) + { + string key = $"{source?.DisplayName ?? ""}::{nounPhrase}::{version}"; + if (this.LoggedDeprecations.Contains(key)) + return false; + this.LoggedDeprecations.Add(key); + return true; + } + + /// Get the simplest stack trace which shows where in the mod the deprecated code was called from. + /// The stack trace. + /// The mod for which to show a stack trace. + private string GetSimplifiedStackTrace(StackTrace stack, IModMetadata? mod) + { + // unknown mod, show entire stack trace + if (mod == null) + return stack.ToString(); + + // get frame info + var frames = stack + .GetFrames() + .Select(frame => (Frame: frame, Mod: this.ModRegistry.GetFrom(frame))) + .ToArray(); + var modIds = new HashSet( + from frame in frames + let id = frame.Mod?.Manifest.UniqueID + where id != null + select id + ); + + // can't filter to the target mod + if (modIds.Count != 1 || !modIds.Contains(mod.Manifest.UniqueID)) + return stack.ToString(); + + // get stack frames for the target mod, plus one for context + var framesStartingAtMod = frames.SkipWhile(p => p.Mod == null).ToArray(); + var displayFrames = framesStartingAtMod.TakeWhile(p => p.Mod != null).ToArray(); + displayFrames = displayFrames.Concat(framesStartingAtMod.Skip(displayFrames.Length).Take(1)).ToArray(); + + // build stack trace + StringBuilder str = new(); + foreach (var frame in displayFrames) + str.Append(new StackTrace(frame.Frame)); + return str.ToString().TrimEnd(); + } + } +} diff --git a/src/SMAPI/Framework/Deprecations/DeprecationWarning.cs b/src/SMAPI/Framework/Deprecations/DeprecationWarning.cs new file mode 100644 index 00000000..38062daf --- /dev/null +++ b/src/SMAPI/Framework/Deprecations/DeprecationWarning.cs @@ -0,0 +1,48 @@ +using System.Diagnostics; + +namespace StardewModdingAPI.Framework.Deprecations +{ + /// A deprecation warning for a mod. + internal class DeprecationWarning + { + /********* + ** Accessors + *********/ + /// The affected mod. + public IModMetadata? Mod { get; } + + /// Get the display name for the affected mod. + public string ModName => this.Mod?.DisplayName ?? ""; + + /// A noun phrase describing what is deprecated. + public string NounPhrase { get; } + + /// The SMAPI version which deprecated it. + public string Version { get; } + + /// The deprecation level for the affected code. + public DeprecationLevel Level { get; } + + /// The stack trace when the deprecation warning was raised. + public StackTrace StackTrace { get; } + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The affected mod. + /// A noun phrase describing what is deprecated. + /// The SMAPI version which deprecated it. + /// The deprecation level for the affected code. + /// The stack trace when the deprecation warning was raised. + public DeprecationWarning(IModMetadata? mod, string nounPhrase, string version, DeprecationLevel level, StackTrace stackTrace) + { + this.Mod = mod; + this.NounPhrase = nounPhrase; + this.Version = version; + this.Level = level; + this.StackTrace = stackTrace; + } + } +} diff --git a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs index e430fb1c..226a8d69 100644 --- a/src/SMAPI/Framework/ModHelpers/CommandHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/CommandHelper.cs @@ -1,4 +1,5 @@ using System; +using StardewModdingAPI.Framework.Deprecations; namespace StardewModdingAPI.Framework.ModHelpers { diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index 534ac138..94a30bf1 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.ContentManagers; +using StardewModdingAPI.Framework.Deprecations; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Reflection; using StardewValley; diff --git a/src/SMAPI/Framework/ModHelpers/ModHelper.cs b/src/SMAPI/Framework/ModHelpers/ModHelper.cs index 5b450c36..a23a9beb 100644 --- a/src/SMAPI/Framework/ModHelpers/ModHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ModHelper.cs @@ -1,6 +1,7 @@ using System; using System.IO; using StardewModdingAPI.Events; +using StardewModdingAPI.Framework.Deprecations; using StardewModdingAPI.Framework.Input; namespace StardewModdingAPI.Framework.ModHelpers diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 44853627..c208788a 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -20,6 +20,7 @@ using StardewModdingAPI.Enums; using StardewModdingAPI.Events; using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.ContentManagers; +using StardewModdingAPI.Framework.Deprecations; using StardewModdingAPI.Framework.Events; using StardewModdingAPI.Framework.Exceptions; using StardewModdingAPI.Framework.Input; diff --git a/src/SMAPI/Utilities/PerScreen.cs b/src/SMAPI/Utilities/PerScreen.cs index 6c2e436b..1c4c56fe 100644 --- a/src/SMAPI/Utilities/PerScreen.cs +++ b/src/SMAPI/Utilities/PerScreen.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using StardewModdingAPI.Framework; +using StardewModdingAPI.Framework.Deprecations; namespace StardewModdingAPI.Utilities { -- cgit From d7d8cdaa5a4bc01001e3538a9189b04d1130d3bb Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Wed, 27 Apr 2022 20:09:43 -0400 Subject: add backwards compatibility for loading content assets with .xnb extension --- src/SMAPI/Framework/Content/ContentCache.cs | 2 +- src/SMAPI/Framework/ModHelpers/ContentHelper.cs | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) (limited to 'src/SMAPI/Framework/ModHelpers') diff --git a/src/SMAPI/Framework/Content/ContentCache.cs b/src/SMAPI/Framework/Content/ContentCache.cs index cbb6c1e9..736ee5da 100644 --- a/src/SMAPI/Framework/Content/ContentCache.cs +++ b/src/SMAPI/Framework/Content/ContentCache.cs @@ -76,7 +76,7 @@ namespace StardewModdingAPI.Framework.Content { key = this.NormalizePathSeparators(key); return key.EndsWith(".xnb", StringComparison.OrdinalIgnoreCase) - ? key.Substring(0, key.Length - 4) + ? key[..^4] : key; } diff --git a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs index 94a30bf1..3c2441e8 100644 --- a/src/SMAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/ContentHelper.cs @@ -118,6 +118,17 @@ namespace StardewModdingAPI.Framework.ModHelpers switch (source) { case ContentSource.GameContent: + if (assetName.Name.EndsWith(".xnb", StringComparison.OrdinalIgnoreCase)) + { + assetName = this.ContentCore.ParseAssetName(assetName.Name[..^4], allowLocales: true); + SCore.DeprecationManager.Warn( + this.Mod, + "loading assets from the Content folder with a .xnb file extension", + "3.14.0", + DeprecationLevel.Notice + ); + } + return this.GameContentManager.LoadLocalized(assetName, this.CurrentLocaleConstant, useCache: false); case ContentSource.ModFolder: -- cgit