From d9d33c59490cdd175f0c64a3032e5938d5ab9e5f Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 27 Mar 2026 23:59:33 -0400 Subject: [PATCH] reduce remaining uses of LINQ on warmer paths --- src/SMAPI/Framework/Content/ContentCache.cs | 3 +- .../ContentManagers/GameContentManager.cs | 2 +- .../Extensions/ClickableMenuExtensions.cs | 12 +++---- .../Framework/ModHelpers/GameContentHelper.cs | 1 - .../Framework/ModLoading/AssemblyLoader.cs | 14 ++++---- .../Finders/ReferenceToInvalidMemberFinder.cs | 19 ++++++++--- .../ModLoading/Framework/RecursiveRewriter.cs | 17 +++++++--- .../Rewriters/HeuristicFieldRewriter.cs | 24 ++++++++++---- .../Rewriters/HeuristicMethodRewriter.cs | 32 +++++++++++++------ .../ModLoading/TypeReferenceComparer.cs | 24 +++++++------- src/SMAPI/Framework/SCore.cs | 2 +- src/SMAPI/Framework/SGameRunner.cs | 11 ++++--- .../FieldWatchers/ComparableListWatcher.cs | 13 ++++++-- .../ObservableCollectionWatcher.cs | 31 ++++++++++-------- .../StateTracking/Snapshots/PlayerSnapshot.cs | 3 +- .../JsonSettingsWrapper.cs | 3 +- src/SMAPI/Metadata/CoreAssetPropagator.cs | 11 ++++--- src/SMAPI/Program.cs | 1 - src/SMAPI/Utilities/KeybindList.cs | 10 ++++-- 19 files changed, 149 insertions(+), 84 deletions(-) diff --git a/src/SMAPI/Framework/Content/ContentCache.cs b/src/SMAPI/Framework/Content/ContentCache.cs index d2f5647b..132bd44d 100644 --- a/src/SMAPI/Framework/Content/ContentCache.cs +++ b/src/SMAPI/Framework/Content/ContentCache.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; -using System.Linq; using StardewModdingAPI.Toolkit.Utilities; namespace StardewModdingAPI.Framework.Content; @@ -117,7 +116,7 @@ internal class ContentCache this.Remove(key, dispose); return removed.Count == 0 - ? Enumerable.Empty() // let GC collect the list in gen0 instead of potentially living longer + ? Array.Empty() // let GC collect the list in gen0 instead of potentially living longer : removed; } } diff --git a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs index 1ec60507..106f338c 100644 --- a/src/SMAPI/Framework/ContentManagers/GameContentManager.cs +++ b/src/SMAPI/Framework/ContentManagers/GameContentManager.cs @@ -170,7 +170,7 @@ internal class GameContentManager : BaseContentManager return null; } - loader = loadOperations.OrderByDescending(p => p.Priority).FirstOrDefault(); + loader = loadOperations.MaxBy(p => p.Priority); } if (loader == null) return null; diff --git a/src/SMAPI/Framework/Extensions/ClickableMenuExtensions.cs b/src/SMAPI/Framework/Extensions/ClickableMenuExtensions.cs index 3d1cef28..1058a2dc 100644 --- a/src/SMAPI/Framework/Extensions/ClickableMenuExtensions.cs +++ b/src/SMAPI/Framework/Extensions/ClickableMenuExtensions.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using StardewValley.Menus; namespace StardewModdingAPI.Framework.Extensions; @@ -13,13 +12,12 @@ internal static class ClickableMenuExtensions /// Get a string representation of the menu chain to the given menu (including the specified menu), in parent to child order. public string GetMenuChainLabel() { - static IEnumerable GetAncestors(IClickableMenu menu) - { - for (; menu != null; menu = menu.GetParentMenu()) - yield return menu; - } + Stack chain = []; - return string.Join(" > ", GetAncestors(menu).Reverse().Select(p => p.GetType().FullName)); + for (; menu != null; menu = menu.GetParentMenu()) + chain.Push(menu.GetType().FullName!); + + return string.Join(" > ", chain); } } } diff --git a/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs index 501f8d44..ad76269c 100644 --- a/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs +++ b/src/SMAPI/Framework/ModHelpers/GameContentHelper.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using StardewModdingAPI.Framework.Content; using StardewModdingAPI.Framework.ContentManagers; using StardewModdingAPI.Framework.Exceptions; diff --git a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs index fb845eaa..b6f01de6 100644 --- a/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs +++ b/src/SMAPI/Framework/ModLoading/AssemblyLoader.cs @@ -114,12 +114,14 @@ internal class AssemblyLoader : IDisposable // get referenced local assemblies AssemblyParseResult[] assemblies; { - HashSet visitedAssemblyNames = new( // 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 - ); + // don't try loading assemblies that are already loaded + HashSet visitedAssemblyNames = []; + foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + string? name = assembly.GetName().Name; + if (name != null) + visitedAssemblyNames.Add(name); + } assemblies = this.GetReferencedLocalAssemblies(assemblyFile, visitedAssemblyNames, this.AssemblyDefinitionResolver).ToArray(); } diff --git a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToInvalidMemberFinder.cs b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToInvalidMemberFinder.cs index 6a14c576..5ae63758 100644 --- a/src/SMAPI/Framework/ModLoading/Finders/ReferenceToInvalidMemberFinder.cs +++ b/src/SMAPI/Framework/ModLoading/Finders/ReferenceToInvalidMemberFinder.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -41,13 +40,25 @@ internal class ReferenceToInvalidMemberFinder : BaseInstructionHandler FieldReference? fieldRef = RewriteHelper.AsFieldReference(instruction); if (fieldRef != null && this.ShouldValidate(fieldRef.DeclaringType)) { - FieldDefinition? targetField = fieldRef.DeclaringType.Resolve()?.Fields.FirstOrDefault(p => p.Name == fieldRef.Name); + // resolve field + FieldDefinition? targetField = null; + if (fieldRef.DeclaringType.Resolve() is { } declaringType) + { + foreach (FieldDefinition field in declaringType.Fields) + { + if (field.Name == fieldRef.Name) + { + targetField = field; + break; + } + } + } - // wrong return type + // detect wrong return type if (targetField != null && !RewriteHelper.LooksLikeSameType(fieldRef.FieldType, targetField.FieldType)) this.MarkFlag(InstructionHandleResult.NotCompatible, $"reference to {this.GetMemberDisplayName(fieldRef)} (field returns {this.GetFriendlyTypeName(targetField.FieldType)}, not {this.GetFriendlyTypeName(fieldRef.FieldType)})"); - // missing + // detect missing else if (targetField == null || targetField.HasConstant || !RewriteHelper.HasSameNamespaceAndName(fieldRef.DeclaringType, targetField.DeclaringType)) this.MarkFlag(InstructionHandleResult.NotCompatible, $"reference to {this.GetMemberDisplayName(fieldRef)} (no such field)"); diff --git a/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs b/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs index bea1d89f..240bc5a5 100644 --- a/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Framework/RecursiveRewriter.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Collections.Generic; @@ -292,10 +291,18 @@ internal class RecursiveRewriter if (curChanged) { // get constructor - MethodDefinition? constructor = (newAttrType ?? attribute.AttributeType) - .Resolve() - ?.Methods - .FirstOrDefault(method => method.IsConstructor && RewriteHelper.HasMatchingSignature(method, attribute.Constructor)); + MethodDefinition? constructor = null; + if ((newAttrType ?? attribute.AttributeType).Resolve() is { } typeDef) + { + foreach (MethodDefinition method in typeDef.Methods) + { + if (method.IsConstructor && RewriteHelper.HasMatchingSignature(method, attribute.Constructor)) + { + constructor = method; + break; + } + } + } if (constructor == null) throw new InvalidOperationException($"Can't rewrite attribute type '{attribute.AttributeType.FullName}' to '{newAttrType?.FullName}', no equivalent constructor found."); diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs index 753ed31b..1416457d 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicFieldRewriter.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; using StardewModdingAPI.Framework.ModLoading.Framework; @@ -67,17 +66,30 @@ internal class HeuristicFieldRewriter : BaseInstructionHandler /// The field reference. /// The type on which the field was defined. /// Whether the field is being read; else it's being written to. - private bool TryRewriteToProperty(ModuleDefinition module, Instruction instruction, FieldReference fieldRef, TypeDefinition declaringType, bool isRead) + 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; - if (method == null) + MethodDefinition? accessor = null; + if (declaringType is not null) + { + PropertyDefinition? targetProperty = null; + foreach (PropertyDefinition property in declaringType.Properties) + { + if (property.Name == fieldRef.Name) + { + targetProperty = property; + break; + } + } + + accessor = isRead ? targetProperty?.GetMethod : targetProperty?.SetMethod; + } + if (accessor is null) return false; // rewrite field to property instruction.OpCode = OpCodes.Call; - instruction.Operand = module.ImportReference(method); + instruction.Operand = module.ImportReference(accessor); this.Phrases.Add($"{fieldRef.DeclaringType.Name}.{fieldRef.Name} (field => property)"); return this.MarkRewritten(); diff --git a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs index ac5e4fe7..44962703 100644 --- a/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs +++ b/src/SMAPI/Framework/ModLoading/Rewriters/HeuristicMethodRewriter.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; +using Mono.Collections.Generic; using StardewModdingAPI.Framework.ModLoading.Framework; namespace StardewModdingAPI.Framework.ModLoading.Rewriters; @@ -46,6 +46,7 @@ internal class HeuristicMethodRewriter : BaseInstructionHandler return false; // get method definition + int firstMissingIndex = methodRef.Parameters.Count; MethodDefinition? method = null; foreach (MethodDefinition match in type.Methods) { @@ -53,11 +54,11 @@ internal class HeuristicMethodRewriter : BaseInstructionHandler continue; // reference matches initial parameters of definition - if (methodRef.Parameters.Count >= match.Parameters.Count || !this.InitialParametersMatch(methodRef, match)) + if (firstMissingIndex >= match.Parameters.Count || !this.InitialParametersMatch(methodRef, match)) continue; // all remaining parameters in definition are optional - if (!match.Parameters.Skip(methodRef.Parameters.Count).All(p => p.IsOptional)) + if (!this.RemainingParametersAreOptional(match.Parameters, startAt: firstMissingIndex)) continue; method = match; @@ -67,15 +68,14 @@ internal class HeuristicMethodRewriter : BaseInstructionHandler return false; // get instructions to inject parameter values - var loadInstructions = method.Parameters.Skip(methodRef.Parameters.Count) - .Select(p => RewriteHelper.GetLoadValueInstruction(p.Constant)) - .ToArray(); - - // skip if invalid - foreach (Instruction? loadInstruction in loadInstructions) + Instruction[] loadInstructions = new Instruction[method.Parameters.Count - firstMissingIndex]; + for (int i = 0; i + firstMissingIndex < method.Parameters.Count; i++) { + Instruction? loadInstruction = RewriteHelper.GetLoadValueInstruction(method.Parameters[i + firstMissingIndex].Constant); 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 + + loadInstructions[i] = loadInstruction; } // rewrite method reference @@ -114,4 +114,18 @@ internal class HeuristicMethodRewriter : BaseInstructionHandler return true; } + + /// Get whether all parameters starting from the given index are marked optional. + /// The parameters to check. + /// The index of the first parameter to check. + private bool RemainingParametersAreOptional(Collection parameters, int startAt) + { + for (int i = startAt; i < parameters.Count; i++) + { + if (!parameters[i].IsOptional) + return false; + } + + return true; + } } diff --git a/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs b/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs index b16bda80..1367a386 100644 --- a/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs +++ b/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Mono.Cecil; namespace StardewModdingAPI.Framework.ModLoading; @@ -73,12 +72,12 @@ internal class TypeReferenceComparer : IEqualityComparer typeNameB = this.MapPlaceholder(placeholder: typeNameB, type: typeNameA, map: tokenMap); // compare inner tokens - string[] symbolsA = this.GetTypeSymbols(typeNameA).ToArray(); - string[] symbolsB = this.GetTypeSymbols(typeNameB).ToArray(); - if (symbolsA.Length != symbolsB.Length) + IReadOnlyList symbolsA = this.GetTypeSymbols(typeNameA); + IReadOnlyList symbolsB = this.GetTypeSymbols(typeNameB); + if (symbolsA.Count != symbolsB.Count) return false; - for (int i = 0; i < symbolsA.Length; i++) + for (int i = 0; i < symbolsA.Count; i++) { if (!HeuristicallyEqualsImpl(symbolsA[i], symbolsB[i], tokenMap)) return false; @@ -106,10 +105,11 @@ internal class TypeReferenceComparer : IEqualityComparer /// Get the top-level type symbols in a type name (e.g. List and NetRef<T> in List<NetRef<T>>) /// The full type name. - private IEnumerable GetTypeSymbols(string typeName) + private IReadOnlyList GetTypeSymbols(string typeName) { - int openGenerics = 0; + List symbols = []; + int openGenerics = 0; Queue queue = new(typeName); string symbol = ""; while (queue.Count > 0) @@ -129,7 +129,7 @@ internal class TypeReferenceComparer : IEqualityComparer { // start new generic symbol case 0: - yield return symbol; + symbols.Add(symbol); symbol = ""; openGenerics++; break; @@ -152,7 +152,7 @@ internal class TypeReferenceComparer : IEqualityComparer // start next generic symbol case 1: - yield return symbol; + symbols.Add(symbol); symbol = ""; break; @@ -174,7 +174,7 @@ internal class TypeReferenceComparer : IEqualityComparer // end generic symbol case 1: - yield return symbol; + symbols.Add(symbol); symbol = ""; openGenerics--; break; @@ -195,6 +195,8 @@ internal class TypeReferenceComparer : IEqualityComparer } if (symbol != "") - yield return symbol; + symbols.Add(symbol); + + return symbols; } } diff --git a/src/SMAPI/Framework/SCore.cs b/src/SMAPI/Framework/SCore.cs index 2f9428f5..bf701f7d 100644 --- a/src/SMAPI/Framework/SCore.cs +++ b/src/SMAPI/Framework/SCore.cs @@ -883,7 +883,7 @@ internal class SCore : IDisposable string context = $"Context: loaded save '{Constants.SaveFolderName}', starting {Game1.currentSeason} {Game1.dayOfMonth} Y{Game1.year}, locale set to {this.ContentCore.GetLocale()}."; if (Context.IsMultiplayer) { - int onlineCount = Game1.getOnlineFarmers().Count(); + int onlineCount = Game1.getOnlineFarmers().Count; context += $" {(Context.IsMainPlayer ? "Main player" : "Farmhand")} with {onlineCount} {(onlineCount == 1 ? "player" : "players")} online."; } else diff --git a/src/SMAPI/Framework/SGameRunner.cs b/src/SMAPI/Framework/SGameRunner.cs index 25548d89..2362cdf4 100644 --- a/src/SMAPI/Framework/SGameRunner.cs +++ b/src/SMAPI/Framework/SGameRunner.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Enums; @@ -145,9 +144,13 @@ internal class SGameRunner : GameRunner /// The player ID to check. public int? GetScreenId(long playerId) { - return this.gameInstances - .FirstOrDefault(p => ((SGame)p).PlayerId == playerId) - ?.instanceId; + foreach (Game1 gameInstance in this.gameInstances) + { + if (((SGame)gameInstance).PlayerId == playerId) + return gameInstance.instanceId; + } + + return null; } diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs index ef61f2c7..4e5c06b3 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ComparableListWatcher.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers; @@ -71,8 +70,16 @@ internal class ComparableListWatcher : BaseDisposableWatcher, ICollectio // detect changes HashSet curValues = new HashSet(this.CurrentValues, this.LastValues.Comparer); - this.RemovedImpl.AddRange(from value in this.LastValues where !curValues.Contains(value) select value); - this.AddedImpl.AddRange(from value in curValues where !this.LastValues.Contains(value) select value); + foreach (TValue value in this.LastValues) + { + if (!curValues.Contains(value)) + this.RemovedImpl.Add(value); + } + foreach (TValue value in curValues) + { + if (!this.LastValues.Contains(value)) + this.AddedImpl.Add(value); + } this.LastValues = curValues; } diff --git a/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs b/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs index 8f3f0db6..09952266 100644 --- a/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs +++ b/src/SMAPI/Framework/StateTracking/FieldWatchers/ObservableCollectionWatcher.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; -using System.Linq; namespace StardewModdingAPI.Framework.StateTracking.FieldWatchers; @@ -86,25 +85,31 @@ internal class ObservableCollectionWatcher : BaseDisposableWatcher, ICol /// The event arguments. private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { + // clear if (e.Action == NotifyCollectionChangedAction.Reset) { this.RemovedImpl.AddRange(this.PreviousValues); this.PreviousValues.Clear(); + return; } - else - { - TValue[]? added = e.NewItems?.Cast().ToArray(); - TValue[]? removed = e.OldItems?.Cast().ToArray(); - if (removed != null) + // removed items + if (e.OldItems != null) + { + foreach (TValue value in e.OldItems) + this.RemovedImpl.Add(value); + this.PreviousValues.RemoveRange(e.OldStartingIndex, e.OldItems.Count); + } + + // added items + if (e.NewItems != null) + { + int insertAt = e.NewStartingIndex; + foreach (TValue value in e.NewItems) { - this.RemovedImpl.AddRange(removed); - this.PreviousValues.RemoveRange(e.OldStartingIndex, removed.Length); - } - if (added != null) - { - this.AddedImpl.AddRange(added); - this.PreviousValues.InsertRange(e.NewStartingIndex, added); + this.AddedImpl.Add(value); + this.PreviousValues.Insert(insertAt, value); + insertAt++; } } } diff --git a/src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs b/src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs index d79caf73..52efefa6 100644 --- a/src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs +++ b/src/SMAPI/Framework/StateTracking/Snapshots/PlayerSnapshot.cs @@ -28,8 +28,7 @@ internal class PlayerSnapshot /// Tracks changes to the player's skill levels. public IDictionary> Skills { get; } = Enum - .GetValues(typeof(SkillType)) - .Cast() + .GetValues() .ToDictionary(skill => skill, _ => new SnapshotDiff()); /// Get a list of inventory changes. diff --git a/src/SMAPI/Integrations/GenericModConfigMenu/JsonSettingsWrapper.cs b/src/SMAPI/Integrations/GenericModConfigMenu/JsonSettingsWrapper.cs index b06d43e5..e1b551fc 100644 --- a/src/SMAPI/Integrations/GenericModConfigMenu/JsonSettingsWrapper.cs +++ b/src/SMAPI/Integrations/GenericModConfigMenu/JsonSettingsWrapper.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -163,7 +162,7 @@ internal class JsonSettingsWrapper { oldSet = new HashSet(oldSet, StringComparer.OrdinalIgnoreCase); - bool changed = newValue.Count != oldSet.Count || !oldSet.All(newValue.Contains); + bool changed = newValue.Count != oldSet.Count || !oldSet.IsSubsetOf(newValue); if (changed) { this.Properties[fieldName] = JToken.FromObject(newValue); diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index aa967c98..57e3ccc7 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -754,8 +754,11 @@ internal class CoreAssetPropagator { List characters = []; - foreach (NPC character in this.GetLocations().SelectMany(p => p.characters)) - characters.Add(character); + foreach (GameLocation location in this.GetLocations()) + { + foreach (NPC character in location.characters) + characters.Add(character); + } if (Game1.CurrentEvent?.actors != null) { @@ -823,9 +826,9 @@ internal class CoreAssetPropagator // get child locations if (buildingInteriors) { - foreach (GameLocation location in locations.Select(p => p.Location).ToArray()) + foreach (LocationInfo location in locations.ToArray()) { - foreach (Building building in location.buildings) + foreach (Building building in location.Location.buildings) { GameLocation indoors = building.indoors.Value; if (indoors is not null) diff --git a/src/SMAPI/Program.cs b/src/SMAPI/Program.cs index 0d909653..8040d68a 100644 --- a/src/SMAPI/Program.cs +++ b/src/SMAPI/Program.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Linq; using System.Reflection; using System.Threading; using StardewModdingAPI.Framework; diff --git a/src/SMAPI/Utilities/KeybindList.cs b/src/SMAPI/Utilities/KeybindList.cs index 04954a33..e459b1c4 100644 --- a/src/SMAPI/Utilities/KeybindList.cs +++ b/src/SMAPI/Utilities/KeybindList.cs @@ -152,7 +152,13 @@ public class KeybindList /// Get the keybind which is currently down, if any. If there are multiple keybinds down, the first one is returned. public Keybind? GetKeybindCurrentlyDown() { - return this.Keybinds.FirstOrDefault(p => p.GetState().IsDown()); + foreach (Keybind keybind in this.Keybinds) + { + if (keybind.GetState().IsDown()) + return keybind; + } + + return null; } /// Get a string representation of the input binding. @@ -161,6 +167,6 @@ public class KeybindList { return this.Keybinds.Length > 0 ? string.Join(", ", this.Keybinds.Select(p => p.ToString())) - : SButton.None.ToString(); + : nameof(SButton.None); } }