diff --git a/src/SMAPI.Toolkit/Framework/Clients/CompatibilityRepo/ChangeDescriptor.cs b/src/SMAPI.Toolkit/Framework/Clients/CompatibilityRepo/ChangeDescriptor.cs index eb093ad6..54dd22f3 100644 --- a/src/SMAPI.Toolkit/Framework/Clients/CompatibilityRepo/ChangeDescriptor.cs +++ b/src/SMAPI.Toolkit/Framework/Clients/CompatibilityRepo/ChangeDescriptor.cs @@ -80,7 +80,7 @@ public class ChangeDescriptor this.Apply(values); // format - if (rawField == null && !values.Any()) + if (rawField == null && values.Count == 0) return null; return string.Join(", ", values); } @@ -91,7 +91,7 @@ public class ChangeDescriptor public void Apply(List values) { // replace/remove values - if (this.Replace.Any() || this.Remove.Any()) + if (this.Replace.Count > 0 || this.Remove.Count > 0) { for (int i = values.Count - 1; i >= 0; i--) { @@ -106,7 +106,7 @@ public class ChangeDescriptor } // add values - if (this.Add.Any()) + if (this.Add.Count > 0) { HashSet curValues = new(values.Select(p => p.Trim()), StringComparer.OrdinalIgnoreCase); foreach (string add in this.Add) diff --git a/src/SMAPI.Toolkit/Framework/ManifestValidator.cs b/src/SMAPI.Toolkit/Framework/ManifestValidator.cs index 2a98cef3..d38e8bf8 100644 --- a/src/SMAPI.Toolkit/Framework/ManifestValidator.cs +++ b/src/SMAPI.Toolkit/Framework/ManifestValidator.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Linq; using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingAPI.Toolkit.Framework; @@ -37,7 +36,7 @@ public static class ManifestValidator // validate EntryDll/ContentPackFor format if (hasDll) { - if (manifest.EntryDll!.Intersect(Path.GetInvalidFileNameChars()).Any()) + if (manifest.EntryDll!.IndexOfAny(Path.GetInvalidFileNameChars()) > -1) { error = $"manifest has invalid filename '{manifest.EntryDll}' for the {nameof(IManifest.EntryDll)} field."; return false; @@ -63,7 +62,7 @@ public static class ManifestValidator if (string.IsNullOrWhiteSpace(manifest.UniqueID)) missingFields.Add(nameof(IManifest.UniqueID)); - if (missingFields.Any()) + if (missingFields.Count > 0) { error = $"manifest is missing required fields ({string.Join(", ", missingFields)})."; return false; diff --git a/src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs b/src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs index fe792df1..aa8efd66 100644 --- a/src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs +++ b/src/SMAPI.Toolkit/Framework/ModData/ModDataField.cs @@ -1,5 +1,3 @@ -using System.Linq; - namespace StardewModdingAPI.Toolkit.Framework.ModData; /// A versioned mod metadata field. @@ -66,7 +64,13 @@ public class ModDataField { // update key case ModDataFieldKey.UpdateKey: - return manifest.UpdateKeys.Any(p => !string.IsNullOrWhiteSpace(p)); + foreach (string updateKey in manifest.UpdateKeys) + { + if (!string.IsNullOrWhiteSpace(updateKey)) + return true; + } + + return false; // non-manifest fields case ModDataFieldKey.StatusReasonPhrase: diff --git a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs index 42f4cf34..147b8830 100644 --- a/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs +++ b/src/SMAPI.Toolkit/Framework/ModScanning/ModScanner.cs @@ -138,7 +138,7 @@ public class ModScanner return new ModFolder(root, searchFolder, ModType.Invalid, null, ModParseError.EmptyVortexFolder, "it's an empty Vortex folder (is the mod disabled in Vortex?)."); // empty folder - if (!relevantFiles.Any()) + if (relevantFiles.Length == 0) return new ModFolder(root, searchFolder, ModType.Invalid, null, ModParseError.EmptyFolder, "it's an empty folder."); // XNB mod @@ -290,7 +290,7 @@ public class ModScanner DirectoryInfo[] subfolders = folder.GetDirectories().Where(this.IsRelevant).ToArray(); FileInfo[] files = folder.GetFiles().Where(this.IsRelevant).ToArray(); - return subfolders.Any() && !files.Any(); + return subfolders.Length > 0 && files.Length == 0; } /// Recursively get all files in a folder. @@ -320,7 +320,13 @@ public class ModScanner return false; // ignored entry name - return !this.IgnoreFilesystemNames.Any(p => p.IsMatch(entry.Name)); + foreach (Regex rule in this.IgnoreFilesystemNames) + { + if (rule.IsMatch(entry.Name)) + return false; + } + + return true; } /// Get whether a set of files looks like an XNB mod. diff --git a/src/SMAPI.Toolkit/Utilities/PathUtilities.cs b/src/SMAPI.Toolkit/Utilities/PathUtilities.cs index 3f28063f..b7def093 100644 --- a/src/SMAPI.Toolkit/Utilities/PathUtilities.cs +++ b/src/SMAPI.Toolkit/Utilities/PathUtilities.cs @@ -93,7 +93,7 @@ public static class PathUtilities } // keep trailing separator - if ((!hasRoot || segments.Any()) && PathUtilities.PossiblePathSeparators.Contains(path[path.Length - 1])) + if ((!hasRoot || segments.Length > 0) && PathUtilities.PossiblePathSeparators.Contains(path[path.Length - 1])) newPath += PathUtilities.PreferredPathSeparator; return newPath; diff --git a/src/SMAPI/Constants.cs b/src/SMAPI/Constants.cs index a7d1deec..e6fe62d9 100644 --- a/src/SMAPI/Constants.cs +++ b/src/SMAPI/Constants.cs @@ -10,6 +10,7 @@ using StardewModdingAPI.Framework.ModLoading; using StardewModdingAPI.Toolkit.Framework; using StardewModdingAPI.Toolkit.Utilities; using StardewValley; +using StardewValley.Extensions; namespace StardewModdingAPI; @@ -298,7 +299,7 @@ public static class Constants /// The target game platform. internal static PlatformAssemblyMap GetAssemblyMap(Platform targetPlatform) { - var removeAssemblyReferences = new List(); + var removeAssemblyReferences = new HashSet(); var targetAssemblies = new List(); // get assembly renamed in SMAPI 3.0 diff --git a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs index 822bc337..ce5b05fe 100644 --- a/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/BaseContentManager.cs @@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Globalization; using System.IO; -using System.Linq; using System.Reflection; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; @@ -199,7 +198,7 @@ internal abstract class BaseContentManager : LocalizedContentManager, IContentMa // throwing other types like ArgumentException here. if (string.IsNullOrWhiteSpace(assetName)) throw new SContentLoadException(ContentLoadErrorType.InvalidName, "The asset key or local path is empty."); - if (assetName.Intersect(Path.GetInvalidPathChars()).Any()) + if (assetName.IndexOfAny(Path.GetInvalidPathChars()) > -1) throw new SContentLoadException(ContentLoadErrorType.InvalidName, "The asset key or local path contains invalid characters."); return this.Cache.NormalizeKey(assetName); diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs index f7a3dbd7..77d7e58d 100644 --- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs @@ -312,8 +312,13 @@ internal sealed class ModContentManager : BaseContentManager /// The is not compatible with any of the . private void AssertValidType(IAssetName assetName, FileInfo file, params Type[] validTypes) { - if (!validTypes.Any(validType => validType.IsAssignableFrom(typeof(TAsset)))) - this.ThrowLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(TAsset)}'; must be type '{string.Join("' or '", validTypes.Select(p => p.FullName))}'."); + foreach (Type type in validTypes) + { + if (type.IsAssignableFrom(typeof(TAsset))) + return; + } + + this.ThrowLoadError(assetName, ContentLoadErrorType.InvalidData, $"can't read file with extension '{file.Extension}' as type '{typeof(TAsset)}'; must be type '{string.Join("' or '", validTypes.Select(p => p.FullName))}'."); } /// Throw an error which indicates that an asset couldn't be loaded. diff --git a/src/SMAPI/Framework/Events/ManagedEvent.cs b/src/SMAPI/Framework/Events/ManagedEvent.cs index c7ba41c9..bbeaeacc 100644 --- a/src/SMAPI/Framework/Events/ManagedEvent.cs +++ b/src/SMAPI/Framework/Events/ManagedEvent.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; using StardewModdingAPI.Events; using StardewModdingAPI.Framework.Extensions; @@ -174,7 +173,17 @@ internal class ManagedEvent : IManagedEvent { // recheck priorities if (this.HasRemovedHandlers) - this.HasPriorities = this.Handlers.Any(p => p.Priority != EventPriority.Normal); + { + this.HasPriorities = false; + foreach (var handler in this.Handlers) + { + if (handler.Priority != EventPriority.Normal) + { + this.HasPriorities = true; + break; + } + } + } // sort by priority if needed if (this.HasPriorities) diff --git a/src/SMAPI/Framework/Input/SInputState.cs b/src/SMAPI/Framework/Input/SInputState.cs index 92a45013..4c117096 100644 --- a/src/SMAPI/Framework/Input/SInputState.cs +++ b/src/SMAPI/Framework/Input/SInputState.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; using StardewValley; @@ -211,7 +210,13 @@ internal sealed class SInputState : InputState /// The buttons to check. public bool IsAnyDown(InputButton[] buttons) { - return buttons.Any(button => this.IsDown(button.ToSButton())); + foreach (InputButton button in buttons) + { + if (this.IsDown(button.ToSButton())) + return true; + } + + return false; } /// Get the state of a button. diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index 0e4a03b8..3fc043d4 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -336,7 +336,7 @@ internal class AssemblyLoader : IDisposable for (int i = 0; i < module.AssemblyReferences.Count; i++) { // remove old assembly reference - if (this.AssemblyMap.RemoveNames.Any(name => module.AssemblyReferences[i].Name == name)) + if (this.AssemblyMap.RemoveNames.Contains(module.AssemblyReferences[i].Name)) { platformChanged = true; module.AssemblyReferences.RemoveAt(i); diff --git a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs index 39ea3a5c..a6e933f6 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RewriteHelper.cs @@ -267,14 +267,22 @@ internal static class RewriteHelper { if (reference.Name == ".ctor") { - return type - .GetConstructors(BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly | BindingFlags.Public) - .Any(method => RewriteHelper.HasMatchingSignature(method, reference)); + foreach (ConstructorInfo constructor in type.GetConstructors(BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly | BindingFlags.Public)) + { + if (RewriteHelper.HasMatchingSignature(constructor, reference)) + return true; + } + } + else + { + foreach (MethodInfo method in type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly | BindingFlags.Public)) + { + if (RewriteHelper.HasMatchingSignature(method, reference)) + return true; + } } - return type - .GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly | BindingFlags.Public) - .Any(method => RewriteHelper.HasMatchingSignature(method, reference)); + return false; } /**** diff --git a/src/SMAPI/Framework/ModLoading/ModMetadata.cs b/src/SMAPI/Framework/ModLoading/ModMetadata.cs index d2fc661d..976df51f 100644 --- a/src/SMAPI/Framework/ModLoading/ModMetadata.cs +++ b/src/SMAPI/Framework/ModLoading/ModMetadata.cs @@ -241,14 +241,23 @@ internal class ModMetadata : IModMetadata /// public bool HasValidUpdateKeys() { - return this.GetUpdateKeys(validOnly: true).Any(); + foreach (UpdateKey _ in this.GetUpdateKeys(validOnly: true)) + return true; + return false; } /// public bool HasWarnings(params ModWarning[] warnings) { ModWarning curWarnings = this.Warnings; - return warnings.Any(warning => curWarnings.HasFlag(warning)); + + foreach (ModWarning matchWarning in warnings) + { + if (curWarnings.HasFlag(matchWarning)) + return true; + } + + return false; } /// diff --git a/src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs b/src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs index da663ac1..03b1150e 100644 --- a/src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs +++ b/src/SMAPI/Framework/ModLoading/PlatformAssemblyMap.cs @@ -20,7 +20,7 @@ internal class PlatformAssemblyMap : IDisposable public readonly Platform TargetPlatform; /// The short assembly names to remove as assembly reference, and replace with the . These should be short names (like "Stardew Valley"). - public readonly IReadOnlyList RemoveNames; + public readonly HashSet RemoveNames; /**** ** Metadata @@ -42,7 +42,7 @@ internal class PlatformAssemblyMap : IDisposable /// The target game platform. /// The assembly short names to remove (like Stardew Valley). /// The assemblies to target. - public PlatformAssemblyMap(Platform targetPlatform, IReadOnlyList removeAssemblyNames, IReadOnlyList targetAssemblies) + public PlatformAssemblyMap(Platform targetPlatform, HashSet removeAssemblyNames, IReadOnlyList targetAssemblies) { // save data this.TargetPlatform = targetPlatform; diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs index 4bca259a..ac5e4fe7 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs @@ -70,8 +70,13 @@ internal class HeuristicMethodRewriter : BaseInstructionHandler var loadInstructions = method.Parameters.Skip(methodRef.Parameters.Count) .Select(p => RewriteHelper.GetLoadValueInstruction(p.Constant)) .ToArray(); - if (loadInstructions.Any(p => p == null)) - return false; // SMAPI needs to load the value onto the stack before the method call, but the optional parameter type wasn't recognized + + // skip if invalid + foreach (Instruction? loadInstruction in loadInstructions) + { + if (loadInstruction is null) + 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) diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 628bc482..2f9428f5 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -1011,8 +1011,8 @@ internal class SCore : IDisposable if (verbose) { - string addedText = added.Any() ? string.Join(", ", added.Select(p => p.Name)) : "none"; - string removedText = removed.Any() ? string.Join(", ", removed.Select(p => p.Name)) : "none"; + string addedText = added.Count > 0 ? string.Join(", ", added.Select(p => p.Name)) : "none"; + string removedText = removed.Count > 0 ? string.Join(", ", removed.Select(p => p.Name)) : "none"; this.Monitor.Log($"Context: location list changed (added {addedText}; removed {removedText}).", Monitor.ContextLogLevel); } diff --git a/src/SMAPI/Framework/StateTracking/LocationTracker.cs b/src/SMAPI/Framework/StateTracking/LocationTracker.cs index 4f0e8117..91a4e582 100644 --- a/src/SMAPI/Framework/StateTracking/LocationTracker.cs +++ b/src/SMAPI/Framework/StateTracking/LocationTracker.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Microsoft.Xna.Framework; using StardewModdingAPI.Framework.StateTracking.FieldWatchers; using StardewValley; @@ -20,6 +19,9 @@ internal class LocationTracker : IWatcher /// The underlying watchers. private readonly List Watchers = []; + /// Whether any of the changed. + private bool WatchersHaveChanges; + /********* ** Accessors @@ -28,7 +30,7 @@ internal class LocationTracker : IWatcher public string Name { get; } /// - public bool IsChanged => this.Watchers.Any(p => p.IsChanged); + public bool IsChanged => this.WatchersHaveChanges; /// The tracked location. public GameLocation Location { get; } @@ -93,9 +95,15 @@ internal class LocationTracker : IWatcher /// public void Update() { + this.WatchersHaveChanges = false; foreach (IWatcher watcher in this.Watchers) + { watcher.Update(); + if (watcher.IsChanged) + this.WatchersHaveChanges = true; + } + this.UpdateChestWatcherList(added: this.ObjectsWatcher.Added, removed: this.ObjectsWatcher.Removed); foreach (var watcher in this.ChestWatchers) @@ -105,6 +113,8 @@ internal class LocationTracker : IWatcher /// public void Reset() { + this.WatchersHaveChanges = false; + foreach (IWatcher watcher in this.Watchers) watcher.Reset(); @@ -118,7 +128,7 @@ internal class LocationTracker : IWatcher foreach (IWatcher watcher in this.Watchers) watcher.Dispose(); - foreach (var watcher in this.ChestWatchers.Values) + foreach (ChestTracker watcher in this.ChestWatchers.Values) watcher.Dispose(); } diff --git a/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs b/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs index a4667f63..6319558b 100644 --- a/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs +++ b/src/SMAPI/Framework/StateTracking/WorldLocationsTracker.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; using StardewModdingAPI.Framework.StateTracking.Comparers; using StardewModdingAPI.Framework.StateTracking.FieldWatchers; using StardewValley; @@ -33,6 +32,9 @@ internal class WorldLocationsTracker : IWatcher /// The pooled list instance for . private static readonly List PooledLocationsWithBuildingsChanged = []; + /// Whether any of the have content changes. + private bool LocationsHaveChanges; + /********* ** Accessors @@ -44,7 +46,7 @@ internal class WorldLocationsTracker : IWatcher public bool IsLocationListChanged => this.Added.Count > 0 || this.Removed.Count > 0; /// - public bool IsChanged => this.IsLocationListChanged || this.Locations.Any(p => p.IsChanged); + public bool IsChanged => this.IsLocationListChanged || this.LocationsHaveChanges; /// The tracked locations. public IEnumerable Locations => this.LocationDict.Values; @@ -77,8 +79,15 @@ internal class WorldLocationsTracker : IWatcher this.LocationListWatcher.Update(); this.MineLocationListWatcher.Update(); this.VolcanoLocationListWatcher.Update(); + + // update location content watchers + this.LocationsHaveChanges = false; foreach (LocationTracker watcher in this.Locations) + { watcher.Update(); + if (watcher.IsChanged) + this.LocationsHaveChanges = true; + } // detect added/removed locations if (this.LocationListWatcher.IsChanged) @@ -131,6 +140,8 @@ internal class WorldLocationsTracker : IWatcher /// public void Reset() { + this.LocationsHaveChanges = false; + this.ResetLocationList(); foreach (IWatcher watcher in this.GetWatchers()) watcher.Reset(); diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 50c14ab9..aa967c98 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -177,7 +177,18 @@ internal class CoreAssetPropagator this.UpdateMap(info); var newWarps = GetWarpSet(location); - changedWarpRoutes = changedWarpRoutes || oldWarps.Count != newWarps.Count || oldWarps.Any(p => !newWarps.Contains(p)); + changedWarpRoutes = changedWarpRoutes || oldWarps.Count != newWarps.Count; + if (!changedWarpRoutes) + { + foreach (string oldWarp in oldWarps) + { + if (!newWarps.Contains(oldWarp)) + { + changedWarpRoutes = true; + break; + } + } + } changed = true; } } diff --git a/src/SMAPI/SButton.cs b/src/SMAPI/SButton.cs index 2ae261ed..edcdc4db 100644 --- a/src/SMAPI/SButton.cs +++ b/src/SMAPI/SButton.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Microsoft.Xna.Framework.Input; using StardewValley; @@ -697,13 +696,31 @@ public static class SButtonExtensions /// Get whether the given button is equivalent to . public bool IsUseToolButton() { - return input == SButton.ControllerX || Game1.options.useToolButton.Any(p => p.ToSButton() == input); + if (input is SButton.ControllerX) + return true; + + foreach (InputButton button in Game1.options.useToolButton) + { + if (button.ToSButton() == input) + return true; + } + + return false; } /// Get whether the given button is equivalent to . public bool IsActionButton() { - return input == SButton.ControllerA || Game1.options.actionButton.Any(p => p.ToSButton() == input); + if (input is SButton.ControllerA) + return true; + + foreach (InputButton button in Game1.options.actionButton) + { + if (button.ToSButton() == input) + return true; + } + + return false; } } } diff --git a/src/SMAPI/Utilities/Keybind.cs b/src/SMAPI/Utilities/Keybind.cs index bf3e7597..2c26ccbe 100644 --- a/src/SMAPI/Utilities/Keybind.cs +++ b/src/SMAPI/Utilities/Keybind.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using StardewModdingAPI.Framework; namespace StardewModdingAPI.Utilities; @@ -36,7 +35,15 @@ public class Keybind public Keybind(params SButton[] buttons) { this.Buttons = buttons; - this.IsBound = buttons.Any(p => p != SButton.None); + + foreach (SButton button in buttons) + { + if (button != SButton.None) + { + this.IsBound = true; + break; + } + } } /// Parse a keybind string, if it's valid.