From b7e0ed9b870b9241473b3099a7585a52d9894c2e Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Fri, 7 Jul 2023 08:31:29 +0100 Subject: [PATCH] Improve lookups of nodes by key in MiniYaml. When handling the Nodes collection in MiniYaml, individual nodes are located via one of two methods: // Lookup a single key with linear search. var node = yaml.Nodes.FirstOrDefault(n => n.Key == "SomeKey"); // Convert to dictionary, expecting many key lookups. var dict = nodes.ToDictionary(); // Lookup a single key in the dictionary. var node = dict["SomeKey"]; To simplify lookup of individual keys via linear search, provide helper methods NodeWithKeyOrDefault and NodeWithKey. These helpers do the equivalent of Single{OrDefault} searches. Whilst this requires checking the whole list, it provides a useful correctness check. Two duplicated keys in TS yaml are fixed as a result. We can also optimize the helpers to not use LINQ, avoiding allocation of the delegate to search for a key. Adjust existing code to use either lnear searches or dictionary lookups based on whether it will be resolving many keys. Resolving few keys can be done with linear searches to avoid building a dictionary. Resolving many keys should be done with a dictionary to avoid quaradtic runtime from repeated linear searches. --- OpenRA.Game/ExternalMods.cs | 6 +-- OpenRA.Game/GameRules/SoundInfo.cs | 6 +-- OpenRA.Game/GameRules/WeaponInfo.cs | 3 +- OpenRA.Game/GameSpeed.cs | 3 +- OpenRA.Game/Graphics/CursorProvider.cs | 6 +-- OpenRA.Game/HotkeyDefinition.cs | 28 +++++------- OpenRA.Game/Manifest.cs | 2 +- OpenRA.Game/Map/Map.cs | 10 ++--- OpenRA.Game/MiniYaml.cs | 38 +++++++++++++--- OpenRA.Game/Network/GameServer.cs | 4 +- OpenRA.Game/Network/LocalizedMessage.cs | 2 +- OpenRA.Game/Network/Session.cs | 2 +- OpenRA.Game/PlayerDatabase.cs | 9 ++-- OpenRA.Game/PlayerProfile.cs | 3 +- OpenRA.Game/Settings.cs | 2 +- OpenRA.Game/Traits/TraitsInterfaces.cs | 3 +- OpenRA.Game/Widgets/WidgetLoader.cs | 2 +- OpenRA.Game/World.cs | 2 +- .../ClassicTilesetSpecificSpriteSequence.cs | 9 ++-- .../UtilityCommands/ImportGen1MapCommand.cs | 6 +-- .../Graphics/DefaultSpriteSequence.cs | 8 ++-- .../Graphics/TilesetSpecificSpriteSequence.cs | 9 ++-- .../Installer/InstallerUtils.cs | 5 +-- .../SourceActions/ExtractIscabSourceAction.cs | 5 +-- .../SourceActions/ExtractRawSourceAction.cs | 5 +-- .../SourceResolvers/GogSourceResolver.cs | 4 +- .../SourceResolvers/SteamSourceResolver.cs | 4 +- OpenRA.Mods.Common/Lint/CheckCursors.cs | 4 +- OpenRA.Mods.Common/ModContent.cs | 12 ++--- OpenRA.Mods.Common/Terrain/TerrainInfo.cs | 2 +- .../Traits/AppearsOnMapPreview.cs | 2 +- .../Traits/BotModules/BaseBuilderBotModule.cs | 7 ++- .../Traits/BotModules/McvManagerBotModule.cs | 5 +-- .../BotModules/SquadManagerBotModule.cs | 45 ++++++++----------- .../Traits/BotModules/Squads/Squad.cs | 8 ++-- .../BotModules/SupportPowerBotModule.cs | 7 ++- .../Traits/BotModules/UnitBuilderBotModule.cs | 7 ++- .../Traits/Buildings/Building.cs | 4 +- OpenRA.Mods.Common/Traits/HitShape.cs | 2 +- .../Traits/Player/GameSaveViewportManager.cs | 7 ++- .../Traits/World/ControlGroups.cs | 4 +- .../Traits/World/EditorResourceLayer.cs | 2 +- OpenRA.Mods.Common/Traits/World/Locomotor.cs | 8 ++-- .../Traits/World/ResourceLayer.cs | 2 +- .../Traits/World/ResourceRenderer.cs | 2 +- OpenRA.Mods.Common/Traits/World/Selection.cs | 5 +-- .../Traits/World/SmudgeLayer.cs | 6 +-- .../20230225/ExplicitSequenceFilenames.cs | 2 +- OpenRA.Mods.Common/UpdateRules/UpdateUtils.cs | 16 +++---- .../Widgets/ControlGroupsWidget.cs | 11 +++-- .../Installation/InstallFromSourceLogic.cs | 8 ++-- .../Widgets/Logic/ServerListLogic.cs | 4 +- .../Logic/Settings/HotkeysSettingsLogic.cs | 2 +- .../Widgets/ProductionPaletteWidget.cs | 4 +- .../Widgets/SupportPowersWidget.cs | 4 +- .../Widgets/ViewportControllerWidget.cs | 6 +-- .../UtilityCommands/D2kMapImporter.cs | 2 +- OpenRA.Test/OpenRA.Game/MiniYamlTest.cs | 4 +- mods/ts/sequences/civilian.yaml | 1 - mods/ts/sequences/structures.yaml | 1 - 60 files changed, 196 insertions(+), 196 deletions(-) diff --git a/OpenRA.Game/ExternalMods.cs b/OpenRA.Game/ExternalMods.cs index 3decd6b20c..40dc1c30c4 100644 --- a/OpenRA.Game/ExternalMods.cs +++ b/OpenRA.Game/ExternalMods.cs @@ -94,17 +94,17 @@ namespace OpenRA if (sheetBuilder != null) { - var iconNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon"); + var iconNode = yaml.NodeWithKeyOrDefault("Icon"); if (iconNode != null && !string.IsNullOrEmpty(iconNode.Value.Value)) using (var stream = new MemoryStream(Convert.FromBase64String(iconNode.Value.Value))) mod.Icon = sheetBuilder.Add(new Png(stream)); - var icon2xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon2x"); + var icon2xNode = yaml.NodeWithKeyOrDefault("Icon2x"); if (icon2xNode != null && !string.IsNullOrEmpty(icon2xNode.Value.Value)) using (var stream = new MemoryStream(Convert.FromBase64String(icon2xNode.Value.Value))) mod.Icon2x = sheetBuilder.Add(new Png(stream), 1f / 2); - var icon3xNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon3x"); + var icon3xNode = yaml.NodeWithKeyOrDefault("Icon3x"); if (icon3xNode != null && !string.IsNullOrEmpty(icon3xNode.Value.Value)) using (var stream = new MemoryStream(Convert.FromBase64String(icon3xNode.Value.Value))) mod.Icon3x = sheetBuilder.Add(new Png(stream), 1f / 3); diff --git a/OpenRA.Game/GameRules/SoundInfo.cs b/OpenRA.Game/GameRules/SoundInfo.cs index b42fd281f2..e2711ccf45 100644 --- a/OpenRA.Game/GameRules/SoundInfo.cs +++ b/OpenRA.Game/GameRules/SoundInfo.cs @@ -40,16 +40,16 @@ namespace OpenRA.GameRules static Dictionary ParseSoundPool(MiniYaml y, string key) { var ret = new Dictionary(); - var classifiction = y.Nodes.First(x => x.Key == key); + var classifiction = y.NodeWithKey(key); foreach (var t in classifiction.Value.Nodes) { var volumeModifier = 1f; - var volumeModifierNode = t.Value.Nodes.FirstOrDefault(x => x.Key == nameof(SoundPool.VolumeModifier)); + var volumeModifierNode = t.Value.NodeWithKeyOrDefault(nameof(SoundPool.VolumeModifier)); if (volumeModifierNode != null) volumeModifier = FieldLoader.GetValue(volumeModifierNode.Key, volumeModifierNode.Value.Value); var interruptType = SoundPool.DefaultInterruptType; - var interruptTypeNode = t.Value.Nodes.FirstOrDefault(x => x.Key == nameof(SoundPool.InterruptType)); + var interruptTypeNode = t.Value.NodeWithKeyOrDefault(nameof(SoundPool.InterruptType)); if (interruptTypeNode != null) interruptType = FieldLoader.GetValue(interruptTypeNode.Key, interruptTypeNode.Value.Value); diff --git a/OpenRA.Game/GameRules/WeaponInfo.cs b/OpenRA.Game/GameRules/WeaponInfo.cs index b4b76807dc..bac06ff470 100644 --- a/OpenRA.Game/GameRules/WeaponInfo.cs +++ b/OpenRA.Game/GameRules/WeaponInfo.cs @@ -145,7 +145,8 @@ namespace OpenRA.GameRules static object LoadProjectile(MiniYaml yaml) { - if (!yaml.ToDictionary().TryGetValue("Projectile", out var proj)) + var proj = yaml.NodeWithKeyOrDefault("Projectile")?.Value; + if (proj == null) return null; var ret = Game.CreateObject(proj.Value + "Info"); diff --git a/OpenRA.Game/GameSpeed.cs b/OpenRA.Game/GameSpeed.cs index 17233e4a52..ccb93d6c5a 100644 --- a/OpenRA.Game/GameSpeed.cs +++ b/OpenRA.Game/GameSpeed.cs @@ -10,7 +10,6 @@ #endregion using System.Collections.Generic; -using System.Linq; namespace OpenRA { @@ -38,7 +37,7 @@ namespace OpenRA static object LoadSpeeds(MiniYaml y) { var ret = new Dictionary(); - var speedsNode = y.Nodes.FirstOrDefault(n => n.Key == "Speeds"); + var speedsNode = y.NodeWithKeyOrDefault("Speeds"); if (speedsNode == null) throw new YamlException("Error parsing GameSpeeds: Missing Speeds node!"); diff --git a/OpenRA.Game/Graphics/CursorProvider.cs b/OpenRA.Game/Graphics/CursorProvider.cs index 04c623885e..85919dbe7d 100644 --- a/OpenRA.Game/Graphics/CursorProvider.cs +++ b/OpenRA.Game/Graphics/CursorProvider.cs @@ -27,7 +27,7 @@ namespace OpenRA.Graphics var sequenceYaml = MiniYaml.Merge(modData.Manifest.Cursors.Select( s => MiniYaml.FromStream(fileSystem.Open(s), s))); - var nodesDict = new MiniYaml(null, sequenceYaml).ToDictionary(); + var cursorsYaml = new MiniYaml(null, sequenceYaml).NodeWithKey("Cursors").Value; // Overwrite previous definitions if there are duplicates var pals = new Dictionary(); @@ -35,14 +35,14 @@ namespace OpenRA.Graphics if (p.Palette != null) pals[p.Palette] = p; - Palettes = nodesDict["Cursors"].Nodes.Select(n => n.Value.Value) + Palettes = cursorsYaml.Nodes.Select(n => n.Value.Value) .Where(p => p != null) .Distinct() .ToDictionary(p => p, p => pals[p].ReadPalette(modData.DefaultFileSystem)); var frameCache = new FrameCache(fileSystem, modData.SpriteLoaders); var cursors = new Dictionary(); - foreach (var s in nodesDict["Cursors"].Nodes) + foreach (var s in cursorsYaml.Nodes) foreach (var sequence in s.Value.Nodes) cursors.Add(sequence.Key, new CursorSequence(frameCache, sequence.Key, s.Key, s.Value.Value, sequence.Value)); diff --git a/OpenRA.Game/HotkeyDefinition.cs b/OpenRA.Game/HotkeyDefinition.cs index 84eef05295..ad4a09a85a 100644 --- a/OpenRA.Game/HotkeyDefinition.cs +++ b/OpenRA.Game/HotkeyDefinition.cs @@ -10,7 +10,6 @@ #endregion using System.Collections.Generic; -using System.Linq; namespace OpenRA { @@ -31,29 +30,26 @@ namespace OpenRA if (!string.IsNullOrEmpty(node.Value)) Default = FieldLoader.GetValue("value", node.Value); - var descriptionNode = node.Nodes.FirstOrDefault(n => n.Key == "Description"); - if (descriptionNode != null) - Description = descriptionNode.Value.Value; + var nodeDict = node.ToDictionary(); - var typesNode = node.Nodes.FirstOrDefault(n => n.Key == "Types"); - if (typesNode != null) - Types = FieldLoader.GetValue>("Types", typesNode.Value.Value); + if (nodeDict.TryGetValue("Description", out var descriptionYaml)) + Description = descriptionYaml.Value; - var contextsNode = node.Nodes.FirstOrDefault(n => n.Key == "Contexts"); - if (contextsNode != null) - Contexts = FieldLoader.GetValue>("Contexts", contextsNode.Value.Value); + if (nodeDict.TryGetValue("Types", out var typesYaml)) + Types = FieldLoader.GetValue>("Types", typesYaml.Value); - var platformNode = node.Nodes.FirstOrDefault(n => n.Key == "Platform"); - if (platformNode != null) + if (nodeDict.TryGetValue("Contexts", out var contextYaml)) + Contexts = FieldLoader.GetValue>("Contexts", contextYaml.Value); + + if (nodeDict.TryGetValue("Platform", out var platformYaml)) { - var platformOverride = platformNode.Value.Nodes.FirstOrDefault(n => n.Key == Platform.CurrentPlatform.ToString()); + var platformOverride = platformYaml.NodeWithKeyOrDefault(Platform.CurrentPlatform.ToString()); if (platformOverride != null) Default = FieldLoader.GetValue("value", platformOverride.Value.Value); } - var readonlyNode = node.Nodes.FirstOrDefault(n => n.Key == "Readonly"); - if (readonlyNode != null) - Readonly = FieldLoader.GetValue("Readonly", readonlyNode.Value.Value); + if (nodeDict.TryGetValue("Readonly", out var readonlyYaml)) + Readonly = FieldLoader.GetValue("Readonly", readonlyYaml.Value); } } } diff --git a/OpenRA.Game/Manifest.cs b/OpenRA.Game/Manifest.cs index 4405cfc684..8f3da202da 100644 --- a/OpenRA.Game/Manifest.cs +++ b/OpenRA.Game/Manifest.cs @@ -214,7 +214,7 @@ namespace OpenRA if (!yaml.ContainsKey(key)) return Array.Empty(); - return yaml[key].ToDictionary().Keys.ToArray(); + return yaml[key].Nodes.Select(n => n.Key).ToArray(); } static IReadOnlyDictionary YamlDictionary(Dictionary yaml, string key) diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index 2b386c1576..f506b2e733 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -95,9 +95,9 @@ namespace OpenRA t == typeof(MiniYaml) ? Type.MiniYaml : Type.Normal; } - public void Deserialize(Map map, ImmutableArray nodes) + public void Deserialize(Map map, MiniYaml yaml) { - var node = nodes.FirstOrDefault(n => n.Key == key); + var node = yaml.NodeWithKeyOrDefault(key); if (node == null) { if (required) @@ -363,13 +363,13 @@ namespace OpenRA var yaml = new MiniYaml(null, MiniYaml.FromStream(Package.GetStream("map.yaml"), package.Name)); foreach (var field in YamlFields) - field.Deserialize(this, yaml.Nodes); + field.Deserialize(this, yaml); if (MapFormat < SupportedMapFormat) throw new InvalidDataException($"Map format {MapFormat} is not supported.\n File: {package.Name}"); - PlayerDefinitions = MiniYaml.NodesOrEmpty(yaml, "Players"); - ActorDefinitions = MiniYaml.NodesOrEmpty(yaml, "Actors"); + PlayerDefinitions = yaml.NodeWithKeyOrDefault("Players")?.Value.Nodes ?? ImmutableArray.Empty; + ActorDefinitions = yaml.NodeWithKeyOrDefault("Actors")?.Value.Nodes ?? ImmutableArray.Empty; Grid = modData.Manifest.Get(); diff --git a/OpenRA.Game/MiniYaml.cs b/OpenRA.Game/MiniYaml.cs index a28ca60fd0..7807389a7b 100644 --- a/OpenRA.Game/MiniYaml.cs +++ b/OpenRA.Game/MiniYaml.cs @@ -141,6 +141,34 @@ namespace OpenRA return new MiniYaml(Value, newNodes); } + public MiniYamlNode NodeWithKey(string key) + { + var result = NodeWithKeyOrDefault(key); + if (result == null) + throw new InvalidDataException($"No node with key '{key}'"); + return result; + } + + public MiniYamlNode NodeWithKeyOrDefault(string key) + { + // PERF: Avoid LINQ. + var first = true; + MiniYamlNode result = null; + foreach (var node in Nodes) + { + if (node.Key != key) + continue; + + if (!first) + throw new InvalidDataException($"Duplicate key '{node.Key}' in {node.Location}"); + + first = false; + result = node; + } + + return result; + } + public Dictionary ToDictionary() { return ToDictionary(MiniYamlIdentity); @@ -175,11 +203,6 @@ namespace OpenRA Nodes = ImmutableArray.CreateRange(nodes); } - public static ImmutableArray NodesOrEmpty(MiniYaml y, string s) - { - return y.Nodes.FirstOrDefault(n => n.Key == s)?.Value.Nodes ?? ImmutableArray.Empty; - } - static List FromLines(IEnumerable> lines, string filename, bool discardCommentsAndWhitespace, Dictionary stringPool) { stringPool ??= new Dictionary(); @@ -668,6 +691,11 @@ namespace OpenRA foreach (var line in Nodes.ToLines()) yield return "\t" + line; } + + public MiniYamlNodeBuilder NodeWithKeyOrDefault(string key) + { + return Nodes.SingleOrDefault(n => n.Key == key); + } } [Serializable] diff --git a/OpenRA.Game/Network/GameServer.cs b/OpenRA.Game/Network/GameServer.cs index 63d9c11aa3..7a7b766b18 100644 --- a/OpenRA.Game/Network/GameServer.cs +++ b/OpenRA.Game/Network/GameServer.cs @@ -140,7 +140,7 @@ namespace OpenRA.Network static object LoadClients(MiniYaml yaml) { var clients = new List(); - var clientsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Clients"); + var clientsNode = yaml.NodeWithKeyOrDefault("Clients"); if (clientsNode != null) { var regex = new Regex(@"Client@\d+"); @@ -159,7 +159,7 @@ namespace OpenRA.Network // Games advertised using the old API used a single Mods field if (Mod == null || Version == null) { - var modsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Mods"); + var modsNode = yaml.NodeWithKeyOrDefault("Mods"); if (modsNode != null) { var modVersion = modsNode.Value.Value.Split('@'); diff --git a/OpenRA.Game/Network/LocalizedMessage.cs b/OpenRA.Game/Network/LocalizedMessage.cs index 4deb7b7330..3a0feaa9ca 100644 --- a/OpenRA.Game/Network/LocalizedMessage.cs +++ b/OpenRA.Game/Network/LocalizedMessage.cs @@ -60,7 +60,7 @@ namespace OpenRA.Network static object LoadArguments(MiniYaml yaml) { var arguments = new Dictionary(); - var argumentsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Arguments"); + var argumentsNode = yaml.NodeWithKeyOrDefault("Arguments"); if (argumentsNode != null) { foreach (var argumentNode in argumentsNode.Value.Nodes) diff --git a/OpenRA.Game/Network/Session.cs b/OpenRA.Game/Network/Session.cs index 1c7b21b293..c916e700c8 100644 --- a/OpenRA.Game/Network/Session.cs +++ b/OpenRA.Game/Network/Session.cs @@ -227,7 +227,7 @@ namespace OpenRA.Network { var gs = FieldLoader.Load(data); - var optionsNode = data.Nodes.FirstOrDefault(n => n.Key == "Options"); + var optionsNode = data.NodeWithKeyOrDefault("Options"); if (optionsNode != null) foreach (var n in optionsNode.Value.Nodes) gs.LobbyOptions[n.Key] = FieldLoader.Load(n.Value); diff --git a/OpenRA.Game/PlayerDatabase.cs b/OpenRA.Game/PlayerDatabase.cs index 3a98f5aaa9..c02895d09a 100644 --- a/OpenRA.Game/PlayerDatabase.cs +++ b/OpenRA.Game/PlayerDatabase.cs @@ -9,7 +9,6 @@ */ #endregion -using System.Linq; using System.Threading.Tasks; using OpenRA.FileFormats; using OpenRA.Graphics; @@ -94,10 +93,10 @@ namespace OpenRA }); } - var labelNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Label"); - var icon24Node = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon24"); - var icon48Node = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon48"); - var icon72Node = yaml.Nodes.FirstOrDefault(n => n.Key == "Icon72"); + var labelNode = yaml.NodeWithKeyOrDefault("Label"); + var icon24Node = yaml.NodeWithKeyOrDefault("Icon24"); + var icon48Node = yaml.NodeWithKeyOrDefault("Icon48"); + var icon72Node = yaml.NodeWithKeyOrDefault("Icon72"); if (labelNode == null) return null; diff --git a/OpenRA.Game/PlayerProfile.cs b/OpenRA.Game/PlayerProfile.cs index d8c72c53bc..b9a74a8efd 100644 --- a/OpenRA.Game/PlayerProfile.cs +++ b/OpenRA.Game/PlayerProfile.cs @@ -10,7 +10,6 @@ #endregion using System.Collections.Generic; -using System.Linq; namespace OpenRA { @@ -31,7 +30,7 @@ namespace OpenRA { var badges = new List(); - var badgesNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Badges"); + var badgesNode = yaml.NodeWithKeyOrDefault("Badges"); if (badgesNode != null) { var playerDatabase = Game.ModData.Manifest.Get(); diff --git a/OpenRA.Game/Settings.cs b/OpenRA.Game/Settings.cs index e7e5847db5..11c7de0a3f 100644 --- a/OpenRA.Game/Settings.cs +++ b/OpenRA.Game/Settings.cs @@ -385,7 +385,7 @@ namespace OpenRA else { // Update or add the custom value - var fieldYaml = sectionYaml.Value.Nodes.FirstOrDefault(n => n.Key == fli.YamlName); + var fieldYaml = sectionYaml.Value.NodeWithKeyOrDefault(fli.YamlName); if (fieldYaml != null) fieldYaml.Value.Value = serialized; else diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index 57154dc125..b0923d3c63 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -11,7 +11,6 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -350,7 +349,7 @@ namespace OpenRA.Traits public interface IGameSaveTraitData { List IssueTraitData(Actor self); - void ResolveTraitData(Actor self, ImmutableArray data); + void ResolveTraitData(Actor self, MiniYaml data); } [RequireExplicitImplementation] diff --git a/OpenRA.Game/Widgets/WidgetLoader.cs b/OpenRA.Game/Widgets/WidgetLoader.cs index af402b5363..8cdbdb0777 100644 --- a/OpenRA.Game/Widgets/WidgetLoader.cs +++ b/OpenRA.Game/Widgets/WidgetLoader.cs @@ -66,7 +66,7 @@ namespace OpenRA foreach (var c in child.Value.Nodes) LoadWidget(args, widget, c); - var logicNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "Logic"); + var logicNode = node.Value.NodeWithKeyOrDefault("Logic"); var logic = logicNode?.Value.ToDictionary(); args.Add("logicArgs", logic); diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index d4b3d27793..6a69c15aee 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -423,7 +423,7 @@ namespace OpenRA if (tp.Actor == null) break; - tp.Trait.ResolveTraitData(tp.Actor, kv.Value.Nodes); + tp.Trait.ResolveTraitData(tp.Actor, kv.Value); } gameSaveTraitData.Clear(); diff --git a/OpenRA.Mods.Cnc/Graphics/ClassicTilesetSpecificSpriteSequence.cs b/OpenRA.Mods.Cnc/Graphics/ClassicTilesetSpecificSpriteSequence.cs index b0ed9dcc0d..783290cfe1 100644 --- a/OpenRA.Mods.Cnc/Graphics/ClassicTilesetSpecificSpriteSequence.cs +++ b/OpenRA.Mods.Cnc/Graphics/ClassicTilesetSpecificSpriteSequence.cs @@ -10,7 +10,6 @@ #endregion using System.Collections.Generic; -using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Graphics; @@ -39,10 +38,10 @@ namespace OpenRA.Mods.Cnc.Graphics protected override IEnumerable ParseFilenames(ModData modData, string tileset, int[] frames, MiniYaml data, MiniYaml defaults) { - var node = data.Nodes.FirstOrDefault(n => n.Key == TilesetFilenames.Key) ?? defaults.Nodes.FirstOrDefault(n => n.Key == TilesetFilenames.Key); + var node = data.NodeWithKeyOrDefault(TilesetFilenames.Key) ?? defaults.NodeWithKeyOrDefault(TilesetFilenames.Key); if (node != null) { - var tilesetNode = node.Value.Nodes.FirstOrDefault(n => n.Key == tileset); + var tilesetNode = node.Value.NodeWithKeyOrDefault(tileset); if (tilesetNode != null) { var loadFrames = CalculateFrameIndices(start, length, stride ?? length ?? 0, facings, frames, transpose, reverseFacings, shadowStart); @@ -55,10 +54,10 @@ namespace OpenRA.Mods.Cnc.Graphics protected override IEnumerable ParseCombineFilenames(ModData modData, string tileset, int[] frames, MiniYaml data) { - var node = data.Nodes.FirstOrDefault(n => n.Key == TilesetFilenames.Key); + var node = data.NodeWithKeyOrDefault(TilesetFilenames.Key); if (node != null) { - var tilesetNode = node.Value.Nodes.FirstOrDefault(n => n.Key == tileset); + var tilesetNode = node.Value.NodeWithKeyOrDefault(tileset); if (tilesetNode != null) { if (frames == null) diff --git a/OpenRA.Mods.Cnc/UtilityCommands/ImportGen1MapCommand.cs b/OpenRA.Mods.Cnc/UtilityCommands/ImportGen1MapCommand.cs index acbff3e9d0..1f2ae3deca 100644 --- a/OpenRA.Mods.Cnc/UtilityCommands/ImportGen1MapCommand.cs +++ b/OpenRA.Mods.Cnc/UtilityCommands/ImportGen1MapCommand.cs @@ -130,7 +130,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands protected MiniYamlNodeBuilder GetWorldNodeBuilderFromRules() { - var worldNode = Map.RuleDefinitions.Nodes.FirstOrDefault(n => n.Key == "World"); + var worldNode = Map.RuleDefinitions.NodeWithKeyOrDefault("World"); var worldNodeBuilder = worldNode != null ? new MiniYamlNodeBuilder(worldNode) : new MiniYamlNodeBuilder("World", new MiniYamlBuilder("", new List())); @@ -166,7 +166,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands var worldNodeBuilder = GetWorldNodeBuilderFromRules(); - var missionData = worldNodeBuilder.Value.Nodes.FirstOrDefault(n => n.Key == "MissionData"); + var missionData = worldNodeBuilder.Value.NodeWithKeyOrDefault("MissionData"); if (missionData == null) { missionData = new MiniYamlNodeBuilder("MissionData", new MiniYamlBuilder("", new List())); @@ -237,7 +237,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands { var worldNodeBuilder = GetWorldNodeBuilderFromRules(); - var missionData = worldNodeBuilder.Value.Nodes.FirstOrDefault(n => n.Key == "MissionData"); + var missionData = worldNodeBuilder.Value.NodeWithKeyOrDefault("MissionData"); if (missionData == null) { missionData = new MiniYamlNodeBuilder("MissionData", new MiniYamlBuilder("", new List())); diff --git a/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs b/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs index 0f70ece2ff..5fbc5cb0c8 100644 --- a/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs +++ b/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs @@ -47,7 +47,7 @@ namespace OpenRA.Mods.Common.Graphics IReadOnlyDictionary ISpriteSequenceLoader.ParseSequences(ModData modData, string tileset, SpriteCache cache, MiniYamlNode imageNode) { var sequences = new Dictionary(); - var node = imageNode.Value.Nodes.SingleOrDefault(n => n.Key == "Defaults"); + var node = imageNode.Value.NodeWithKeyOrDefault("Defaults"); var defaults = node?.Value ?? NoData; imageNode = imageNode.WithValue(imageNode.Value.WithNodes(imageNode.Value.Nodes.Remove(node))); @@ -262,7 +262,7 @@ namespace OpenRA.Mods.Common.Graphics protected static T LoadField(string key, T fallback, MiniYaml data, MiniYaml defaults = null) { - var node = data.Nodes.FirstOrDefault(n => n.Key == key) ?? defaults?.Nodes.FirstOrDefault(n => n.Key == key); + var node = data.NodeWithKeyOrDefault(key) ?? defaults?.NodeWithKeyOrDefault(key); if (node == null) return fallback; @@ -276,7 +276,7 @@ namespace OpenRA.Mods.Common.Graphics protected static T LoadField(SpriteSequenceField field, MiniYaml data, MiniYaml defaults, out MiniYamlNode.SourceLocation location) { - var node = data.Nodes.FirstOrDefault(n => n.Key == field.Key) ?? defaults?.Nodes.FirstOrDefault(n => n.Key == field.Key); + var node = data.NodeWithKeyOrDefault(field.Key) ?? defaults?.NodeWithKeyOrDefault(field.Key); if (node == null) { location = default; @@ -414,7 +414,7 @@ namespace OpenRA.Mods.Common.Graphics var offset = LoadField(Offset, data, defaults); var blendMode = LoadField(BlendMode, data, defaults); - var combineNode = data.Nodes.FirstOrDefault(n => n.Key == Combine.Key); + var combineNode = data.NodeWithKeyOrDefault(Combine.Key); if (combineNode != null) { for (var i = 0; i < combineNode.Value.Nodes.Length; i++) diff --git a/OpenRA.Mods.Common/Graphics/TilesetSpecificSpriteSequence.cs b/OpenRA.Mods.Common/Graphics/TilesetSpecificSpriteSequence.cs index d1f3b49b35..40046118d6 100644 --- a/OpenRA.Mods.Common/Graphics/TilesetSpecificSpriteSequence.cs +++ b/OpenRA.Mods.Common/Graphics/TilesetSpecificSpriteSequence.cs @@ -10,7 +10,6 @@ #endregion using System.Collections.Generic; -using System.Linq; using OpenRA.Graphics; namespace OpenRA.Mods.Common.Graphics @@ -37,10 +36,10 @@ namespace OpenRA.Mods.Common.Graphics protected override IEnumerable ParseFilenames(ModData modData, string tileset, int[] frames, MiniYaml data, MiniYaml defaults) { - var node = data.Nodes.FirstOrDefault(n => n.Key == TilesetFilenames.Key) ?? defaults.Nodes.FirstOrDefault(n => n.Key == TilesetFilenames.Key); + var node = data.NodeWithKeyOrDefault(TilesetFilenames.Key) ?? defaults.NodeWithKeyOrDefault(TilesetFilenames.Key); if (node != null) { - var tilesetNode = node.Value.Nodes.FirstOrDefault(n => n.Key == tileset); + var tilesetNode = node.Value.NodeWithKeyOrDefault(tileset); if (tilesetNode != null) { var loadFrames = CalculateFrameIndices(start, length, stride ?? length ?? 0, facings, frames, transpose, reverseFacings, shadowStart); @@ -53,10 +52,10 @@ namespace OpenRA.Mods.Common.Graphics protected override IEnumerable ParseCombineFilenames(ModData modData, string tileset, int[] frames, MiniYaml data) { - var node = data.Nodes.FirstOrDefault(n => n.Key == TilesetFilenames.Key); + var node = data.NodeWithKeyOrDefault(TilesetFilenames.Key); if (node != null) { - var tilesetNode = node.Value.Nodes.FirstOrDefault(n => n.Key == tileset); + var tilesetNode = node.Value.NodeWithKeyOrDefault(tileset); if (tilesetNode != null) { if (frames == null) diff --git a/OpenRA.Mods.Common/Installer/InstallerUtils.cs b/OpenRA.Mods.Common/Installer/InstallerUtils.cs index 726da2ac01..fd711ba9e4 100644 --- a/OpenRA.Mods.Common/Installer/InstallerUtils.cs +++ b/OpenRA.Mods.Common/Installer/InstallerUtils.cs @@ -11,7 +11,6 @@ using System; using System.IO; -using System.Linq; using FS = OpenRA.FileSystem.FileSystem; namespace OpenRA.Mods.Common.Installer @@ -33,8 +32,8 @@ namespace OpenRA.Mods.Common.Installer using (var fileStream = File.OpenRead(filePath)) { - var offsetNode = kv.Value.Nodes.FirstOrDefault(n => n.Key == "Offset"); - var lengthNode = kv.Value.Nodes.FirstOrDefault(n => n.Key == "Length"); + var offsetNode = kv.Value.NodeWithKeyOrDefault("Offset"); + var lengthNode = kv.Value.NodeWithKeyOrDefault("Length"); if (offsetNode != null || lengthNode != null) { var offset = 0L; diff --git a/OpenRA.Mods.Common/Installer/SourceActions/ExtractIscabSourceAction.cs b/OpenRA.Mods.Common/Installer/SourceActions/ExtractIscabSourceAction.cs index 08dd1acc5d..1a497bfae3 100644 --- a/OpenRA.Mods.Common/Installer/SourceActions/ExtractIscabSourceAction.cs +++ b/OpenRA.Mods.Common/Installer/SourceActions/ExtractIscabSourceAction.cs @@ -12,7 +12,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using OpenRA.Mods.Common.FileFormats; using OpenRA.Mods.Common.Widgets.Logic; using FS = OpenRA.FileSystem.FileSystem; @@ -26,11 +25,11 @@ namespace OpenRA.Mods.Common.Installer // Yaml path may be specified relative to a named directory (e.g. ^SupportDir) or the detected source path var sourcePath = actionYaml.Value.StartsWith('^') ? Platform.ResolvePath(actionYaml.Value) : FS.ResolveCaseInsensitivePath(Path.Combine(path, actionYaml.Value)); - var volumeNode = actionYaml.Nodes.FirstOrDefault(n => n.Key == "Volumes"); + var volumeNode = actionYaml.NodeWithKeyOrDefault("Volumes"); if (volumeNode == null) throw new InvalidDataException("extract-iscab entry doesn't define a Volumes node"); - var extractNode = actionYaml.Nodes.FirstOrDefault(n => n.Key == "Extract"); + var extractNode = actionYaml.NodeWithKeyOrDefault("Extract"); if (extractNode == null) throw new InvalidDataException("extract-iscab entry doesn't define an Extract node"); diff --git a/OpenRA.Mods.Common/Installer/SourceActions/ExtractRawSourceAction.cs b/OpenRA.Mods.Common/Installer/SourceActions/ExtractRawSourceAction.cs index 70c0f4e9ba..4a237c28d6 100644 --- a/OpenRA.Mods.Common/Installer/SourceActions/ExtractRawSourceAction.cs +++ b/OpenRA.Mods.Common/Installer/SourceActions/ExtractRawSourceAction.cs @@ -12,7 +12,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using OpenRA.Mods.Common.Widgets.Logic; using FS = OpenRA.FileSystem.FileSystem; @@ -37,14 +36,14 @@ namespace OpenRA.Mods.Common.Installer continue; } - var offsetNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "Offset"); + var offsetNode = node.Value.NodeWithKeyOrDefault("Offset"); if (offsetNode == null) { Log.Write("install", "Skipping entry with missing Offset definition " + targetPath); continue; } - var lengthNode = node.Value.Nodes.FirstOrDefault(n => n.Key == "Length"); + var lengthNode = node.Value.NodeWithKeyOrDefault("Length"); if (lengthNode == null) { Log.Write("install", "Skipping entry with missing Length definition " + targetPath); diff --git a/OpenRA.Mods.Common/Installer/SourceResolvers/GogSourceResolver.cs b/OpenRA.Mods.Common/Installer/SourceResolvers/GogSourceResolver.cs index 0a7c8323b7..29d7c2b900 100644 --- a/OpenRA.Mods.Common/Installer/SourceResolvers/GogSourceResolver.cs +++ b/OpenRA.Mods.Common/Installer/SourceResolvers/GogSourceResolver.cs @@ -18,7 +18,7 @@ namespace OpenRA.Mods.Common.Installer { public string FindSourcePath(ModContent.ModSource modSource) { - modSource.Type.ToDictionary().TryGetValue("AppId", out var appId); + var appId = modSource.Type.NodeWithKeyOrDefault("AppId"); if (appId == null) return null; @@ -35,7 +35,7 @@ namespace OpenRA.Mods.Common.Installer foreach (var prefix in prefixes) { - if (Registry.GetValue($"{prefix}GOG.com\\Games\\{appId.Value}", "path", null) is not string installDir) + if (Registry.GetValue($"{prefix}GOG.com\\Games\\{appId.Value.Value}", "path", null) is not string installDir) continue; if (InstallerUtils.IsValidSourcePath(installDir, modSource)) diff --git a/OpenRA.Mods.Common/Installer/SourceResolvers/SteamSourceResolver.cs b/OpenRA.Mods.Common/Installer/SourceResolvers/SteamSourceResolver.cs index 712bdd1f8d..a2d1e85968 100644 --- a/OpenRA.Mods.Common/Installer/SourceResolvers/SteamSourceResolver.cs +++ b/OpenRA.Mods.Common/Installer/SourceResolvers/SteamSourceResolver.cs @@ -23,14 +23,14 @@ namespace OpenRA.Mods.Common.Installer { public string FindSourcePath(ModContent.ModSource modSource) { - modSource.Type.ToDictionary().TryGetValue("AppId", out var appId); + var appId = modSource.Type.NodeWithKeyOrDefault("AppId"); if (appId == null) return null; foreach (var steamDirectory in SteamDirectory()) { - var manifestPath = Path.Combine(steamDirectory, "steamapps", $"appmanifest_{appId.Value}.acf"); + var manifestPath = Path.Combine(steamDirectory, "steamapps", $"appmanifest_{appId.Value.Value}.acf"); if (!File.Exists(manifestPath)) continue; diff --git a/OpenRA.Mods.Common/Lint/CheckCursors.cs b/OpenRA.Mods.Common/Lint/CheckCursors.cs index 43d3ebd39a..76f58b3f1a 100644 --- a/OpenRA.Mods.Common/Lint/CheckCursors.cs +++ b/OpenRA.Mods.Common/Lint/CheckCursors.cs @@ -33,11 +33,11 @@ namespace OpenRA.Mods.Common.Lint { var fileSystem = modData.DefaultFileSystem; var sequenceYaml = MiniYaml.Merge(modData.Manifest.Cursors.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s))); - var nodesDict = new MiniYaml(null, sequenceYaml).ToDictionary(); + var cursorsYaml = new MiniYaml(null, sequenceYaml).NodeWithKey("Cursors").Value; // Avoid using CursorProvider as it attempts to load palettes from the file system. var cursors = new List(); - foreach (var s in nodesDict["Cursors"].Nodes) + foreach (var s in cursorsYaml.Nodes) foreach (var sequence in s.Value.Nodes) cursors.Add(sequence.Key); diff --git a/OpenRA.Mods.Common/ModContent.cs b/OpenRA.Mods.Common/ModContent.cs index f5358b6397..81fcab8fde 100644 --- a/OpenRA.Mods.Common/ModContent.cs +++ b/OpenRA.Mods.Common/ModContent.cs @@ -66,15 +66,15 @@ namespace OpenRA ObjectCreator = objectCreator; Title = yaml.Value; - var type = yaml.Nodes.FirstOrDefault(n => n.Key == "Type"); + var type = yaml.NodeWithKeyOrDefault("Type"); if (type != null) Type = type.Value; - var idFiles = yaml.Nodes.FirstOrDefault(n => n.Key == "IDFiles"); + var idFiles = yaml.NodeWithKeyOrDefault("IDFiles"); if (idFiles != null) IDFiles = idFiles.Value; - var installNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Install"); + var installNode = yaml.NodeWithKeyOrDefault("Install"); if (installNode != null) Install = installNode.Value.Nodes; @@ -111,7 +111,7 @@ namespace OpenRA static object LoadPackages(MiniYaml yaml) { var packages = new Dictionary(); - var packageNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Packages"); + var packageNode = yaml.NodeWithKeyOrDefault("Packages"); if (packageNode != null) foreach (var node in packageNode.Value.Nodes) packages.Add(node.Key, new ModPackage(node.Value)); @@ -124,7 +124,7 @@ namespace OpenRA static object LoadDownloads(MiniYaml yaml) { - var downloadNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Downloads"); + var downloadNode = yaml.NodeWithKeyOrDefault("Downloads"); return downloadNode != null ? downloadNode.Value.Nodes.Select(n => n.Key).ToArray() : Array.Empty(); } @@ -133,7 +133,7 @@ namespace OpenRA static object LoadSources(MiniYaml yaml) { - var sourceNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Sources"); + var sourceNode = yaml.NodeWithKeyOrDefault("Sources"); return sourceNode != null ? sourceNode.Value.Nodes.Select(n => n.Key).ToArray() : Array.Empty(); } } diff --git a/OpenRA.Mods.Common/Terrain/TerrainInfo.cs b/OpenRA.Mods.Common/Terrain/TerrainInfo.cs index 7fcb834482..f0a06344f0 100644 --- a/OpenRA.Mods.Common/Terrain/TerrainInfo.cs +++ b/OpenRA.Mods.Common/Terrain/TerrainInfo.cs @@ -37,7 +37,7 @@ namespace OpenRA.Mods.Common.Terrain { FieldLoader.Load(this, my); - var nodes = my.ToDictionary()["Tiles"].Nodes; + var nodes = my.NodeWithKey("Tiles").Value.Nodes; if (!PickAny) { diff --git a/OpenRA.Mods.Common/Traits/AppearsOnMapPreview.cs b/OpenRA.Mods.Common/Traits/AppearsOnMapPreview.cs index 79e0149200..902fa40256 100644 --- a/OpenRA.Mods.Common/Traits/AppearsOnMapPreview.cs +++ b/OpenRA.Mods.Common/Traits/AppearsOnMapPreview.cs @@ -41,7 +41,7 @@ namespace OpenRA.Mods.Common.Traits else { var owner = map.PlayerDefinitions.Single(p => s.Get().InternalName == p.Value.Nodes.Last(k => k.Key == "Name").Value.Value); - var colorValue = owner.Value.Nodes.FirstOrDefault(n => n.Key == "Color"); + var colorValue = owner.Value.NodeWithKeyOrDefault("Color"); var ownerColor = colorValue?.Value.Value ?? "FFFFFF"; Color.TryParse(ownerColor, out color); } diff --git a/OpenRA.Mods.Common/Traits/BotModules/BaseBuilderBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/BaseBuilderBotModule.cs index 9edb861aca..67ba202ec0 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/BaseBuilderBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/BaseBuilderBotModule.cs @@ -10,7 +10,6 @@ #endregion using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using OpenRA.Traits; @@ -326,16 +325,16 @@ namespace OpenRA.Mods.Common.Traits }; } - void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray data) + void IGameSaveTraitData.ResolveTraitData(Actor self, MiniYaml data) { if (self.World.IsReplay) return; - var initialBaseCenterNode = data.FirstOrDefault(n => n.Key == "InitialBaseCenter"); + var initialBaseCenterNode = data.NodeWithKeyOrDefault("InitialBaseCenter"); if (initialBaseCenterNode != null) initialBaseCenter = FieldLoader.GetValue("InitialBaseCenter", initialBaseCenterNode.Value.Value); - var defenseCenterNode = data.FirstOrDefault(n => n.Key == "DefenseCenter"); + var defenseCenterNode = data.NodeWithKeyOrDefault("DefenseCenter"); if (defenseCenterNode != null) DefenseCenter = FieldLoader.GetValue("DefenseCenter", defenseCenterNode.Value.Value); } diff --git a/OpenRA.Mods.Common/Traits/BotModules/McvManagerBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/McvManagerBotModule.cs index 3aa3c0514a..ebb5b8f0ae 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/McvManagerBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/McvManagerBotModule.cs @@ -10,7 +10,6 @@ #endregion using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using OpenRA.Traits; @@ -213,12 +212,12 @@ namespace OpenRA.Mods.Common.Traits }; } - void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray data) + void IGameSaveTraitData.ResolveTraitData(Actor self, MiniYaml data) { if (self.World.IsReplay) return; - var initialBaseCenterNode = data.FirstOrDefault(n => n.Key == "InitialBaseCenter"); + var initialBaseCenterNode = data.NodeWithKeyOrDefault("InitialBaseCenter"); if (initialBaseCenterNode != null) initialBaseCenter = FieldLoader.GetValue("InitialBaseCenter", initialBaseCenterNode.Value.Value); } diff --git a/OpenRA.Mods.Common/Traits/BotModules/SquadManagerBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/SquadManagerBotModule.cs index 6793bedda0..ca5588f8b3 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/SquadManagerBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/SquadManagerBotModule.cs @@ -11,7 +11,6 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using OpenRA.Mods.Common.Traits.BotModules.Squads; using OpenRA.Primitives; @@ -526,52 +525,46 @@ namespace OpenRA.Mods.Common.Traits }; } - void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray data) + void IGameSaveTraitData.ResolveTraitData(Actor self, MiniYaml data) { if (self.World.IsReplay) return; - var initialBaseCenterNode = data.FirstOrDefault(n => n.Key == "InitialBaseCenter"); - if (initialBaseCenterNode != null) - initialBaseCenter = FieldLoader.GetValue("InitialBaseCenter", initialBaseCenterNode.Value.Value); + var nodes = data.ToDictionary(); - var unitsHangingAroundTheBaseNode = data.FirstOrDefault(n => n.Key == "UnitsHangingAroundTheBase"); - if (unitsHangingAroundTheBaseNode != null) + if (nodes.TryGetValue("InitialBaseCenter", out var initialBaseCenterNode)) + initialBaseCenter = FieldLoader.GetValue("InitialBaseCenter", initialBaseCenterNode.Value); + + if (nodes.TryGetValue("UnitsHangingAroundTheBase", out var unitsHangingAroundTheBaseNode)) { unitsHangingAroundTheBase.Clear(); - unitsHangingAroundTheBase.AddRange(FieldLoader.GetValue("UnitsHangingAroundTheBase", unitsHangingAroundTheBaseNode.Value.Value) + unitsHangingAroundTheBase.AddRange(FieldLoader.GetValue("UnitsHangingAroundTheBase", unitsHangingAroundTheBaseNode.Value) .Select(a => self.World.GetActorById(a)).Where(a => a != null)); } - var activeUnitsNode = data.FirstOrDefault(n => n.Key == "ActiveUnits"); - if (activeUnitsNode != null) + if (nodes.TryGetValue("ActiveUnits", out var activeUnitsNode)) { activeUnits.Clear(); - activeUnits.UnionWith(FieldLoader.GetValue("ActiveUnits", activeUnitsNode.Value.Value) + activeUnits.UnionWith(FieldLoader.GetValue("ActiveUnits", activeUnitsNode.Value) .Select(a => self.World.GetActorById(a)).Where(a => a != null)); } - var rushTicksNode = data.FirstOrDefault(n => n.Key == "RushTicks"); - if (rushTicksNode != null) - rushTicks = FieldLoader.GetValue("RushTicks", rushTicksNode.Value.Value); + if (nodes.TryGetValue("RushTicks", out var rushTicksNode)) + rushTicks = FieldLoader.GetValue("RushTicks", rushTicksNode.Value); - var assignRolesTicksNode = data.FirstOrDefault(n => n.Key == "AssignRolesTicks"); - if (assignRolesTicksNode != null) - assignRolesTicks = FieldLoader.GetValue("AssignRolesTicks", assignRolesTicksNode.Value.Value); + if (nodes.TryGetValue("AssignRolesTicks", out var assignRolesTicksNode)) + assignRolesTicks = FieldLoader.GetValue("AssignRolesTicks", assignRolesTicksNode.Value); - var attackForceTicksNode = data.FirstOrDefault(n => n.Key == "AttackForceTicks"); - if (attackForceTicksNode != null) - attackForceTicks = FieldLoader.GetValue("AttackForceTicks", attackForceTicksNode.Value.Value); + if (nodes.TryGetValue("AttackForceTicks", out var attackForceTicksNode)) + attackForceTicks = FieldLoader.GetValue("AttackForceTicks", attackForceTicksNode.Value); - var minAttackForceDelayTicksNode = data.FirstOrDefault(n => n.Key == "MinAttackForceDelayTicks"); - if (minAttackForceDelayTicksNode != null) - minAttackForceDelayTicks = FieldLoader.GetValue("MinAttackForceDelayTicks", minAttackForceDelayTicksNode.Value.Value); + if (nodes.TryGetValue("MinAttackForceDelayTicks", out var minAttackForceDelayTicksNode)) + minAttackForceDelayTicks = FieldLoader.GetValue("MinAttackForceDelayTicks", minAttackForceDelayTicksNode.Value); - var squadsNode = data.FirstOrDefault(n => n.Key == "Squads"); - if (squadsNode != null) + if (nodes.TryGetValue("Squads", out var squadsNode)) { Squads.Clear(); - foreach (var n in squadsNode.Value.Nodes) + foreach (var n in squadsNode.Nodes) Squads.Add(Squad.Deserialize(bot, this, n.Value)); } } diff --git a/OpenRA.Mods.Common/Traits/BotModules/Squads/Squad.cs b/OpenRA.Mods.Common/Traits/BotModules/Squads/Squad.cs index 1b5f632be6..7afe09e31b 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/Squads/Squad.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/Squads/Squad.cs @@ -155,12 +155,12 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads var type = SquadType.Rush; var target = ((Actor)null, WVec.Zero); - var typeNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Type"); + var typeNode = yaml.NodeWithKeyOrDefault("Type"); if (typeNode != null) type = FieldLoader.GetValue("Type", typeNode.Value.Value); - var actorToTargetNode = yaml.Nodes.FirstOrDefault(n => n.Key == "ActorToTarget"); - var targetOffsetNode = yaml.Nodes.FirstOrDefault(n => n.Key == "TargetOffset"); + var actorToTargetNode = yaml.NodeWithKeyOrDefault("ActorToTarget"); + var targetOffsetNode = yaml.NodeWithKeyOrDefault("TargetOffset"); if (actorToTargetNode != null && targetOffsetNode != null) { var actorToTarget = squadManager.World.GetActorById(FieldLoader.GetValue("ActorToTarget", actorToTargetNode.Value.Value)); @@ -170,7 +170,7 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads var squad = new Squad(bot, squadManager, type, target); - var unitsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Units"); + var unitsNode = yaml.NodeWithKeyOrDefault("Units"); if (unitsNode != null) squad.Units.UnionWith(FieldLoader.GetValue("Units", unitsNode.Value.Value) .Select(a => squadManager.World.GetActorById(a))); diff --git a/OpenRA.Mods.Common/Traits/BotModules/SupportPowerBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/SupportPowerBotModule.cs index fe94e6f73e..079fc3c7fd 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/SupportPowerBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/SupportPowerBotModule.cs @@ -10,7 +10,6 @@ #endregion using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using OpenRA.Traits; @@ -27,7 +26,7 @@ namespace OpenRA.Mods.Common.Traits static object LoadDecisions(MiniYaml yaml) { var ret = new List(); - var decisions = yaml.Nodes.FirstOrDefault(n => n.Key == "Decisions"); + var decisions = yaml.NodeWithKeyOrDefault("Decisions"); if (decisions != null) foreach (var d in decisions.Value.Nodes) ret.Add(new SupportPowerDecision(d.Value)); @@ -224,12 +223,12 @@ namespace OpenRA.Mods.Common.Traits }; } - void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray data) + void IGameSaveTraitData.ResolveTraitData(Actor self, MiniYaml data) { if (self.World.IsReplay) return; - var waitingPowersNode = data.FirstOrDefault(n => n.Key == "WaitingPowers"); + var waitingPowersNode = data.NodeWithKeyOrDefault("WaitingPowers"); if (waitingPowersNode != null) { foreach (var n in waitingPowersNode.Value.Nodes) diff --git a/OpenRA.Mods.Common/Traits/BotModules/UnitBuilderBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/UnitBuilderBotModule.cs index 0d576c6e3f..7964b320f4 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/UnitBuilderBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/UnitBuilderBotModule.cs @@ -10,7 +10,6 @@ #endregion using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using OpenRA.Traits; @@ -234,19 +233,19 @@ namespace OpenRA.Mods.Common.Traits }; } - void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray data) + void IGameSaveTraitData.ResolveTraitData(Actor self, MiniYaml data) { if (self.World.IsReplay) return; - var queuedBuildRequestsNode = data.FirstOrDefault(n => n.Key == "QueuedBuildRequests"); + var queuedBuildRequestsNode = data.NodeWithKeyOrDefault("QueuedBuildRequests"); if (queuedBuildRequestsNode != null) { queuedBuildRequests.Clear(); queuedBuildRequests.AddRange(FieldLoader.GetValue("QueuedBuildRequests", queuedBuildRequestsNode.Value.Value)); } - var idleUnitCountNode = data.FirstOrDefault(n => n.Key == "IdleUnitCount"); + var idleUnitCountNode = data.NodeWithKeyOrDefault("IdleUnitCount"); if (idleUnitCountNode != null) idleUnitCount = FieldLoader.GetValue("IdleUnitCount", idleUnitCountNode.Value.Value); } diff --git a/OpenRA.Mods.Common/Traits/Buildings/Building.cs b/OpenRA.Mods.Common/Traits/Buildings/Building.cs index ab14327a67..bf037e72d6 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/Building.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/Building.cs @@ -64,10 +64,10 @@ namespace OpenRA.Mods.Common.Traits protected static object LoadFootprint(MiniYaml yaml) { - var footprintYaml = yaml.Nodes.FirstOrDefault(n => n.Key == "Footprint"); + var footprintYaml = yaml.NodeWithKeyOrDefault("Footprint"); var footprintChars = footprintYaml?.Value.Value.Where(x => !char.IsWhiteSpace(x)).ToArray() ?? new[] { 'x' }; - var dimensionsYaml = yaml.Nodes.FirstOrDefault(n => n.Key == "Dimensions"); + var dimensionsYaml = yaml.NodeWithKeyOrDefault("Dimensions"); var dim = dimensionsYaml != null ? FieldLoader.GetValue("Dimensions", dimensionsYaml.Value.Value) : new CVec(1, 1); if (footprintChars.Length != dim.X * dim.Y) diff --git a/OpenRA.Mods.Common/Traits/HitShape.cs b/OpenRA.Mods.Common/Traits/HitShape.cs index 467c20ea99..00c5039fcf 100644 --- a/OpenRA.Mods.Common/Traits/HitShape.cs +++ b/OpenRA.Mods.Common/Traits/HitShape.cs @@ -43,7 +43,7 @@ namespace OpenRA.Mods.Common.Traits { IHitShape ret; - var shapeNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Type"); + var shapeNode = yaml.NodeWithKeyOrDefault("Type"); var shape = shapeNode != null ? shapeNode.Value.Value : string.Empty; if (!string.IsNullOrEmpty(shape)) diff --git a/OpenRA.Mods.Common/Traits/Player/GameSaveViewportManager.cs b/OpenRA.Mods.Common/Traits/Player/GameSaveViewportManager.cs index 7f01f56347..6210d68578 100644 --- a/OpenRA.Mods.Common/Traits/Player/GameSaveViewportManager.cs +++ b/OpenRA.Mods.Common/Traits/Player/GameSaveViewportManager.cs @@ -10,7 +10,6 @@ #endregion using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using OpenRA.Graphics; using OpenRA.Traits; @@ -50,13 +49,13 @@ namespace OpenRA.Mods.Common.Traits return nodes; } - void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray data) + void IGameSaveTraitData.ResolveTraitData(Actor self, MiniYaml data) { - var viewportNode = data.FirstOrDefault(n => n.Key == "Viewport"); + var viewportNode = data.NodeWithKeyOrDefault("Viewport"); if (viewportNode != null) worldRenderer.Viewport.Center(FieldLoader.GetValue("Viewport", viewportNode.Value.Value)); - var renderPlayerNode = data.FirstOrDefault(n => n.Key == "RenderPlayer"); + var renderPlayerNode = data.NodeWithKeyOrDefault("RenderPlayer"); if (renderPlayerNode != null) { var renderPlayerActorID = FieldLoader.GetValue("RenderPlayer", renderPlayerNode.Value.Value); diff --git a/OpenRA.Mods.Common/Traits/World/ControlGroups.cs b/OpenRA.Mods.Common/Traits/World/ControlGroups.cs index 3579b7d1a3..9022d37d30 100644 --- a/OpenRA.Mods.Common/Traits/World/ControlGroups.cs +++ b/OpenRA.Mods.Common/Traits/World/ControlGroups.cs @@ -133,9 +133,9 @@ namespace OpenRA.Mods.Common.Traits }; } - void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray data) + void IGameSaveTraitData.ResolveTraitData(Actor self, MiniYaml data) { - var groupsNode = data.FirstOrDefault(n => n.Key == "Groups"); + var groupsNode = data.NodeWithKeyOrDefault("Groups"); if (groupsNode != null) { foreach (var n in groupsNode.Value.Nodes) diff --git a/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs b/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs index d9f16b6ac0..aa060ae850 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs @@ -28,7 +28,7 @@ namespace OpenRA.Mods.Common.Traits protected static object LoadResourceTypes(MiniYaml yaml) { var ret = new Dictionary(); - var resources = yaml.Nodes.FirstOrDefault(n => n.Key == "ResourceTypes"); + var resources = yaml.NodeWithKeyOrDefault("ResourceTypes"); if (resources != null) foreach (var r in resources.Value.Nodes) ret[r.Key] = new ResourceLayerInfo.ResourceTypeInfo(r.Value); diff --git a/OpenRA.Mods.Common/Traits/World/Locomotor.cs b/OpenRA.Mods.Common/Traits/World/Locomotor.cs index 2d14b273a5..53c99f2849 100644 --- a/OpenRA.Mods.Common/Traits/World/Locomotor.cs +++ b/OpenRA.Mods.Common/Traits/World/Locomotor.cs @@ -84,16 +84,16 @@ namespace OpenRA.Mods.Common.Traits protected static object LoadSpeeds(MiniYaml y) { - var speeds = y.ToDictionary()["TerrainSpeeds"].Nodes; + var speeds = y.NodeWithKey("TerrainSpeeds").Value.Nodes; var ret = new Dictionary(speeds.Length); foreach (var t in speeds) { var speed = FieldLoader.GetValue("speed", t.Value.Value); if (speed > 0) { - var nodesDict = t.Value.ToDictionary(); - var cost = nodesDict.TryGetValue("PathingCost", out var entry) - ? FieldLoader.GetValue("cost", entry.Value) + var pathingCost = t.Value.NodeWithKeyOrDefault("PathingCost"); + var cost = pathingCost != null + ? FieldLoader.GetValue("cost", pathingCost.Value.Value) : 10000 / speed; ret.Add(t.Key, new TerrainInfo(speed, (short)cost)); } diff --git a/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs b/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs index 498eedee5f..5266eb40e3 100644 --- a/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs @@ -67,7 +67,7 @@ namespace OpenRA.Mods.Common.Traits protected static object LoadResourceTypes(MiniYaml yaml) { var ret = new Dictionary(); - var resources = yaml.Nodes.FirstOrDefault(n => n.Key == "ResourceTypes"); + var resources = yaml.NodeWithKeyOrDefault("ResourceTypes"); if (resources != null) foreach (var r in resources.Value.Nodes) ret[r.Key] = new ResourceTypeInfo(r.Value); diff --git a/OpenRA.Mods.Common/Traits/World/ResourceRenderer.cs b/OpenRA.Mods.Common/Traits/World/ResourceRenderer.cs index e05548a798..0a79f976b3 100644 --- a/OpenRA.Mods.Common/Traits/World/ResourceRenderer.cs +++ b/OpenRA.Mods.Common/Traits/World/ResourceRenderer.cs @@ -54,7 +54,7 @@ namespace OpenRA.Mods.Common.Traits protected static object LoadResourceTypes(MiniYaml yaml) { var ret = new Dictionary(); - var resources = yaml.Nodes.FirstOrDefault(n => n.Key == "ResourceTypes"); + var resources = yaml.NodeWithKeyOrDefault("ResourceTypes"); if (resources != null) foreach (var r in resources.Value.Nodes) ret[r.Key] = new ResourceTypeInfo(r.Value); diff --git a/OpenRA.Mods.Common/Traits/World/Selection.cs b/OpenRA.Mods.Common/Traits/World/Selection.cs index 64e081cf1e..22c51cf905 100644 --- a/OpenRA.Mods.Common/Traits/World/Selection.cs +++ b/OpenRA.Mods.Common/Traits/World/Selection.cs @@ -10,7 +10,6 @@ #endregion using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using OpenRA.Traits; @@ -186,9 +185,9 @@ namespace OpenRA.Mods.Common.Traits }; } - void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray data) + void IGameSaveTraitData.ResolveTraitData(Actor self, MiniYaml data) { - var selectionNode = data.FirstOrDefault(n => n.Key == "Selection"); + var selectionNode = data.NodeWithKeyOrDefault("Selection"); if (selectionNode != null) { var selected = FieldLoader.GetValue("Selection", selectionNode.Value.Value) diff --git a/OpenRA.Mods.Common/Traits/World/SmudgeLayer.cs b/OpenRA.Mods.Common/Traits/World/SmudgeLayer.cs index 9b830c6fe2..6ea51afb84 100644 --- a/OpenRA.Mods.Common/Traits/World/SmudgeLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/SmudgeLayer.cs @@ -56,11 +56,11 @@ namespace OpenRA.Mods.Common.Traits public static object LoadInitialSmudges(MiniYaml yaml) { - var nd = yaml.ToDictionary(); var smudges = new Dictionary(); - if (nd.TryGetValue("InitialSmudges", out var smudgeYaml)) + var smudgeYaml = yaml.NodeWithKeyOrDefault("InitialSmudges"); + if (smudgeYaml != null) { - foreach (var node in smudgeYaml.Nodes) + foreach (var node in smudgeYaml.Value.Nodes) { try { diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/ExplicitSequenceFilenames.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/ExplicitSequenceFilenames.cs index 6a42628fa8..69ea3a2b64 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/ExplicitSequenceFilenames.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/ExplicitSequenceFilenames.cs @@ -230,7 +230,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules if (!string.IsNullOrEmpty(sequenceNode.Value.Value)) explicitlyNamedSequences.Add(sequenceNode.Key); - var resolvedSequenceNode = resolvedImageNode.Value.Nodes.SingleOrDefault(n => n.Key == sequenceNode.Key); + var resolvedSequenceNode = resolvedImageNode.Value.NodeWithKeyOrDefault(sequenceNode.Key); if (resolvedSequenceNode == null) continue; diff --git a/OpenRA.Mods.Common/UpdateRules/UpdateUtils.cs b/OpenRA.Mods.Common/UpdateRules/UpdateUtils.cs index 76431011dc..86fc35db20 100644 --- a/OpenRA.Mods.Common/UpdateRules/UpdateUtils.cs +++ b/OpenRA.Mods.Common/UpdateRules/UpdateUtils.cs @@ -99,7 +99,7 @@ namespace OpenRA.Mods.Common.UpdateRules manualSteps.AddRange(rule.BeforeUpdate(modData)); - var mapActorsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Actors"); + var mapActorsNode = yaml.NodeWithKeyOrDefault("Actors"); if (mapActorsNode != null) { var mapActors = new YamlFileSet() @@ -111,7 +111,7 @@ namespace OpenRA.Mods.Common.UpdateRules files.AddRange(mapActors); } - var mapRulesNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Rules"); + var mapRulesNode = yaml.NodeWithKeyOrDefault("Rules"); if (mapRulesNode != null) { if (rule is IBeforeUpdateActors before) @@ -125,7 +125,7 @@ namespace OpenRA.Mods.Common.UpdateRules files.AddRange(mapRules); } - var mapWeaponsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Weapons"); + var mapWeaponsNode = yaml.NodeWithKeyOrDefault("Weapons"); if (mapWeaponsNode != null) { if (rule is IBeforeUpdateWeapons before) @@ -139,7 +139,7 @@ namespace OpenRA.Mods.Common.UpdateRules files.AddRange(mapWeapons); } - var mapSequencesNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Sequences"); + var mapSequencesNode = yaml.NodeWithKeyOrDefault("Sequences"); if (mapSequencesNode != null) { if (rule is IBeforeUpdateSequences before) @@ -216,19 +216,19 @@ namespace OpenRA.Mods.Common.UpdateRules continue; var yaml = new MiniYamlBuilder(new MiniYaml(null, MiniYaml.FromStream(mapStream, package.Name, false))); - var mapRulesNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Rules"); + var mapRulesNode = yaml.NodeWithKeyOrDefault("Rules"); if (mapRulesNode != null) foreach (var f in LoadExternalMapYaml(modData, mapRulesNode.Value, externalFilenames)) if (!modRules.Any(m => m.Item1 == f.Item1 && m.Item2 == f.Item2)) modRules.Add(f); - var mapWeaponsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Weapons"); + var mapWeaponsNode = yaml.NodeWithKeyOrDefault("Weapons"); if (mapWeaponsNode != null) foreach (var f in LoadExternalMapYaml(modData, mapWeaponsNode.Value, externalFilenames)) if (!modWeapons.Any(m => m.Item1 == f.Item1 && m.Item2 == f.Item2)) modWeapons.Add(f); - var mapSequencesNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Sequences"); + var mapSequencesNode = yaml.NodeWithKeyOrDefault("Sequences"); if (mapSequencesNode != null) foreach (var f in LoadExternalMapYaml(modData, mapSequencesNode.Value, externalFilenames)) if (!modSequences.Any(m => m.Item1 == f.Item1 && m.Item2 == f.Item2)) @@ -285,7 +285,7 @@ namespace OpenRA.Mods.Common.UpdateRules foreach (var manualStep in transform(modData, current)) yield return manualStep; - var childrenNode = current.Value.Nodes.FirstOrDefault(n => n.Key == "Children"); + var childrenNode = current.Value.NodeWithKeyOrDefault("Children"); if (childrenNode != null) foreach (var node in childrenNode.Value.Nodes) if (node.Key != null) diff --git a/OpenRA.Mods.Common/Widgets/ControlGroupsWidget.cs b/OpenRA.Mods.Common/Widgets/ControlGroupsWidget.cs index 17e7f82f98..c548a230e5 100644 --- a/OpenRA.Mods.Common/Widgets/ControlGroupsWidget.cs +++ b/OpenRA.Mods.Common/Widgets/ControlGroupsWidget.cs @@ -11,7 +11,6 @@ using System; using System.Collections.Generic; -using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Lint; using OpenRA.Traits; @@ -47,27 +46,27 @@ namespace OpenRA.Mods.Common.Widgets yield break; var selectPrefix = ""; - var selectPrefixNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "SelectGroupKeyPrefix"); + var selectPrefixNode = widgetNode.Value.NodeWithKeyOrDefault("SelectGroupKeyPrefix"); if (selectPrefixNode != null) selectPrefix = selectPrefixNode.Value.Value; var createPrefix = ""; - var createPrefixNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "CreateGroupKeyPrefix"); + var createPrefixNode = widgetNode.Value.NodeWithKeyOrDefault("CreateGroupKeyPrefix"); if (createPrefixNode != null) createPrefix = createPrefixNode.Value.Value; var addToPrefix = ""; - var addToPrefixNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "AddToGroupKeyPrefix"); + var addToPrefixNode = widgetNode.Value.NodeWithKeyOrDefault("AddToGroupKeyPrefix"); if (addToPrefixNode != null) addToPrefix = addToPrefixNode.Value.Value; var combineWithPrefix = ""; - var combineWithPrefixNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "CombineWithGroupKeyPrefix"); + var combineWithPrefixNode = widgetNode.Value.NodeWithKeyOrDefault("CombineWithGroupKeyPrefix"); if (combineWithPrefixNode != null) combineWithPrefix = combineWithPrefixNode.Value.Value; var jumpToPrefix = ""; - var jumpToPrefixNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "JumpToGroupKeyPrefix"); + var jumpToPrefixNode = widgetNode.Value.NodeWithKeyOrDefault("JumpToGroupKeyPrefix"); if (jumpToPrefixNode != null) jumpToPrefix = jumpToPrefixNode.Value.Value; diff --git a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromSourceLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromSourceLogic.cs index aabf39b7a3..e5ff34bbc4 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromSourceLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromSourceLogic.cs @@ -246,7 +246,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic { void RunSourceActions(MiniYamlNode contentPackageYaml) { - var sourceActionListYaml = contentPackageYaml.Value.Nodes.FirstOrDefault(x => x.Key == "Actions"); + var sourceActionListYaml = contentPackageYaml.Value.NodeWithKeyOrDefault("Actions"); if (sourceActionListYaml == null) return; @@ -263,7 +263,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic foreach (var packageInstallationNode in modSource.Install.Where(x => x.Key == "ContentPackage")) { - var packageName = packageInstallationNode.Value.Nodes.SingleOrDefault(x => x.Key == "Name")?.Value.Value; + var packageName = packageInstallationNode.Value.NodeWithKeyOrDefault("Name")?.Value.Value; if (!string.IsNullOrEmpty(packageName) && selectedPackages.TryGetValue(packageName, out var required) && required) RunSourceActions(packageInstallationNode); } @@ -338,9 +338,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic checkboxWidget.OnClick = () => selectedPackages[package.Identifier] = !selectedPackages[package.Identifier]; var contentPackageNode = source.Install.FirstOrDefault(x => - x.Value.Nodes.FirstOrDefault(y => y.Key == "Name")?.Value.Value == package.Identifier); + x.Value.NodeWithKeyOrDefault("Name")?.Value.Value == package.Identifier); - var tooltipText = contentPackageNode?.Value.Nodes.FirstOrDefault(x => x.Key == nameof(ModContent.ModSource.TooltipText))?.Value.Value; + var tooltipText = contentPackageNode?.Value.NodeWithKeyOrDefault(nameof(ModContent.ModSource.TooltipText))?.Value.Value; var tooltipIcon = containerWidget.Get("PACKAGE_INFO"); tooltipIcon.IsVisible = () => !string.IsNullOrWhiteSpace(tooltipText); tooltipIcon.GetTooltipText = () => tooltipText; diff --git a/OpenRA.Mods.Common/Widgets/Logic/ServerListLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/ServerListLogic.cs index 445791c800..18581affab 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/ServerListLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/ServerListLogic.cs @@ -487,7 +487,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic continue; var game = new MiniYamlBuilder(MiniYaml.FromString(bl.Data)[0].Value); - var idNode = game.Nodes.FirstOrDefault(n => n.Key == "Id"); + var idNode = game.NodeWithKeyOrDefault("Id"); // Skip beacons created by this instance and replace Id by expected int value if (idNode != null && idNode.Value.Value != Platform.SessionGUID.ToString()) @@ -495,7 +495,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic idNode.Value.Value = "-1"; // Rewrite the server address with the correct IP - var addressNode = game.Nodes.FirstOrDefault(n => n.Key == "Address"); + var addressNode = game.NodeWithKeyOrDefault("Address"); if (addressNode != null) addressNode.Value.Value = bl.Address.ToString().Split(':')[0] + ":" + addressNode.Value.Value.Split(':')[1]; diff --git a/OpenRA.Mods.Common/Widgets/Logic/Settings/HotkeysSettingsLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Settings/HotkeysSettingsLogic.cs index 561f8f991e..154fd98546 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Settings/HotkeysSettingsLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Settings/HotkeysSettingsLogic.cs @@ -138,7 +138,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic { foreach (var hg in hotkeyGroupsYaml.Nodes) { - var typesNode = hg.Value.Nodes.FirstOrDefault(n => n.Key == "Types"); + var typesNode = hg.Value.NodeWithKeyOrDefault("Types"); if (typesNode != null) hotkeyGroups.Add(hg.Key, FieldLoader.GetValue>("Types", typesNode.Value.Value)); } diff --git a/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs b/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs index 66edfbd2b2..d63a3af8bb 100644 --- a/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs +++ b/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs @@ -132,12 +132,12 @@ namespace OpenRA.Mods.Common.Widgets public static IEnumerable LinterHotkeyNames(MiniYamlNode widgetNode, Action emitError) { var prefix = ""; - var prefixNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "HotkeyPrefix"); + var prefixNode = widgetNode.Value.NodeWithKeyOrDefault("HotkeyPrefix"); if (prefixNode != null) prefix = prefixNode.Value.Value; var count = 0; - var countNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "HotkeyCount"); + var countNode = widgetNode.Value.NodeWithKeyOrDefault("HotkeyCount"); if (countNode != null) count = FieldLoader.GetValue("HotkeyCount", countNode.Value.Value); diff --git a/OpenRA.Mods.Common/Widgets/SupportPowersWidget.cs b/OpenRA.Mods.Common/Widgets/SupportPowersWidget.cs index f44c1869dd..2dc70f7364 100644 --- a/OpenRA.Mods.Common/Widgets/SupportPowersWidget.cs +++ b/OpenRA.Mods.Common/Widgets/SupportPowersWidget.cs @@ -70,12 +70,12 @@ namespace OpenRA.Mods.Common.Widgets public static IEnumerable LinterHotkeyNames(MiniYamlNode widgetNode, Action emitError) { var prefix = ""; - var prefixNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "HotkeyPrefix"); + var prefixNode = widgetNode.Value.NodeWithKeyOrDefault("HotkeyPrefix"); if (prefixNode != null) prefix = prefixNode.Value.Value; var count = 0; - var countNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "HotkeyCount"); + var countNode = widgetNode.Value.NodeWithKeyOrDefault("HotkeyCount"); if (countNode != null) count = FieldLoader.GetValue("HotkeyCount", countNode.Value.Value); diff --git a/OpenRA.Mods.Common/Widgets/ViewportControllerWidget.cs b/OpenRA.Mods.Common/Widgets/ViewportControllerWidget.cs index 7da08b1fb2..61b184d80d 100644 --- a/OpenRA.Mods.Common/Widgets/ViewportControllerWidget.cs +++ b/OpenRA.Mods.Common/Widgets/ViewportControllerWidget.cs @@ -105,17 +105,17 @@ namespace OpenRA.Mods.Common.Widgets public static IEnumerable LinterHotkeyNames(MiniYamlNode widgetNode, Action emitError) { var savePrefix = ""; - var savePrefixNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "BookmarkSaveKeyPrefix"); + var savePrefixNode = widgetNode.Value.NodeWithKeyOrDefault("BookmarkSaveKeyPrefix"); if (savePrefixNode != null) savePrefix = savePrefixNode.Value.Value; var restorePrefix = ""; - var restorePrefixNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "BookmarkRestoreKeyPrefix"); + var restorePrefixNode = widgetNode.Value.NodeWithKeyOrDefault("BookmarkRestoreKeyPrefix"); if (restorePrefixNode != null) restorePrefix = restorePrefixNode.Value.Value; var count = 0; - var countNode = widgetNode.Value.Nodes.FirstOrDefault(n => n.Key == "BookmarkKeyCount"); + var countNode = widgetNode.Value.NodeWithKeyOrDefault("BookmarkKeyCount"); if (countNode != null) count = FieldLoader.GetValue("BookmarkKeyCount", countNode.Value.Value); diff --git a/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs b/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs index 462b733880..70d19fdbd2 100644 --- a/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs +++ b/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs @@ -384,7 +384,7 @@ namespace OpenRA.Mods.D2k.UtilityCommands actorNodes.Add(new MiniYamlNode("Actor" + (map.ActorDefinitions.Count + actorNodes.Count), a.Save())); if (map.PlayerDefinitions.Concat(playerNodes).All( - x => x.Value.Nodes.Single(y => y.Key == "Name").Value.Value != kvp.Owner)) + x => x.Value.NodeWithKey("Name").Value.Value != kvp.Owner)) { var playerInfo = PlayerReferenceDataByPlayerName[kvp.Owner]; var playerReference = new PlayerReference diff --git a/OpenRA.Test/OpenRA.Game/MiniYamlTest.cs b/OpenRA.Test/OpenRA.Game/MiniYamlTest.cs index c9b223b276..4223eca9cc 100644 --- a/OpenRA.Test/OpenRA.Game/MiniYamlTest.cs +++ b/OpenRA.Test/OpenRA.Game/MiniYamlTest.cs @@ -247,7 +247,7 @@ Test: .First(n => n.Key == "Test").Value.Nodes; Assert.IsTrue(result.Any(n => n.Key == "MockString"), "Node should have the MockString child, but does not."); - Assert.IsTrue(result.First(n => n.Key == "MockString").Value.ToDictionary()["AString"].Value == "Override", + Assert.IsTrue(result.First(n => n.Key == "MockString").Value.NodeWithKey("AString").Value.Value == "Override", "MockString value has not been set with the correct override value for AString."); } @@ -272,7 +272,7 @@ Test: .First(n => n.Key == "Test").Value.Nodes; Assert.IsTrue(result.Any(n => n.Key == "MockString"), "Node should have the MockString child, but does not."); - Assert.IsTrue(result.First(n => n.Key == "MockString").Value.ToDictionary()["AString"].Value == "Override", + Assert.IsTrue(result.First(n => n.Key == "MockString").Value.NodeWithKey("AString").Value.Value == "Override", "MockString value has not been set with the correct override value for AString."); } diff --git a/mods/ts/sequences/civilian.yaml b/mods/ts/sequences/civilian.yaml index 8e5aaa9cd5..d6903b3f0a 100644 --- a/mods/ts/sequences/civilian.yaml +++ b/mods/ts/sequences/civilian.yaml @@ -779,7 +779,6 @@ ctdam: damaged-idle-water-b: Filename: ctdam_b.shp Start: 24 - Start: 8 Length: 8 Tick: 200 diff --git a/mods/ts/sequences/structures.yaml b/mods/ts/sequences/structures.yaml index acc008a3c8..3cd244ed7f 100644 --- a/mods/ts/sequences/structures.yaml +++ b/mods/ts/sequences/structures.yaml @@ -1667,7 +1667,6 @@ napuls.nod: ZOffset: 512 BlendMode: Additive IgnoreWorldTint: True - IgnoreWorldTint: True DepthSprite: icon: Filename: sidebar-nod|empicon.shp