From 949ba589c0c47df3214e873d99fe24b2e18752ab Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Sat, 6 May 2023 10:39:15 +0100 Subject: [PATCH] MiniYaml becomes an immutable data structure. This changeset is motivated by a simple concept - get rid of the MiniYaml.Clone and MiniYamlNode.Clone methods to avoid deep copying yaml trees during merging. MiniYaml becoming immutable allows the merge function to reuse existing yaml trees rather than cloning them, saving on memory and improving merge performance. On initial loading the YAML for all maps is processed, so this provides a small reduction in initial loading time. The rest of the changeset is dealing with the change in the exposed API surface. Some With* helper methods are introduced to allow creating new YAML from existing YAML. Areas of code that generated small amounts of YAML are able to transition directly to the immutable model without too much ceremony. Some use cases are far less ergonomic even with these helper methods and so a MiniYamlBuilder is introduced to retain mutable creation functionality. This allows those areas to continue to use the old mutable structures. The main users are the update rules and linting capabilities. --- OpenRA.Game/ExternalMods.cs | 12 +- OpenRA.Game/FieldLoader.cs | 2 +- OpenRA.Game/FieldSaver.cs | 2 +- OpenRA.Game/GameRules/Ruleset.cs | 4 +- OpenRA.Game/GameRules/WeaponInfo.cs | 2 +- OpenRA.Game/Map/ActorReference.cs | 6 +- OpenRA.Game/Map/Map.cs | 29 +- OpenRA.Game/Map/MapPreview.cs | 2 +- OpenRA.Game/MiniYaml.cs | 294 +++++++++++++----- OpenRA.Game/Network/GameServer.cs | 6 +- OpenRA.Game/Network/LocalizedMessage.cs | 7 +- OpenRA.Game/Network/Session.cs | 5 +- OpenRA.Game/Settings.cs | 21 +- OpenRA.Game/Traits/TraitsInterfaces.cs | 3 +- .../UtilityCommands/ImportGen1MapCommand.cs | 104 ++++--- .../UtilityCommands/ImportGen2MapCommand.cs | 34 +- .../ImportRedAlertMapCommand.cs | 10 +- .../ImportTiberianDawnMapCommand.cs | 10 +- .../Graphics/DefaultSpriteSequence.cs | 10 +- OpenRA.Mods.Common/Lint/CheckChromeHotkeys.cs | 5 +- .../Lint/CheckChromeIntegerExpressions.cs | 2 +- OpenRA.Mods.Common/Lint/CheckChromeLogic.cs | 2 +- .../Lint/CheckUnknownTraitFields.cs | 6 +- .../Lint/CheckUnknownWeaponFields.cs | 4 +- .../Lint/CheckWorldAndPlayerInherits.cs | 2 +- OpenRA.Mods.Common/ModContent.cs | 3 +- OpenRA.Mods.Common/Terrain/TerrainInfo.cs | 2 +- .../Traits/BotModules/BaseBuilderBotModule.cs | 3 +- .../Traits/BotModules/McvManagerBotModule.cs | 3 +- .../BotModules/SquadManagerBotModule.cs | 3 +- .../Traits/BotModules/Squads/Squad.cs | 9 +- .../BotModules/SupportPowerBotModule.cs | 3 +- .../Traits/BotModules/UnitBuilderBotModule.cs | 3 +- .../Traits/Player/GameSaveViewportManager.cs | 3 +- .../Traits/World/ControlGroups.cs | 3 +- OpenRA.Mods.Common/Traits/World/Locomotor.cs | 2 +- OpenRA.Mods.Common/Traits/World/Selection.cs | 3 +- .../Rules/20200503/AddPipDecorationTraits.cs | 20 +- .../ChangeTargetLineDelayToMilliseconds.cs | 2 +- .../ConvertSupportPowerRangesToFootprint.cs | 8 +- .../CreateFlashPaletteEffectWarhead.cs | 2 +- .../20200503/ModernizeDecorationTraits.cs | 2 +- .../Rules/20200503/MoveClassicFacingFudge.cs | 4 +- .../Rules/20200503/RemoveConditionManager.cs | 2 +- .../Rules/20200503/RemoveLaysTerrain.cs | 2 +- .../20200503/RemoveMuzzleSplitFacings.cs | 2 +- .../Rules/20200503/RemoveTurnToDock.cs | 2 +- .../Rules/20200503/RenameCircleOutline.cs | 2 +- .../Rules/20200503/RenameHealCrateAction.cs | 2 +- .../RenameInfiltrationNotifications.cs | 2 +- .../Rules/20200503/RenameSelfHealing.cs | 2 +- .../Rules/20200503/RenameSmudgeSmokeFields.cs | 2 +- .../Rules/20200503/RenameStances.cs | 4 +- .../Rules/20200503/RenameWithNukeLaunch.cs | 2 +- .../Rules/20200503/ReplaceBurns.cs | 8 +- .../Rules/20200503/ReplaceFacingAngles.cs | 4 +- .../20200503/SpawnActorPowerDefaultEffect.cs | 2 +- .../Rules/20200503/SplitDamagedByTerrain.cs | 2 +- .../Rules/20200503/UpdateMapInits.cs | 6 +- .../Rules/20200503/UpdateTilesetColors.cs | 2 +- .../Rules/20210321/AddControlGroups.cs | 4 +- .../20210321/AttackBomberFacingTolerance.cs | 4 +- .../20210321/AttackFrontalFacingTolerance.cs | 4 +- .../Rules/20210321/ConvertBoundsToWDist.cs | 2 +- .../Rules/20210321/RemoveDomainIndex.cs | 6 +- .../20210321/RemovePlaceBuildingPalette.cs | 2 +- .../20210321/RemovePlayerHighlightPalette.cs | 2 +- .../20210321/RemoveRenderSpritesScale.cs | 2 +- .../Rules/20210321/RemoveResourceType.cs | 20 +- .../20210321/RemoveSmokeTrailWhenDamaged.cs | 4 +- .../Rules/20210321/RenameCloakTypes.cs | 2 +- .../20210321/RenameContrailProperties.cs | 4 +- .../Rules/20210321/RenameMPTraits.cs | 2 +- .../20210321/RenameSupportPowerDescription.cs | 2 +- .../20210321/ReplaceCrateSecondsWithTicks.cs | 2 +- .../20210321/ReplaceResourceValueModifiers.cs | 2 +- .../ReplaceSequenceEmbeddedPalette.cs | 2 +- .../Rules/20210321/ReplaceShadowPalette.cs | 4 +- .../ReplaceWithColoredOverlayPalette.cs | 2 +- .../20210321/SplitNukePowerMissileImage.cs | 4 +- .../UnhardcodeBaseBuilderBotModule.cs | 8 +- .../Rules/20210321/UnhardcodeSquadManager.cs | 10 +- .../UnhardcodeVeteranProductionIconOverlay.cs | 4 +- .../20210321/UseMillisecondsForSounds.cs | 2 +- .../20230225/AddColorPickerValueRange.cs | 2 +- .../20230225/ExplicitSequenceFilenames.cs | 42 +-- ...ductionTabsWidgetAddTabButtonCollection.cs | 4 +- .../RemoveExperienceFromInfiltrates.cs | 2 +- .../20230225/RemoveNegativeSequenceLength.cs | 8 +- .../RemoveSequenceHasEmbeddedPalette.cs | 4 +- .../Rules/20230225/RemoveTSRefinery.cs | 2 +- .../Rules/20230225/RenameContrailWidth.cs | 4 +- .../Rules/20230225/RenameEngineerRepair.cs | 2 +- .../Rules/20230225/RenameMcvCrateAction.cs | 2 +- ...extNotificationsDisplayWidgetRemoveTime.cs | 2 +- .../Rules/CopyIsometricSelectableHeight.cs | 4 +- OpenRA.Mods.Common/UpdateRules/UpdateRule.cs | 24 +- OpenRA.Mods.Common/UpdateRules/UpdateUtils.cs | 67 ++-- .../UtilityCommands/ResizeMapCommand.cs | 4 +- .../UtilityCommands/UpdateModCommand.cs | 2 +- .../Widgets/Logic/Editor/SaveMapLogic.cs | 2 +- .../Widgets/Logic/ServerListLogic.cs | 6 +- .../UtilityCommands/D2kMapImporter.cs | 12 +- OpenRA.Test/OpenRA.Game/MiniYamlTest.cs | 110 ++++++- 104 files changed, 722 insertions(+), 393 deletions(-) diff --git a/OpenRA.Game/ExternalMods.cs b/OpenRA.Game/ExternalMods.cs index 002cfbccca..3decd6b20c 100644 --- a/OpenRA.Game/ExternalMods.cs +++ b/OpenRA.Game/ExternalMods.cs @@ -122,7 +122,7 @@ namespace OpenRA return; var key = ExternalMod.MakeKey(mod); - var yaml = new MiniYamlNode("Registration", new MiniYaml("", new List() + var yaml = new MiniYamlNode("Registration", new MiniYaml("", new[] { new MiniYamlNode("Id", mod.Id), new MiniYamlNode("Version", mod.Metadata.Version), @@ -131,17 +131,21 @@ namespace OpenRA new MiniYamlNode("LaunchArgs", new[] { "Game.Mod=" + mod.Id }.Concat(launchArgs).JoinWith(", ")) })); + var iconNodes = new List(); + using (var stream = mod.Package.GetStream("icon.png")) if (stream != null) - yaml.Value.Nodes.Add(new MiniYamlNode("Icon", Convert.ToBase64String(stream.ReadAllBytes()))); + iconNodes.Add(new MiniYamlNode("Icon", Convert.ToBase64String(stream.ReadAllBytes()))); using (var stream = mod.Package.GetStream("icon-2x.png")) if (stream != null) - yaml.Value.Nodes.Add(new MiniYamlNode("Icon2x", Convert.ToBase64String(stream.ReadAllBytes()))); + iconNodes.Add(new MiniYamlNode("Icon2x", Convert.ToBase64String(stream.ReadAllBytes()))); using (var stream = mod.Package.GetStream("icon-3x.png")) if (stream != null) - yaml.Value.Nodes.Add(new MiniYamlNode("Icon3x", Convert.ToBase64String(stream.ReadAllBytes()))); + iconNodes.Add(new MiniYamlNode("Icon3x", Convert.ToBase64String(stream.ReadAllBytes()))); + + yaml = yaml.WithValue(yaml.Value.WithNodesAppended(iconNodes)); var sources = new HashSet(); if (registration.HasFlag(ModRegistration.System)) diff --git a/OpenRA.Game/FieldLoader.cs b/OpenRA.Game/FieldLoader.cs index 9864babac9..ec70ee97c5 100644 --- a/OpenRA.Game/FieldLoader.cs +++ b/OpenRA.Game/FieldLoader.cs @@ -500,7 +500,7 @@ namespace OpenRA if (yaml == null) return Activator.CreateInstance(fieldType); - var dict = Activator.CreateInstance(fieldType, yaml.Nodes.Count); + var dict = Activator.CreateInstance(fieldType, yaml.Nodes.Length); var arguments = fieldType.GetGenericArguments(); var addMethod = fieldType.GetMethod(nameof(Dictionary.Add), arguments); var addArgs = new object[2]; diff --git a/OpenRA.Game/FieldSaver.cs b/OpenRA.Game/FieldSaver.cs index b676ab1987..e6d0e4d397 100644 --- a/OpenRA.Game/FieldSaver.cs +++ b/OpenRA.Game/FieldSaver.cs @@ -58,7 +58,7 @@ namespace OpenRA return new MiniYaml( null, - fields.Select(info => new MiniYamlNode(info.YamlName, FormatValue(o, info.Field))).ToList()); + fields.Select(info => new MiniYamlNode(info.YamlName, FormatValue(o, info.Field)))); } public static MiniYamlNode SaveField(object o, string field) diff --git a/OpenRA.Game/GameRules/Ruleset.cs b/OpenRA.Game/GameRules/Ruleset.cs index 8aa04d8d4c..312c6805aa 100644 --- a/OpenRA.Game/GameRules/Ruleset.cs +++ b/OpenRA.Game/GameRules/Ruleset.cs @@ -226,10 +226,10 @@ namespace OpenRA static bool AnyCustomYaml(MiniYaml yaml) { - return yaml != null && (yaml.Value != null || yaml.Nodes.Count > 0); + return yaml != null && (yaml.Value != null || yaml.Nodes.Length > 0); } - static bool AnyFlaggedTraits(ModData modData, List actors) + static bool AnyFlaggedTraits(ModData modData, IEnumerable actors) { foreach (var actorNode in actors) { diff --git a/OpenRA.Game/GameRules/WeaponInfo.cs b/OpenRA.Game/GameRules/WeaponInfo.cs index 5322c6de14..0d9cebff7d 100644 --- a/OpenRA.Game/GameRules/WeaponInfo.cs +++ b/OpenRA.Game/GameRules/WeaponInfo.cs @@ -139,7 +139,7 @@ namespace OpenRA.GameRules { // Resolve any weapon-level yaml inheritance or removals // HACK: The "Defaults" sequence syntax prevents us from doing this generally during yaml parsing - content.Nodes = MiniYaml.Merge(new[] { content.Nodes }); + content = content.WithNodes(MiniYaml.Merge(new IReadOnlyCollection[] { content.Nodes })); FieldLoader.Load(this, content); } diff --git a/OpenRA.Game/Map/ActorReference.cs b/OpenRA.Game/Map/ActorReference.cs index 180a7847c7..7cdf657848 100644 --- a/OpenRA.Game/Map/ActorReference.cs +++ b/OpenRA.Game/Map/ActorReference.cs @@ -84,7 +84,7 @@ namespace OpenRA public MiniYaml Save(Func initFilter = null) { - var ret = new MiniYaml(Type); + var nodes = new List(); foreach (var o in initDict.Value) { if (o is not ActorInit init || o is ISuppressInitExport) @@ -98,10 +98,10 @@ namespace OpenRA if (!string.IsNullOrEmpty(init.InstanceName)) initName += ActorInfo.TraitInstanceSeparator + init.InstanceName; - ret.Nodes.Add(new MiniYamlNode(initName, init.Save())); + nodes.Add(new MiniYamlNode(initName, init.Save())); } - return ret; + return new MiniYaml(Type, nodes); } public IEnumerator GetEnumerator() { return initDict.Value.GetEnumerator(); } diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index e987f1a515..253bc5de18 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; using System.Linq; using System.Reflection; @@ -90,11 +91,11 @@ namespace OpenRA throw new InvalidOperationException("Map does not have a field/property " + fieldName); var t = field != null ? field.FieldType : property.PropertyType; - type = t == typeof(List) ? Type.NodeList : + type = t == typeof(IReadOnlyCollection) ? Type.NodeList : t == typeof(MiniYaml) ? Type.MiniYaml : Type.Normal; } - public void Deserialize(Map map, List nodes) + public void Deserialize(Map map, ImmutableArray nodes) { var node = nodes.FirstOrDefault(n => n.Key == key); if (node == null) @@ -130,14 +131,14 @@ namespace OpenRA var value = field != null ? field.GetValue(map) : property.GetValue(map, null); if (type == Type.NodeList) { - var listValue = (List)value; + var listValue = (IReadOnlyCollection)value; if (required || listValue.Count > 0) nodes.Add(new MiniYamlNode(key, null, listValue)); } else if (type == Type.MiniYaml) { var yamlValue = (MiniYaml)value; - if (required || (yamlValue != null && (yamlValue.Value != null || yamlValue.Nodes.Count > 0))) + if (required || (yamlValue != null && (yamlValue.Value != null || yamlValue.Nodes.Length > 0))) nodes.Add(new MiniYamlNode(key, yamlValue)); } else @@ -197,18 +198,18 @@ namespace OpenRA public int2 MapSize { get; private set; } // Player and actor yaml. Public for access by the map importers and lint checks. - public List PlayerDefinitions = new(); - public List ActorDefinitions = new(); + public IReadOnlyCollection PlayerDefinitions = ImmutableArray.Empty; + public IReadOnlyCollection ActorDefinitions = ImmutableArray.Empty; // Custom map yaml. Public for access by the map importers and lint checks - public readonly MiniYaml RuleDefinitions; - public readonly MiniYaml TranslationDefinitions; - public readonly MiniYaml SequenceDefinitions; - public readonly MiniYaml ModelSequenceDefinitions; - public readonly MiniYaml WeaponDefinitions; - public readonly MiniYaml VoiceDefinitions; - public readonly MiniYaml MusicDefinitions; - public readonly MiniYaml NotificationDefinitions; + public MiniYaml RuleDefinitions; + public MiniYaml TranslationDefinitions; + public MiniYaml SequenceDefinitions; + public MiniYaml ModelSequenceDefinitions; + public MiniYaml WeaponDefinitions; + public MiniYaml VoiceDefinitions; + public MiniYaml MusicDefinitions; + public MiniYaml NotificationDefinitions; public readonly Dictionary ReplacedInvalidTerrainTiles = new(); diff --git a/OpenRA.Game/Map/MapPreview.cs b/OpenRA.Game/Map/MapPreview.cs index e38def96ac..ec815de07b 100644 --- a/OpenRA.Game/Map/MapPreview.cs +++ b/OpenRA.Game/Map/MapPreview.cs @@ -142,7 +142,7 @@ namespace OpenRA var sources = modDataRules.Select(x => x.Where(IsLoadableRuleDefinition).ToList()) .Concat(files.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s).Where(IsLoadableRuleDefinition).ToList())); - if (RuleDefinitions.Nodes.Count > 0) + if (RuleDefinitions.Nodes.Length > 0) sources = sources.Append(RuleDefinitions.Nodes.Where(IsLoadableRuleDefinition).ToList()); var yamlNodes = MiniYaml.Merge(sources); diff --git a/OpenRA.Game/MiniYaml.cs b/OpenRA.Game/MiniYaml.cs index d1056a63b3..5765f2621a 100644 --- a/OpenRA.Game/MiniYaml.cs +++ b/OpenRA.Game/MiniYaml.cs @@ -20,18 +20,36 @@ namespace OpenRA { public static class MiniYamlExts { - public static void WriteToFile(this List y, string filename) + public static void WriteToFile(this IEnumerable y, string filename) { File.WriteAllLines(filename, y.ToLines().Select(x => x.TrimEnd()).ToArray()); } - public static string WriteToString(this List y) + public static string WriteToString(this IEnumerable y) { // Remove all trailing newlines and restore the final EOF newline return y.ToLines().JoinWith("\n").TrimEnd('\n') + "\n"; } - public static IEnumerable ToLines(this List y) + public static IEnumerable ToLines(this IEnumerable y) + { + foreach (var kv in y) + foreach (var line in kv.Value.ToLines(kv.Key, kv.Comment)) + yield return line; + } + + public static void WriteToFile(this IEnumerable y, string filename) + { + File.WriteAllLines(filename, y.ToLines().Select(x => x.TrimEnd()).ToArray()); + } + + public static string WriteToString(this IEnumerable y) + { + // Remove all trailing newlines and restore the final EOF newline + return y.ToLines().JoinWith("\n").TrimEnd('\n') + "\n"; + } + + public static IEnumerable ToLines(this IEnumerable y) { foreach (var kv in y) foreach (var line in kv.Value.ToLines(kv.Key, kv.Comment)) @@ -55,10 +73,17 @@ namespace OpenRA public override string ToString() { return $"{Filename}:{Line}"; } } - public SourceLocation Location; - public string Key; - public MiniYaml Value; - public string Comment; + public readonly SourceLocation Location; + public readonly string Key; + public readonly MiniYaml Value; + public readonly string Comment; + + public MiniYamlNode WithValue(MiniYaml value) + { + if (Value == value) + return this; + return new MiniYamlNode(Key, value, Comment, Location); + } public MiniYamlNode(string k, MiniYaml v, string c = null) { @@ -74,26 +99,15 @@ namespace OpenRA } public MiniYamlNode(string k, string v, string c = null) - : this(k, v, c, null) { } + : this(k, new MiniYaml(v, Enumerable.Empty()), c) { } - public MiniYamlNode(string k, string v, List n) + public MiniYamlNode(string k, string v, IEnumerable n) : this(k, new MiniYaml(v, n), null) { } - public MiniYamlNode(string k, string v, string c, List n) - : this(k, new MiniYaml(v, n), c) { } - - public MiniYamlNode(string k, string v, string c, List n, SourceLocation loc) - : this(k, new MiniYaml(v, n), c, loc) { } - public override string ToString() { return $"{{YamlNode: {Key} @ {Location}}}"; } - - public MiniYamlNode Clone() - { - return new MiniYamlNode(Key, Value.Clone(), Comment, Location); - } } public sealed class MiniYaml @@ -101,15 +115,30 @@ namespace OpenRA const int SpacesPerLevel = 4; static readonly Func StringIdentity = s => s; static readonly Func MiniYamlIdentity = my => my; - public string Value; - public List Nodes; - public MiniYaml Clone() + public readonly string Value; + public readonly ImmutableArray Nodes; + + public MiniYaml WithValue(string value) { - var clonedNodes = new List(Nodes.Count); - foreach (var node in Nodes) - clonedNodes.Add(node.Clone()); - return new MiniYaml(Value, clonedNodes); + if (Value == value) + return this; + return new MiniYaml(value, Nodes); + } + + public MiniYaml WithNodes(IEnumerable nodes) + { + if (nodes is ImmutableArray n && Nodes == n) + return this; + return new MiniYaml(Value, nodes); + } + + public MiniYaml WithNodesAppended(IEnumerable nodes) + { + var newNodes = Nodes.AddRange(nodes); + if (Nodes == newNodes) + return this; + return new MiniYaml(Value, newNodes); } public Dictionary ToDictionary() @@ -125,7 +154,7 @@ namespace OpenRA public Dictionary ToDictionary( Func keySelector, Func elementSelector) { - var ret = new Dictionary(Nodes.Count); + var ret = new Dictionary(Nodes.Length); foreach (var y in Nodes) { var key = keySelector(y.Key); @@ -138,28 +167,28 @@ namespace OpenRA } public MiniYaml(string value) - : this(value, null) { } + : this(value, Enumerable.Empty()) { } - public MiniYaml(string value, List nodes) + public MiniYaml(string value, IEnumerable nodes) { Value = value; - Nodes = nodes ?? new List(); + Nodes = ImmutableArray.CreateRange(nodes); } - public static List NodesOrEmpty(MiniYaml y, string s) + public static ImmutableArray NodesOrEmpty(MiniYaml y, string s) { - var nd = y.ToDictionary(); - return nd.TryGetValue(s, out var v) ? v.Nodes : new List(); + 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(); - var levels = new List> + var result = new List> { new List() }; + var parsedLines = new List<(int Level, string Key, string Value, string Comment, MiniYamlNode.SourceLocation Location)>(); var lineNo = 0; foreach (var ll in lines) @@ -206,15 +235,9 @@ namespace OpenRA } } - if (levels.Count <= level) + if (parsedLines.Count > 0 && parsedLines[^1].Level < level - 1) throw new YamlException($"Bad indent in miniyaml at {location}"); - while (levels.Count > level + 1) - { - levels[^1].TrimExcess(); - levels.RemoveAt(levels.Count - 1); - } - // Extract key, value, comment from line as `: #` // The # character is allowed in the value if escaped (\#). // Leading and trailing whitespace is always trimmed from keys. @@ -274,6 +297,9 @@ namespace OpenRA if (!key.IsEmpty || !discardCommentsAndWhitespace) { + while (parsedLines.Count > 0 && parsedLines[^1].Level > level) + BuildCompletedSubNode(level); + var keyString = key.IsEmpty ? null : key.ToString(); var valueString = value.IsEmpty ? null : value.ToString(); @@ -285,17 +311,46 @@ namespace OpenRA valueString = valueString == null ? null : stringPool.GetOrAdd(valueString, valueString); commentString = commentString == null ? null : stringPool.GetOrAdd(commentString, commentString); - var nodes = new List(); - levels[level].Add(new MiniYamlNode(keyString, valueString, commentString, nodes, location)); - - levels.Add(nodes); + parsedLines.Add((level, keyString, valueString, commentString, location)); } } - foreach (var nodes in levels) - nodes.TrimExcess(); + if (parsedLines.Count > 0) + BuildCompletedSubNode(0); - return levels[0]; + return result[0]; + + void BuildCompletedSubNode(int level) + { + var lastLevel = parsedLines[^1].Level; + while (lastLevel >= result.Count) + result.Add(new List()); + + while (parsedLines.Count > 0 && parsedLines[^1].Level >= level) + { + var parent = parsedLines[^1]; + var startOfRange = parsedLines.Count - 1; + while (startOfRange > 0 && parsedLines[startOfRange - 1].Level == parent.Level) + startOfRange--; + + for (var i = startOfRange; i < parsedLines.Count - 1; i++) + { + var sibling = parsedLines[i]; + result[parent.Level].Add( + new MiniYamlNode(sibling.Key, new MiniYaml(sibling.Value), sibling.Comment, sibling.Location)); + } + + var childNodes = parent.Level + 1 < result.Count ? result[parent.Level + 1] : null; + result[parent.Level].Add(new MiniYamlNode( + parent.Key, + new MiniYaml(parent.Value, childNodes ?? Enumerable.Empty()), + parent.Comment, + parent.Location)); + childNodes?.Clear(); + + parsedLines.RemoveRange(startOfRange, parsedLines.Count - startOfRange); + } + } } public static List FromFile(string path, bool discardCommentsAndWhitespace = true, Dictionary stringPool = null) @@ -313,7 +368,7 @@ namespace OpenRA return FromLines(text.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None).Select(s => s.AsMemory()), fileName, discardCommentsAndWhitespace, stringPool); } - public static List Merge(IEnumerable> sources) + public static List Merge(IEnumerable> sources) { var sourcesList = sources.ToList(); if (sourcesList.Count == 0) @@ -336,7 +391,7 @@ namespace OpenRA } // Resolve any top-level removals (e.g. removing whole actor blocks) - var nodes = new MiniYaml("", resolved.Select(kv => new MiniYamlNode(kv.Key, kv.Value)).ToList()); + var nodes = new MiniYaml("", resolved.Select(kv => new MiniYamlNode(kv.Key, kv.Value))); return ResolveInherits(nodes, tree, ImmutableDictionary.Empty); } @@ -345,19 +400,23 @@ namespace OpenRA { if (existingNodeKeys.Add(overrideNode.Key)) { - existingNodes.Add(overrideNode.Clone()); + existingNodes.Add(overrideNode); return; } - var existingNode = existingNodes.Find(n => n.Key == overrideNode.Key); - existingNode.Value = MergePartial(existingNode.Value, overrideNode.Value); - existingNode.Value.Nodes = ResolveInherits(existingNode.Value, tree, inherited); + var existingNodeIndex = IndexOfKey(existingNodes, overrideNode.Key); + var existingNode = existingNodes[existingNodeIndex]; + var value = MergePartial(existingNode.Value, overrideNode.Value); + var nodes = ResolveInherits(value, tree, inherited); + if (!value.Nodes.SequenceEqual(nodes)) + value = value.WithNodes(nodes); + existingNodes[existingNodeIndex] = existingNode.WithValue(value); } static List ResolveInherits(MiniYaml node, Dictionary tree, ImmutableDictionary inherited) { - var resolved = new List(node.Nodes.Count); - var resolvedKeys = new HashSet(node.Nodes.Count); + var resolved = new List(node.Nodes.Length); + var resolvedKeys = new HashSet(node.Nodes.Length); foreach (var n in node.Nodes) { @@ -390,7 +449,6 @@ namespace OpenRA MergeIntoResolved(n, resolved, resolvedKeys, tree, inherited); } - resolved.TrimExcess(); return resolved; } @@ -398,7 +456,7 @@ namespace OpenRA /// Merges any duplicate keys that are defined within the same set of nodes. /// Does not resolve inheritance or node removals. /// - static List MergeSelfPartial(List existingNodes) + static IReadOnlyCollection MergeSelfPartial(IReadOnlyCollection existingNodes) { var keys = new HashSet(existingNodes.Count); var ret = new List(existingNodes.Count); @@ -409,12 +467,12 @@ namespace OpenRA else { // Node with the same key has already been added: merge new node over the existing one - var original = ret.First(r => r.Key == n.Key); - original.Value = MergePartial(original.Value, n.Value); + var originalIndex = IndexOfKey(ret, n.Key); + var original = ret[originalIndex]; + ret[originalIndex] = original.WithValue(MergePartial(original.Value, n.Value)); } } - ret.TrimExcess(); return ret; } @@ -432,7 +490,7 @@ namespace OpenRA return new MiniYaml(overrideNodes.Value ?? existingNodes.Value, MergePartial(existingNodes.Nodes, overrideNodes.Nodes)); } - static List MergePartial(List existingNodes, List overrideNodes) + static IReadOnlyCollection MergePartial(IReadOnlyCollection existingNodes, IReadOnlyCollection overrideNodes) { if (existingNodes.Count == 0) return overrideNodes; @@ -468,9 +526,8 @@ namespace OpenRA // A Removal node is closer than the previous node. // We should not merge the new node, as the data being merged will jump before the Removal. // Instead, append it so the previous node is applied, then removed, then the new node is applied. - var removalKey = $"-{node.Key}"; - var previousNodeIndex = ret.FindLastIndex(n => n.Key == node.Key); - var previousRemovalNodeIndex = ret.FindLastIndex(n => n.Key == removalKey); + var previousNodeIndex = LastIndexOfKey(ret, node.Key); + var previousRemovalNodeIndex = LastIndexOfKey(ret, $"-{node.Key}"); if (previousRemovalNodeIndex != -1 && previousRemovalNodeIndex > previousNodeIndex) { ret.Add(node); @@ -479,13 +536,30 @@ namespace OpenRA // A previous node is present with no intervening Removal. // We should merge the new one into it, in place. - ret[previousNodeIndex] = new MiniYamlNode(node.Key, MergePartial(ret[previousNodeIndex].Value, node.Value), node.Comment, node.Location); + ret[previousNodeIndex] = node.WithValue(MergePartial(ret[previousNodeIndex].Value, node.Value)); } - ret.TrimExcess(); return ret; } + static int IndexOfKey(List nodes, string key) + { + // PERF: Avoid LINQ. + for (var i = 0; i < nodes.Count; i++) + if (nodes[i].Key == key) + return i; + return -1; + } + + static int LastIndexOfKey(List nodes, string key) + { + // PERF: Avoid LINQ. + for (var i = nodes.Count - 1; i >= 0; i--) + if (nodes[i].Key == key) + return i; + return -1; + } + public IEnumerable ToLines(string key, string comment = null) { var hasKey = !string.IsNullOrEmpty(key); @@ -508,14 +582,94 @@ namespace OpenRA files = files.Append(mapFiles); } - var yaml = files.Select(s => FromStream(fileSystem.Open(s), s)); - if (mapRules != null && mapRules.Nodes.Count > 0) + IEnumerable> yaml = files.Select(s => FromStream(fileSystem.Open(s), s)); + if (mapRules != null && mapRules.Nodes.Length > 0) yaml = yaml.Append(mapRules.Nodes); return Merge(yaml); } } + public sealed class MiniYamlNodeBuilder + { + public MiniYamlNode.SourceLocation Location; + public string Key; + public MiniYamlBuilder Value; + public string Comment; + + public MiniYamlNodeBuilder(MiniYamlNode node) + { + Location = node.Location; + Key = node.Key; + Value = new MiniYamlBuilder(node.Value); + Comment = node.Comment; + } + + public MiniYamlNodeBuilder(string k, MiniYamlBuilder v, string c = null) + { + Key = k; + Value = v; + Comment = c; + } + + public MiniYamlNodeBuilder(string k, MiniYamlBuilder v, string c, MiniYamlNode.SourceLocation loc) + : this(k, v, c) + { + Location = loc; + } + + public MiniYamlNodeBuilder(string k, string v, string c = null) + : this(k, new MiniYamlBuilder(v, null), c) { } + + public MiniYamlNodeBuilder(string k, string v, List n) + : this(k, new MiniYamlBuilder(v, n), null) { } + + public MiniYamlNode Build() + { + return new MiniYamlNode(Key, Value.Build(), Comment, Location); + } + } + + public sealed class MiniYamlBuilder + { + public string Value; + public List Nodes; + + public MiniYamlBuilder(MiniYaml yaml) + { + Value = yaml.Value; + Nodes = yaml.Nodes.Select(n => new MiniYamlNodeBuilder(n)).ToList(); + } + + public MiniYamlBuilder(string value) + : this(value, null) { } + + public MiniYamlBuilder(string value, List nodes) + { + Value = value; + Nodes = nodes == null ? new List() : nodes.Select(x => new MiniYamlNodeBuilder(x)).ToList(); + } + + public MiniYaml Build() + { + return new MiniYaml(Value, Nodes.Select(n => n.Build())); + } + + public IEnumerable ToLines(string key, string comment = null) + { + var hasKey = !string.IsNullOrEmpty(key); + var hasValue = !string.IsNullOrEmpty(Value); + var hasComment = comment != null; + yield return (hasKey ? key + ":" : "") + + (hasValue ? " " + Value.Replace("#", "\\#") : "") + + (hasComment ? (hasKey || hasValue ? " " : "") + "#" + comment : ""); + + if (Nodes != null) + foreach (var line in Nodes.ToLines()) + yield return "\t" + line; + } + } + [Serializable] public class YamlException : Exception { diff --git a/OpenRA.Game/Network/GameServer.cs b/OpenRA.Game/Network/GameServer.cs index 104b0d8f3f..7fb500c6b2 100644 --- a/OpenRA.Game/Network/GameServer.cs +++ b/OpenRA.Game/Network/GameServer.cs @@ -251,10 +251,8 @@ namespace OpenRA.Network root.Add(new MiniYamlNode("Mods", Mod + "@" + Version)); } - var clientsNode = new MiniYaml(""); - var i = 0; - foreach (var c in Clients) - clientsNode.Nodes.Add(new MiniYamlNode("Client@" + i++.ToString(), FieldSaver.Save(c))); + var clientsNode = new MiniYaml("", Clients.Select((c, i) => + new MiniYamlNode("Client@" + i, FieldSaver.Save(c)))); root.Add(new MiniYamlNode("Clients", clientsNode)); return new MiniYaml("", root) diff --git a/OpenRA.Game/Network/LocalizedMessage.cs b/OpenRA.Game/Network/LocalizedMessage.cs index 73585c9415..09621e2e8d 100644 --- a/OpenRA.Game/Network/LocalizedMessage.cs +++ b/OpenRA.Game/Network/LocalizedMessage.cs @@ -108,10 +108,9 @@ namespace OpenRA.Network if (arguments != null) { - var argumentsNode = new MiniYaml(""); - var i = 0; - foreach (var argument in arguments.Select(a => new FluentArgument(a.Key, a.Value))) - argumentsNode.Nodes.Add(new MiniYamlNode("Argument@" + i++, FieldSaver.Save(argument))); + var argumentsNode = new MiniYaml("", arguments + .Select(a => new FluentArgument(a.Key, a.Value)) + .Select((argument, i) => new MiniYamlNode("Argument@" + i, FieldSaver.Save(argument)))); root.Add(new MiniYamlNode("Arguments", argumentsNode)); } diff --git a/OpenRA.Game/Network/Session.cs b/OpenRA.Game/Network/Session.cs index 8e30a6b265..1c7b21b293 100644 --- a/OpenRA.Game/Network/Session.cs +++ b/OpenRA.Game/Network/Session.cs @@ -238,8 +238,9 @@ namespace OpenRA.Network public MiniYamlNode Serialize() { var data = new MiniYamlNode("GlobalSettings", FieldSaver.Save(this)); - var options = LobbyOptions.Select(kv => new MiniYamlNode(kv.Key, FieldSaver.Save(kv.Value))).ToList(); - data.Value.Nodes.Add(new MiniYamlNode("Options", new MiniYaml(null, options))); + var options = LobbyOptions.Select(kv => new MiniYamlNode(kv.Key, FieldSaver.Save(kv.Value))); + data = data.WithValue(data.Value.WithNodesAppended( + new[] { new MiniYamlNode("Options", new MiniYaml(null, options)) })); return data; } diff --git a/OpenRA.Game/Settings.cs b/OpenRA.Game/Settings.cs index 4abdcb7335..85c299d23d 100644 --- a/OpenRA.Game/Settings.cs +++ b/OpenRA.Game/Settings.cs @@ -361,13 +361,14 @@ namespace OpenRA public void Save() { + var yamlCacheBuilder = yamlCache.Select(n => new MiniYamlNodeBuilder(n)).ToList(); foreach (var kv in Sections) { - var sectionYaml = yamlCache.FirstOrDefault(x => x.Key == kv.Key); + var sectionYaml = yamlCacheBuilder.FirstOrDefault(x => x.Key == kv.Key); if (sectionYaml == null) { - sectionYaml = new MiniYamlNode(kv.Key, new MiniYaml("")); - yamlCache.Add(sectionYaml); + sectionYaml = new MiniYamlNodeBuilder(kv.Key, new MiniYamlBuilder("")); + yamlCacheBuilder.Add(sectionYaml); } var defaultValues = Activator.CreateInstance(kv.Value.GetType()); @@ -388,23 +389,25 @@ namespace OpenRA if (fieldYaml != null) fieldYaml.Value.Value = serialized; else - sectionYaml.Value.Nodes.Add(new MiniYamlNode(fli.YamlName, new MiniYaml(serialized))); + sectionYaml.Value.Nodes.Add(new MiniYamlNodeBuilder(fli.YamlName, new MiniYamlBuilder(serialized))); } } } - var keysYaml = yamlCache.FirstOrDefault(x => x.Key == "Keys"); + var keysYaml = yamlCacheBuilder.FirstOrDefault(x => x.Key == "Keys"); if (keysYaml == null) { - keysYaml = new MiniYamlNode("Keys", new MiniYaml("")); - yamlCache.Add(keysYaml); + keysYaml = new MiniYamlNodeBuilder("Keys", new MiniYamlBuilder("")); + yamlCacheBuilder.Add(keysYaml); } keysYaml.Value.Nodes.Clear(); foreach (var kv in Keys) - keysYaml.Value.Nodes.Add(new MiniYamlNode(kv.Key, FieldSaver.FormatValue(kv.Value))); + keysYaml.Value.Nodes.Add(new MiniYamlNodeBuilder(kv.Key, FieldSaver.FormatValue(kv.Value))); - yamlCache.WriteToFile(settingsFile); + yamlCacheBuilder.WriteToFile(settingsFile); + yamlCache.Clear(); + yamlCache.AddRange(yamlCacheBuilder.Select(n => n.Build())); } static string SanitizedName(string dirty) diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index 02514d7591..e103625250 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -349,7 +350,7 @@ namespace OpenRA.Traits public interface IGameSaveTraitData { List IssueTraitData(Actor self); - void ResolveTraitData(Actor self, List data); + void ResolveTraitData(Actor self, ImmutableArray data); } [RequireExplicitImplementation] diff --git a/OpenRA.Mods.Cnc/UtilityCommands/ImportGen1MapCommand.cs b/OpenRA.Mods.Cnc/UtilityCommands/ImportGen1MapCommand.cs index eafb9ceb57..7b1b45670a 100644 --- a/OpenRA.Mods.Cnc/UtilityCommands/ImportGen1MapCommand.cs +++ b/OpenRA.Mods.Cnc/UtilityCommands/ImportGen1MapCommand.cs @@ -128,6 +128,26 @@ namespace OpenRA.Mods.Cnc.UtilityCommands public abstract void ValidateMapFormat(int format); + protected MiniYamlNodeBuilder GetWorldNodeBuilderFromRules() + { + var worldNode = Map.RuleDefinitions.Nodes.FirstOrDefault(n => n.Key == "World"); + var worldNodeBuilder = worldNode != null + ? new MiniYamlNodeBuilder(worldNode) + : new MiniYamlNodeBuilder("World", new MiniYamlBuilder("", new List())); + return worldNodeBuilder; + } + + protected void SaveUpdatedWorldNodeToRules(MiniYamlNodeBuilder worldNodeBuilder) + { + var nodes = Map.RuleDefinitions.Nodes.ToList(); + var worldNodeIndex = nodes.FindIndex(n => n.Key == "World"); + if (worldNodeIndex != -1) + nodes[worldNodeIndex] = worldNodeBuilder.Build(); + else + nodes.Add(worldNodeBuilder.Build()); + Map.RuleDefinitions = Map.RuleDefinitions.WithNodes(nodes); + } + void LoadBriefing(IniFile file) { var briefingSection = file.GetSection("Briefing", true); @@ -144,21 +164,18 @@ namespace OpenRA.Mods.Cnc.UtilityCommands if (briefing.Length == 0) return; - var worldNode = Map.RuleDefinitions.Nodes.FirstOrDefault(n => n.Key == "World"); - if (worldNode == null) - { - worldNode = new MiniYamlNode("World", new MiniYaml("", new List())); - Map.RuleDefinitions.Nodes.Add(worldNode); - } + var worldNodeBuilder = GetWorldNodeBuilderFromRules(); - var missionData = worldNode.Value.Nodes.FirstOrDefault(n => n.Key == "MissionData"); + var missionData = worldNodeBuilder.Value.Nodes.FirstOrDefault(n => n.Key == "MissionData"); if (missionData == null) { - missionData = new MiniYamlNode("MissionData", new MiniYaml("", new List())); - worldNode.Value.Nodes.Add(missionData); + missionData = new MiniYamlNodeBuilder("MissionData", new MiniYamlBuilder("", new List())); + worldNodeBuilder.Value.Nodes.Add(missionData); } - missionData.Value.Nodes.Add(new MiniYamlNode("Briefing", briefing.Replace("\n", " ").ToString())); + missionData.Value.Nodes.Add(new MiniYamlNodeBuilder("Briefing", briefing.Replace("\n", " ").ToString())); + + SaveUpdatedWorldNodeToRules(worldNodeBuilder); } static void ReplaceInvalidTerrainTiles(Map map) @@ -190,7 +207,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands void LoadVideos(IniFile file, string section) { - var videos = new List(); + var videos = new List(); foreach (var s in file.GetSection(section)) { if (s.Value != "x" && s.Value != "X" && s.Value != "") @@ -198,19 +215,19 @@ namespace OpenRA.Mods.Cnc.UtilityCommands switch (s.Key) { case "Intro": - videos.Add(new MiniYamlNode("BackgroundVideo", s.Value.ToLowerInvariant() + ".vqa")); + videos.Add(new MiniYamlNodeBuilder("BackgroundVideo", s.Value.ToLowerInvariant() + ".vqa")); break; case "Brief": - videos.Add(new MiniYamlNode("BriefingVideo", s.Value.ToLowerInvariant() + ".vqa")); + videos.Add(new MiniYamlNodeBuilder("BriefingVideo", s.Value.ToLowerInvariant() + ".vqa")); break; case "Action": - videos.Add(new MiniYamlNode("StartVideo", s.Value.ToLowerInvariant() + ".vqa")); + videos.Add(new MiniYamlNodeBuilder("StartVideo", s.Value.ToLowerInvariant() + ".vqa")); break; case "Win": - videos.Add(new MiniYamlNode("WinVideo", s.Value.ToLowerInvariant() + ".vqa")); + videos.Add(new MiniYamlNodeBuilder("WinVideo", s.Value.ToLowerInvariant() + ".vqa")); break; case "Lose": - videos.Add(new MiniYamlNode("LossVideo", s.Value.ToLowerInvariant() + ".vqa")); + videos.Add(new MiniYamlNodeBuilder("LossVideo", s.Value.ToLowerInvariant() + ".vqa")); break; } } @@ -218,21 +235,18 @@ namespace OpenRA.Mods.Cnc.UtilityCommands if (videos.Count > 0) { - var worldNode = Map.RuleDefinitions.Nodes.FirstOrDefault(n => n.Key == "World"); - if (worldNode == null) - { - worldNode = new MiniYamlNode("World", new MiniYaml("", new List())); - Map.RuleDefinitions.Nodes.Add(worldNode); - } + var worldNodeBuilder = GetWorldNodeBuilderFromRules(); - var missionData = worldNode.Value.Nodes.FirstOrDefault(n => n.Key == "MissionData"); + var missionData = worldNodeBuilder.Value.Nodes.FirstOrDefault(n => n.Key == "MissionData"); if (missionData == null) { - missionData = new MiniYamlNode("MissionData", new MiniYaml("", new List())); - worldNode.Value.Nodes.Add(missionData); + missionData = new MiniYamlNodeBuilder("MissionData", new MiniYamlBuilder("", new List())); + worldNodeBuilder.Value.Nodes.Add(missionData); } missionData.Value.Nodes.AddRange(videos); + + SaveUpdatedWorldNodeToRules(worldNodeBuilder); } } @@ -264,13 +278,13 @@ namespace OpenRA.Mods.Cnc.UtilityCommands void LoadWaypoints(IniSection waypointSection) { - var actorCount = Map.ActorDefinitions.Count; var wps = waypointSection .Where(kv => Exts.ParseIntegerInvariant(kv.Value) > 0) .Select(kv => (WaypointNumber: Exts.ParseIntegerInvariant(kv.Key), Location: LocationFromMapOffset(Exts.ParseIntegerInvariant(kv.Value), MapSize))); // Add waypoint actors skipping duplicate entries + var nodes = new List(); foreach (var (waypointNumber, location) in wps.DistinctBy(location => location.Location)) { if (!singlePlayer && waypointNumber <= 7) @@ -281,7 +295,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands new OwnerInit("Neutral") }; - Map.ActorDefinitions.Add(new MiniYamlNode("Actor" + actorCount++, ar.Save())); + nodes.Add(new MiniYamlNode("Actor" + (Map.ActorDefinitions.Count + nodes.Count), ar.Save())); spawnCount++; } else @@ -292,15 +306,17 @@ namespace OpenRA.Mods.Cnc.UtilityCommands new OwnerInit("Neutral") }; - SaveWaypoint(waypointNumber, ar); + nodes.Add(SaveWaypoint(waypointNumber, ar)); } } + + Map.ActorDefinitions = Map.ActorDefinitions.Concat(nodes).ToArray(); } - public virtual void SaveWaypoint(int waypointNumber, ActorReference waypointReference) + public virtual MiniYamlNode SaveWaypoint(int waypointNumber, ActorReference waypointReference) { var waypointName = "waypoint" + waypointNumber; - Map.ActorDefinitions.Add(new MiniYamlNode(waypointName, waypointReference.Save())); + return new MiniYamlNode(waypointName, waypointReference.Save()); } void LoadSmudges(IniFile file, string section) @@ -322,25 +338,24 @@ namespace OpenRA.Mods.Cnc.UtilityCommands craters.Add(node); } - var worldNode = Map.RuleDefinitions.Nodes.FirstOrDefault(n => n.Key == "World"); - worldNode ??= new MiniYamlNode("World", new MiniYaml("", new List())); + var worldNodeBuilder = GetWorldNodeBuilderFromRules(); if (scorches.Count > 0) { var initialScorches = new MiniYamlNode("InitialSmudges", new MiniYaml("", scorches)); - var smudgeLayer = new MiniYamlNode("SmudgeLayer@SCORCH", new MiniYaml("", new List() { initialScorches })); - worldNode.Value.Nodes.Add(smudgeLayer); + var smudgeLayer = new MiniYamlNodeBuilder("SmudgeLayer@SCORCH", new MiniYamlBuilder("", new List() { initialScorches })); + worldNodeBuilder.Value.Nodes.Add(smudgeLayer); } if (craters.Count > 0) { var initialCraters = new MiniYamlNode("InitialSmudges", new MiniYaml("", craters)); - var smudgeLayer = new MiniYamlNode("SmudgeLayer@CRATER", new MiniYaml("", new List() { initialCraters })); - worldNode.Value.Nodes.Add(smudgeLayer); + var smudgeLayer = new MiniYamlNodeBuilder("SmudgeLayer@CRATER", new MiniYamlBuilder("", new List() { initialCraters })); + worldNodeBuilder.Value.Nodes.Add(smudgeLayer); } - if (worldNode.Value.Nodes.Count > 0 && !Map.RuleDefinitions.Nodes.Contains(worldNode)) - Map.RuleDefinitions.Nodes.Add(worldNode); + if (worldNodeBuilder.Value.Nodes.Count > 0) + SaveUpdatedWorldNodeToRules(worldNodeBuilder); } // TODO: fix this -- will have bitrotted pretty badly. @@ -395,6 +410,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands public void LoadActors(IniFile file, string section, List players, Map map) { + var nodes = new List(); foreach (var s in file.GetSection(section, true)) { // Structures: num=owner,type,health,location,turret-facing,trigger @@ -429,18 +445,18 @@ namespace OpenRA.Mods.Cnc.UtilityCommands if (section == "INFANTRY") actor.Add(new SubCellInit((SubCell)Exts.ParseByte(parts[4]))); - var actorCount = map.ActorDefinitions.Count; - if (!map.Rules.Actors.ContainsKey(parts[1].ToLowerInvariant())) Console.WriteLine($"Ignoring unknown actor type: `{parts[1].ToLowerInvariant()}`"); else - map.ActorDefinitions.Add(new MiniYamlNode("Actor" + actorCount++, actor.Save())); + nodes.Add(new MiniYamlNode("Actor" + (map.ActorDefinitions.Count + nodes.Count), actor.Save())); } catch (Exception) { Console.WriteLine($"Malformed actor definition: `{s}`"); } } + + map.ActorDefinitions = map.ActorDefinitions.Concat(nodes).ToArray(); } public abstract string ParseTreeActor(string input); @@ -451,6 +467,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands if (terrain == null) return; + var nodes = new List(); foreach (var kv in terrain) { var loc = Exts.ParseIntegerInvariant(kv.Key); @@ -462,9 +479,10 @@ namespace OpenRA.Mods.Cnc.UtilityCommands new OwnerInit("Neutral") }; - var actorCount = Map.ActorDefinitions.Count; - Map.ActorDefinitions.Add(new MiniYamlNode("Actor" + actorCount++, ar.Save())); + nodes.Add(new MiniYamlNode("Actor" + (Map.ActorDefinitions.Count + nodes.Count), ar.Save())); } + + Map.ActorDefinitions = Map.ActorDefinitions.Concat(nodes).ToArray(); } } diff --git a/OpenRA.Mods.Cnc/UtilityCommands/ImportGen2MapCommand.cs b/OpenRA.Mods.Cnc/UtilityCommands/ImportGen2MapCommand.cs index 2f99bdf82f..9e6ac5290d 100644 --- a/OpenRA.Mods.Cnc/UtilityCommands/ImportGen2MapCommand.cs +++ b/OpenRA.Mods.Cnc/UtilityCommands/ImportGen2MapCommand.cs @@ -171,6 +171,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands } } + var nodes = new List(); foreach (var cell in map.AllCells) { var overlayType = overlayPack[overlayIndex[cell]]; @@ -180,7 +181,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands if (TryHandleOverlayToActorInner(cell, overlayPack, overlayIndex, overlayType, out var ar)) { if (ar != null) - map.ActorDefinitions.Add(new MiniYamlNode("Actor" + map.ActorDefinitions.Count, ar.Save())); + nodes.Add(new MiniYamlNode("Actor" + (map.ActorDefinitions.Count + nodes.Count), ar.Save())); continue; } @@ -196,10 +197,13 @@ namespace OpenRA.Mods.Cnc.UtilityCommands Console.WriteLine($"Cell {cell}: unknown overlay {overlayType}"); } + + map.ActorDefinitions = map.ActorDefinitions.Concat(nodes).ToArray(); } protected virtual void ReadWaypoints(Map map, IniFile file, int2 fullSize) { + var nodes = new List(); var waypointsSection = file.GetSection("Waypoints", true); foreach (var kv in waypointsSection) { @@ -214,12 +218,15 @@ namespace OpenRA.Mods.Cnc.UtilityCommands new OwnerInit("Neutral") }; - map.ActorDefinitions.Add(new MiniYamlNode("Actor" + map.ActorDefinitions.Count, ar.Save())); + nodes.Add(new MiniYamlNode("Actor" + (map.ActorDefinitions.Count + nodes.Count), ar.Save())); } + + map.ActorDefinitions = map.ActorDefinitions.Concat(nodes).ToArray(); } protected virtual void ReadTerrainActors(Map map, IniFile file, int2 fullSize) { + var nodes = new List(); var terrainSection = file.GetSection("Terrain", true); foreach (var kv in terrainSection) { @@ -238,12 +245,15 @@ namespace OpenRA.Mods.Cnc.UtilityCommands if (!map.Rules.Actors.ContainsKey(name)) Console.WriteLine($"Ignoring unknown actor type: `{name}`"); else - map.ActorDefinitions.Add(new MiniYamlNode("Actor" + map.ActorDefinitions.Count, ar.Save())); + nodes.Add(new MiniYamlNode("Actor" + (map.ActorDefinitions.Count + nodes.Count), ar.Save())); } + + map.ActorDefinitions = map.ActorDefinitions.Concat(nodes).ToArray(); } protected virtual void ReadActors(Map map, IniFile file, string type, int2 fullSize) { + var nodes = new List(); var structuresSection = file.GetSection(type, true); foreach (var kv in structuresSection) { @@ -296,8 +306,10 @@ namespace OpenRA.Mods.Cnc.UtilityCommands if (!map.Rules.Actors.ContainsKey(name)) Console.WriteLine($"Ignoring unknown actor type: `{name}`"); else - map.ActorDefinitions.Add(new MiniYamlNode("Actor" + map.ActorDefinitions.Count, ar.Save())); + nodes.Add(new MiniYamlNode("Actor" + (map.ActorDefinitions.Count + nodes.Count), ar.Save())); } + + map.ActorDefinitions = map.ActorDefinitions.Concat(nodes).ToArray(); } protected virtual void ReadLighting(Map map, IniFile file) @@ -340,10 +352,13 @@ namespace OpenRA.Mods.Cnc.UtilityCommands if (lightingNodes.Count > 0) { - map.RuleDefinitions.Nodes.Add(new MiniYamlNode("^BaseWorld", new MiniYaml("", new List() + map.RuleDefinitions = map.RuleDefinitions.WithNodesAppended(new[] { - new MiniYamlNode("TerrainLighting", new MiniYaml("", lightingNodes)) - }))); + new MiniYamlNode("^BaseWorld", new MiniYaml("", new[] + { + new MiniYamlNode("TerrainLighting", new MiniYaml("", lightingNodes)) + })) + }); } } @@ -357,6 +372,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands { "LightBlueTint", "BlueTint" }, }; + var nodes = new List(); foreach (var lamp in LampActors) { var lightingSection = file.GetSection(lamp, true); @@ -380,12 +396,14 @@ namespace OpenRA.Mods.Cnc.UtilityCommands if (lightingNodes.Count > 0) { - map.RuleDefinitions.Nodes.Add(new MiniYamlNode(lamp, new MiniYaml("", new List() + nodes.Add(new MiniYamlNode(lamp, new MiniYaml("", new[] { new MiniYamlNode("TerrainLightSource", new MiniYaml("", lightingNodes)) }))); } } + + map.RuleDefinitions = map.RuleDefinitions.WithNodesAppended(nodes); } protected virtual void SetInteractableBounds(Map map, int[] iniBounds) diff --git a/OpenRA.Mods.Cnc/UtilityCommands/ImportRedAlertMapCommand.cs b/OpenRA.Mods.Cnc/UtilityCommands/ImportRedAlertMapCommand.cs index 0b7b09d2b5..f415123274 100644 --- a/OpenRA.Mods.Cnc/UtilityCommands/ImportRedAlertMapCommand.cs +++ b/OpenRA.Mods.Cnc/UtilityCommands/ImportRedAlertMapCommand.cs @@ -94,6 +94,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands void UnpackOverlayData(MemoryStream ms) { + var nodes = new List(); for (var j = 0; j < MapSize; j++) { for (var i = 0; i < MapSize; i++) @@ -115,11 +116,12 @@ namespace OpenRA.Mods.Cnc.UtilityCommands new OwnerInit("Neutral") }; - var actorCount = Map.ActorDefinitions.Count; - Map.ActorDefinitions.Add(new MiniYamlNode("Actor" + actorCount++, ar.Save())); + nodes.Add(new MiniYamlNode("Actor" + (Map.ActorDefinitions.Count + nodes.Count), ar.Save())); } } } + + Map.ActorDefinitions = Map.ActorDefinitions.Concat(nodes).ToArray(); } public override string ParseTreeActor(string input) @@ -241,12 +243,12 @@ namespace OpenRA.Mods.Cnc.UtilityCommands LoadActors(file, "SHIPS", Players, Map); } - public override void SaveWaypoint(int waypointNumber, ActorReference waypointReference) + public override MiniYamlNode SaveWaypoint(int waypointNumber, ActorReference waypointReference) { var waypointName = "waypoint" + waypointNumber; if (waypointNumber == 98) waypointName = "DefaultCameraPosition"; - Map.ActorDefinitions.Add(new MiniYamlNode(waypointName, waypointReference.Save())); + return new MiniYamlNode(waypointName, waypointReference.Save()); } } } diff --git a/OpenRA.Mods.Cnc/UtilityCommands/ImportTiberianDawnMapCommand.cs b/OpenRA.Mods.Cnc/UtilityCommands/ImportTiberianDawnMapCommand.cs index 51d6d41f28..c018b77e56 100644 --- a/OpenRA.Mods.Cnc/UtilityCommands/ImportTiberianDawnMapCommand.cs +++ b/OpenRA.Mods.Cnc/UtilityCommands/ImportTiberianDawnMapCommand.cs @@ -86,6 +86,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands if (overlay == null) return; + var nodes = new List(); foreach (var kv in overlay) { var loc = Exts.ParseIntegerInvariant(kv.Key); @@ -105,10 +106,11 @@ namespace OpenRA.Mods.Cnc.UtilityCommands new OwnerInit("Neutral") }; - var actorCount = Map.ActorDefinitions.Count; - Map.ActorDefinitions.Add(new MiniYamlNode("Actor" + actorCount++, ar.Save())); + nodes.Add(new MiniYamlNode("Actor" + (Map.ActorDefinitions.Count + nodes.Count), ar.Save())); } } + + Map.ActorDefinitions = Map.ActorDefinitions.Concat(nodes).ToArray(); } public override string ParseTreeActor(string input) @@ -170,7 +172,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands ReadOverlay(file); } - public override void SaveWaypoint(int waypointNumber, ActorReference waypointReference) + public override MiniYamlNode SaveWaypoint(int waypointNumber, ActorReference waypointReference) { var waypointName = "waypoint" + waypointNumber; if (waypointNumber == 25) @@ -179,7 +181,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands waypointName = "DefaultCameraPosition"; else if (waypointNumber == 27) waypointName = "DefaultChinookTarget"; - Map.ActorDefinitions.Add(new MiniYamlNode(waypointName, waypointReference.Save())); + return new MiniYamlNode(waypointName, waypointReference.Save()); } } } diff --git a/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs b/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs index 66d2756259..0f70ece2ff 100644 --- a/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs +++ b/OpenRA.Mods.Common/Graphics/DefaultSpriteSequence.cs @@ -49,7 +49,7 @@ namespace OpenRA.Mods.Common.Graphics var sequences = new Dictionary(); var node = imageNode.Value.Nodes.SingleOrDefault(n => n.Key == "Defaults"); var defaults = node?.Value ?? NoData; - imageNode.Value.Nodes.Remove(node); + imageNode = imageNode.WithValue(imageNode.Value.WithNodes(imageNode.Value.Nodes.Remove(node))); foreach (var sequenceNode in imageNode.Value.Nodes) { @@ -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.Find(n => n.Key == key) ?? defaults?.Nodes.Find(n => n.Key == key); + var node = data.Nodes.FirstOrDefault(n => n.Key == key) ?? defaults?.Nodes.FirstOrDefault(n => n.Key == 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.Find(n => n.Key == field.Key) ?? defaults?.Nodes.Find(n => n.Key == field.Key); + var node = data.Nodes.FirstOrDefault(n => n.Key == field.Key) ?? defaults?.Nodes.FirstOrDefault(n => n.Key == field.Key); if (node == null) { location = default; @@ -414,10 +414,10 @@ namespace OpenRA.Mods.Common.Graphics var offset = LoadField(Offset, data, defaults); var blendMode = LoadField(BlendMode, data, defaults); - var combineNode = data.Nodes.Find(n => n.Key == Combine.Key); + var combineNode = data.Nodes.FirstOrDefault(n => n.Key == Combine.Key); if (combineNode != null) { - for (var i = 0; i < combineNode.Value.Nodes.Count; i++) + for (var i = 0; i < combineNode.Value.Nodes.Length; i++) { var subData = combineNode.Value.Nodes[i].Value; var subOffset = LoadField(Offset, subData, NoData); diff --git a/OpenRA.Mods.Common/Lint/CheckChromeHotkeys.cs b/OpenRA.Mods.Common/Lint/CheckChromeHotkeys.cs index 6071caeeea..863475c2e1 100644 --- a/OpenRA.Mods.Common/Lint/CheckChromeHotkeys.cs +++ b/OpenRA.Mods.Common/Lint/CheckChromeHotkeys.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using OpenRA.Widgets; @@ -64,7 +65,7 @@ namespace OpenRA.Mods.Common.Lint } void CheckInner(ModData modData, string[] namedKeys, (string Widget, string Field)[] checkWidgetFields, Dictionary> customLintMethods, - List nodes, string filename, MiniYamlNode parent, Action emitError) + IEnumerable nodes, string filename, MiniYamlNode parent, Action emitError) { foreach (var node in nodes) { @@ -94,7 +95,7 @@ namespace OpenRA.Mods.Common.Lint } // Logic classes can declare the data key names that specify hotkeys. - if (node.Key == "Logic" && node.Value.Nodes.Count > 0) + if (node.Key == "Logic" && node.Value.Nodes.Length > 0) { var typeNames = FieldLoader.GetValue(node.Key, node.Value.Value); var checkArgKeys = new List(); diff --git a/OpenRA.Mods.Common/Lint/CheckChromeIntegerExpressions.cs b/OpenRA.Mods.Common/Lint/CheckChromeIntegerExpressions.cs index 26da497eeb..e170ec242d 100644 --- a/OpenRA.Mods.Common/Lint/CheckChromeIntegerExpressions.cs +++ b/OpenRA.Mods.Common/Lint/CheckChromeIntegerExpressions.cs @@ -24,7 +24,7 @@ namespace OpenRA.Mods.Common.Lint CheckInner(MiniYaml.FromStream(modData.DefaultFileSystem.Open(filename), filename), filename, emitError); } - void CheckInner(List nodes, string filename, Action emitError) + void CheckInner(IEnumerable nodes, string filename, Action emitError) { var substitutions = new Dictionary(); var readOnlySubstitutions = new ReadOnlyDictionary(substitutions); diff --git a/OpenRA.Mods.Common/Lint/CheckChromeLogic.cs b/OpenRA.Mods.Common/Lint/CheckChromeLogic.cs index 02ab41a6af..8700286b96 100644 --- a/OpenRA.Mods.Common/Lint/CheckChromeLogic.cs +++ b/OpenRA.Mods.Common/Lint/CheckChromeLogic.cs @@ -23,7 +23,7 @@ namespace OpenRA.Mods.Common.Lint CheckInner(MiniYaml.FromStream(modData.DefaultFileSystem.Open(filename), filename), filename, emitError); } - void CheckInner(List nodes, string filename, Action emitError) + void CheckInner(IEnumerable nodes, string filename, Action emitError) { foreach (var node in nodes) { diff --git a/OpenRA.Mods.Common/Lint/CheckUnknownTraitFields.cs b/OpenRA.Mods.Common/Lint/CheckUnknownTraitFields.cs index e488ea3fd4..2615a22bb6 100644 --- a/OpenRA.Mods.Common/Lint/CheckUnknownTraitFields.cs +++ b/OpenRA.Mods.Common/Lint/CheckUnknownTraitFields.cs @@ -52,7 +52,7 @@ namespace OpenRA.Mods.Common.Lint // Removals can never define children or values. if (t.Key.StartsWith("-", StringComparison.Ordinal)) { - if (t.Value.Nodes.Count > 0) + if (t.Value.Nodes.Length > 0) emitError($"{t.Location} `{t.Key}` defines child nodes, which are not valid for removals."); if (!string.IsNullOrEmpty(t.Value.Value)) @@ -66,7 +66,7 @@ namespace OpenRA.Mods.Common.Lint // Inherits can never define children. if (traitName == "Inherits") { - if (t.Value.Nodes.Count > 0) + if (t.Value.Nodes.Length > 0) emitError($"{t.Location} defines child nodes, which are not valid for Inherits."); continue; @@ -98,7 +98,7 @@ namespace OpenRA.Mods.Common.Lint foreach (var f in mapFiles) CheckActors(MiniYaml.FromStream(fileSystem.Open(f), f), emitError, modData); - if (ruleDefinitions.Nodes.Count > 0) + if (ruleDefinitions.Nodes.Length > 0) CheckActors(ruleDefinitions.Nodes, emitError, modData); } } diff --git a/OpenRA.Mods.Common/Lint/CheckUnknownWeaponFields.cs b/OpenRA.Mods.Common/Lint/CheckUnknownWeaponFields.cs index 671b470b61..52a4068421 100644 --- a/OpenRA.Mods.Common/Lint/CheckUnknownWeaponFields.cs +++ b/OpenRA.Mods.Common/Lint/CheckUnknownWeaponFields.cs @@ -54,7 +54,7 @@ namespace OpenRA.Mods.Common.Lint // Removals can never define children or values if (field.Key.StartsWith("-", StringComparison.Ordinal)) { - if (field.Value.Nodes.Count > 0) + if (field.Value.Nodes.Length > 0) emitError($"{field.Location} `{field.Key}` defines child nodes, which is not valid for removals."); if (!string.IsNullOrEmpty(field.Value.Value)) @@ -119,7 +119,7 @@ namespace OpenRA.Mods.Common.Lint foreach (var f in mapFiles) CheckWeapons(MiniYaml.FromStream(fileSystem.Open(f), f), emitError, emitWarning, modData); - if (weaponDefinitions.Nodes.Count > 0) + if (weaponDefinitions.Nodes.Length > 0) CheckWeapons(weaponDefinitions.Nodes, emitError, emitWarning, modData); } } diff --git a/OpenRA.Mods.Common/Lint/CheckWorldAndPlayerInherits.cs b/OpenRA.Mods.Common/Lint/CheckWorldAndPlayerInherits.cs index 77acaf9d78..0bb9ee0f02 100644 --- a/OpenRA.Mods.Common/Lint/CheckWorldAndPlayerInherits.cs +++ b/OpenRA.Mods.Common/Lint/CheckWorldAndPlayerInherits.cs @@ -66,7 +66,7 @@ namespace OpenRA.Mods.Common.Lint foreach (var actorNode in nodes) { var inherits = inheritsMap.GetOrAdd(actorNode.Key, _ => new List()); - foreach (var inheritsNode in actorNode.ChildrenMatching("Inherits")) + foreach (var inheritsNode in new MiniYamlNodeBuilder(actorNode).ChildrenMatching("Inherits")) inherits.Add(inheritsNode.Value.Value); } diff --git a/OpenRA.Mods.Common/ModContent.cs b/OpenRA.Mods.Common/ModContent.cs index 2c845588c2..f5358b6397 100644 --- a/OpenRA.Mods.Common/ModContent.cs +++ b/OpenRA.Mods.Common/ModContent.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; using System.Linq; @@ -56,7 +57,7 @@ namespace OpenRA public readonly MiniYaml IDFiles; [FieldLoader.Ignore] - public readonly List Install; + public readonly ImmutableArray Install; public readonly string TooltipText; diff --git a/OpenRA.Mods.Common/Terrain/TerrainInfo.cs b/OpenRA.Mods.Common/Terrain/TerrainInfo.cs index 0b40346b45..7fcb834482 100644 --- a/OpenRA.Mods.Common/Terrain/TerrainInfo.cs +++ b/OpenRA.Mods.Common/Terrain/TerrainInfo.cs @@ -55,7 +55,7 @@ namespace OpenRA.Mods.Common.Terrain } else { - tileInfo = new TerrainTileInfo[nodes.Count]; + tileInfo = new TerrainTileInfo[nodes.Length]; var i = 0; foreach (var node in nodes) diff --git a/OpenRA.Mods.Common/Traits/BotModules/BaseBuilderBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/BaseBuilderBotModule.cs index eca8bb50c1..09776a5e22 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/BaseBuilderBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/BaseBuilderBotModule.cs @@ -10,6 +10,7 @@ #endregion using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using OpenRA.Traits; @@ -275,7 +276,7 @@ namespace OpenRA.Mods.Common.Traits }; } - void IGameSaveTraitData.ResolveTraitData(Actor self, List data) + void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray data) { if (self.World.IsReplay) return; diff --git a/OpenRA.Mods.Common/Traits/BotModules/McvManagerBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/McvManagerBotModule.cs index 8c4f4c225d..f394abc746 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/McvManagerBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/McvManagerBotModule.cs @@ -10,6 +10,7 @@ #endregion using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using OpenRA.Traits; @@ -211,7 +212,7 @@ namespace OpenRA.Mods.Common.Traits }; } - void IGameSaveTraitData.ResolveTraitData(Actor self, List data) + void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray data) { if (self.World.IsReplay) return; diff --git a/OpenRA.Mods.Common/Traits/BotModules/SquadManagerBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/SquadManagerBotModule.cs index 855a4c6d99..03cf48e29a 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/SquadManagerBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/SquadManagerBotModule.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using OpenRA.Mods.Common.Traits.BotModules.Squads; using OpenRA.Primitives; @@ -407,7 +408,7 @@ namespace OpenRA.Mods.Common.Traits }; } - void IGameSaveTraitData.ResolveTraitData(Actor self, List data) + void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray data) { if (self.World.IsReplay) return; diff --git a/OpenRA.Mods.Common/Traits/BotModules/Squads/Squad.cs b/OpenRA.Mods.Common/Traits/BotModules/Squads/Squad.cs index d6ec65fed7..fa7f55b701 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/Squads/Squad.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/Squads/Squad.cs @@ -84,16 +84,15 @@ namespace OpenRA.Mods.Common.Traits.BotModules.Squads public MiniYaml Serialize() { - var nodes = new MiniYaml("", new List() + var nodes = new List() { new MiniYamlNode("Type", FieldSaver.FormatValue(Type)), new MiniYamlNode("Units", FieldSaver.FormatValue(Units.Select(a => a.ActorID).ToArray())), - }); - + }; if (Target.Type == TargetType.Actor) - nodes.Nodes.Add(new MiniYamlNode("Target", FieldSaver.FormatValue(Target.Actor.ActorID))); + nodes.Add(new MiniYamlNode("Target", FieldSaver.FormatValue(Target.Actor.ActorID))); - return nodes; + return new MiniYaml("", nodes); } public static Squad Deserialize(IBot bot, SquadManagerBotModule squadManager, MiniYaml yaml) diff --git a/OpenRA.Mods.Common/Traits/BotModules/SupportPowerBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/SupportPowerBotModule.cs index f266945429..01c1f40af2 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/SupportPowerBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/SupportPowerBotModule.cs @@ -10,6 +10,7 @@ #endregion using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using OpenRA.Traits; @@ -222,7 +223,7 @@ namespace OpenRA.Mods.Common.Traits }; } - void IGameSaveTraitData.ResolveTraitData(Actor self, List data) + void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray data) { if (self.World.IsReplay) return; diff --git a/OpenRA.Mods.Common/Traits/BotModules/UnitBuilderBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/UnitBuilderBotModule.cs index 19e25851da..a1a2d62ad4 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/UnitBuilderBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/UnitBuilderBotModule.cs @@ -10,6 +10,7 @@ #endregion using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using OpenRA.Traits; @@ -221,7 +222,7 @@ namespace OpenRA.Mods.Common.Traits }; } - void IGameSaveTraitData.ResolveTraitData(Actor self, List data) + void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray data) { if (self.World.IsReplay) return; diff --git a/OpenRA.Mods.Common/Traits/Player/GameSaveViewportManager.cs b/OpenRA.Mods.Common/Traits/Player/GameSaveViewportManager.cs index 7a1fb7de45..7f01f56347 100644 --- a/OpenRA.Mods.Common/Traits/Player/GameSaveViewportManager.cs +++ b/OpenRA.Mods.Common/Traits/Player/GameSaveViewportManager.cs @@ -10,6 +10,7 @@ #endregion using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using OpenRA.Graphics; using OpenRA.Traits; @@ -49,7 +50,7 @@ namespace OpenRA.Mods.Common.Traits return nodes; } - void IGameSaveTraitData.ResolveTraitData(Actor self, List data) + void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray data) { var viewportNode = data.FirstOrDefault(n => n.Key == "Viewport"); if (viewportNode != null) diff --git a/OpenRA.Mods.Common/Traits/World/ControlGroups.cs b/OpenRA.Mods.Common/Traits/World/ControlGroups.cs index 1c531abcf5..1752177fba 100644 --- a/OpenRA.Mods.Common/Traits/World/ControlGroups.cs +++ b/OpenRA.Mods.Common/Traits/World/ControlGroups.cs @@ -10,6 +10,7 @@ #endregion using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using OpenRA.Traits; @@ -132,7 +133,7 @@ namespace OpenRA.Mods.Common.Traits }; } - void IGameSaveTraitData.ResolveTraitData(Actor self, List data) + void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray data) { var groupsNode = data.FirstOrDefault(n => n.Key == "Groups"); if (groupsNode != null) diff --git a/OpenRA.Mods.Common/Traits/World/Locomotor.cs b/OpenRA.Mods.Common/Traits/World/Locomotor.cs index a6018f4e4e..fa52f55693 100644 --- a/OpenRA.Mods.Common/Traits/World/Locomotor.cs +++ b/OpenRA.Mods.Common/Traits/World/Locomotor.cs @@ -85,7 +85,7 @@ namespace OpenRA.Mods.Common.Traits protected static object LoadSpeeds(MiniYaml y) { var speeds = y.ToDictionary()["TerrainSpeeds"].Nodes; - var ret = new Dictionary(speeds.Count); + var ret = new Dictionary(speeds.Length); foreach (var t in speeds) { var speed = FieldLoader.GetValue("speed", t.Value.Value); diff --git a/OpenRA.Mods.Common/Traits/World/Selection.cs b/OpenRA.Mods.Common/Traits/World/Selection.cs index ddc8f212f9..17ea008e4c 100644 --- a/OpenRA.Mods.Common/Traits/World/Selection.cs +++ b/OpenRA.Mods.Common/Traits/World/Selection.cs @@ -10,6 +10,7 @@ #endregion using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using OpenRA.Traits; @@ -182,7 +183,7 @@ namespace OpenRA.Mods.Common.Traits }; } - void IGameSaveTraitData.ResolveTraitData(Actor self, List data) + void IGameSaveTraitData.ResolveTraitData(Actor self, ImmutableArray data) { var selectionNode = data.FirstOrDefault(n => n.Key == "Selection"); if (selectionNode != null) diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/AddPipDecorationTraits.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/AddPipDecorationTraits.cs index 5bd6b0c73a..7935f2b050 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/AddPipDecorationTraits.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/AddPipDecorationTraits.cs @@ -72,9 +72,9 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules harvesterPipLocations.Clear(); } - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { - var addNodes = new List(); + var addNodes = new List(); foreach (var selectionDecorations in actorNode.ChildrenMatching("SelectionDecorations")) { @@ -84,7 +84,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules foreach (var ammoPool in actorNode.ChildrenMatching("AmmoPool")) { - var ammoPips = new MiniYamlNode("WithAmmoPipsDecoration", ""); + var ammoPips = new MiniYamlNodeBuilder("WithAmmoPipsDecoration", ""); ammoPips.AddNode("Position", "BottomLeft"); ammoPips.AddNode("RequiresSelection", "true"); @@ -95,7 +95,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules var pipCount = pipCountNode.NodeValue(); if (pipCount == 0) { - addNodes.Add(new MiniYamlNode("-" + ammoPips.Key, "")); + addNodes.Add(new MiniYamlNodeBuilder("-" + ammoPips.Key, "")); continue; } @@ -129,7 +129,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules foreach (var cargo in actorNode.ChildrenMatching("Cargo")) { - var cargoPips = new MiniYamlNode("WithCargoPipsDecoration", ""); + var cargoPips = new MiniYamlNodeBuilder("WithCargoPipsDecoration", ""); cargoPips.AddNode("Position", "BottomLeft"); cargoPips.AddNode("RequiresSelection", "true"); @@ -141,7 +141,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules var pipCount = pipCountNode.NodeValue(); if (pipCount == 0) { - addNodes.Add(new MiniYamlNode("-" + cargoPips.Key, "")); + addNodes.Add(new MiniYamlNodeBuilder("-" + cargoPips.Key, "")); continue; } @@ -171,7 +171,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules foreach (var harvester in actorNode.ChildrenMatching("Harvester")) { - var harvesterPips = new MiniYamlNode("WithHarvesterPipsDecoration", ""); + var harvesterPips = new MiniYamlNodeBuilder("WithHarvesterPipsDecoration", ""); harvesterPips.AddNode("Position", "BottomLeft"); harvesterPips.AddNode("RequiresSelection", "true"); @@ -189,7 +189,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules var pipCount = pipCountNode.NodeValue(); if (pipCount == 0) { - addNodes.Add(new MiniYamlNode("-" + harvesterPips.Key, "")); + addNodes.Add(new MiniYamlNodeBuilder("-" + harvesterPips.Key, "")); continue; } @@ -220,7 +220,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules foreach (var storesResources in actorNode.ChildrenMatching("StoresResources")) { - var storagePips = new MiniYamlNode("WithResourceStoragePipsDecoration", ""); + var storagePips = new MiniYamlNodeBuilder("WithResourceStoragePipsDecoration", ""); storagePips.AddNode("Position", "BottomLeft"); storagePips.AddNode("RequiresSelection", "true"); @@ -231,7 +231,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules var pipCount = pipCountNode.NodeValue(); if (pipCount == 0) { - addNodes.Add(new MiniYamlNode("-" + storagePips.Key, "")); + addNodes.Add(new MiniYamlNodeBuilder("-" + storagePips.Key, "")); continue; } diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ChangeTargetLineDelayToMilliseconds.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ChangeTargetLineDelayToMilliseconds.cs index 29348f10ef..cfd6dae5c4 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ChangeTargetLineDelayToMilliseconds.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ChangeTargetLineDelayToMilliseconds.cs @@ -21,7 +21,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules "Going forward, the value of the `Delay` attribute of the `DrawLineToTarget` trait will be\n" + "interpreted as milliseconds instead of ticks.\n"; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var dltt in actorNode.ChildrenMatching("DrawLineToTarget", includeRemovals: false)) { diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ConvertSupportPowerRangesToFootprint.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ConvertSupportPowerRangesToFootprint.cs index c77a808d87..ba5822785a 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ConvertSupportPowerRangesToFootprint.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ConvertSupportPowerRangesToFootprint.cs @@ -24,7 +24,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules static readonly string[] AffectedTraits = new string[] { "GrantExternalConditionPower", "ChronoshiftPower" }; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var at in AffectedTraits) foreach (var trait in actorNode.ChildrenMatching(at)) @@ -33,7 +33,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules yield break; } - void UpdatePower(MiniYamlNode power) + void UpdatePower(MiniYamlNodeBuilder power) { var range = 1; var rangeNode = power.LastChildMatching("Range"); @@ -47,7 +47,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules } var size = 2 * range + 1; - power.AddNode(new MiniYamlNode("Dimensions", size.ToString() + ", " + size.ToString())); + power.AddNode(new MiniYamlNodeBuilder("Dimensions", size.ToString() + ", " + size.ToString())); var footprint = string.Empty; @@ -79,7 +79,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules footprint += ' '; } - power.AddNode(new MiniYamlNode("Footprint", footprint)); + power.AddNode(new MiniYamlNodeBuilder("Footprint", footprint)); } readonly List locations = new(); diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/CreateFlashPaletteEffectWarhead.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/CreateFlashPaletteEffectWarhead.cs index d45dde3435..88c0529378 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/CreateFlashPaletteEffectWarhead.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/CreateFlashPaletteEffectWarhead.cs @@ -23,7 +23,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules readonly List> weaponsToUpdate = new(); - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { var nukePowerTraits = actorNode.ChildrenMatching("NukePower"); foreach (var nukePowerTrait in nukePowerTraits) diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ModernizeDecorationTraits.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ModernizeDecorationTraits.cs index 68c319e9ea..a31ab56ad8 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ModernizeDecorationTraits.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ModernizeDecorationTraits.cs @@ -63,7 +63,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules locations.Clear(); } - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { var locationKey = $"{actorNode.Key} ({actorNode.Location.Filename})"; diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/MoveClassicFacingFudge.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/MoveClassicFacingFudge.cs index aeb1014314..3d54d83fac 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/MoveClassicFacingFudge.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/MoveClassicFacingFudge.cs @@ -36,7 +36,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules locations.Clear(); } - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var bo in actorNode.ChildrenMatching("BodyOrientation")) { @@ -58,7 +58,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules yield break; } - public override IEnumerable UpdateSequenceNode(ModData modData, MiniYamlNode sequenceNode) + public override IEnumerable UpdateSequenceNode(ModData modData, MiniYamlNodeBuilder sequenceNode) { foreach (var sequence in sequenceNode.Value.Nodes) { diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RemoveConditionManager.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RemoveConditionManager.cs index 63e9fec758..490dbf559b 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RemoveConditionManager.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RemoveConditionManager.cs @@ -19,7 +19,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "ConditionManager trait has been removed. Its functionality has been integrated into the actor itself."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { actorNode.RemoveNodes("ConditionManager"); yield break; diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RemoveLaysTerrain.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RemoveLaysTerrain.cs index e7a67508da..aa5369a8b1 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RemoveLaysTerrain.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RemoveLaysTerrain.cs @@ -19,7 +19,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "'LaysTerrain' was removed."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { if (actorNode.RemoveNodes("LaysTerrain") > 0) yield return $"'LaysTerrain' was removed from {actorNode.Key} ({actorNode.Location.Filename}) without replacement.\n"; diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RemoveMuzzleSplitFacings.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RemoveMuzzleSplitFacings.cs index 455880239c..a6c9eb8bcf 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RemoveMuzzleSplitFacings.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RemoveMuzzleSplitFacings.cs @@ -22,7 +22,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules "The same result can be created by using `Combine` in the sequence definitions to\n" + "assemble the different facings sprites into a single sequence."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var a in actorNode.ChildrenMatching("Armament")) { diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RemoveTurnToDock.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RemoveTurnToDock.cs index bb6f0c443f..85d7739c4f 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RemoveTurnToDock.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RemoveTurnToDock.cs @@ -39,7 +39,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules turningAircraft.Clear(); } - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { var aircraft = actorNode.LastChildMatching("Aircraft"); if (aircraft != null) diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameCircleOutline.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameCircleOutline.cs index d75953e2a4..222b8a9999 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameCircleOutline.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameCircleOutline.cs @@ -19,7 +19,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "RenderDetectionCircle and RenderShroudCircle ContrastColor have been renamed to BorderColor for consistency."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var rdc in actorNode.ChildrenMatching("RenderDetectionCircle")) rdc.RenameChildrenMatching("ContrastColor", "BorderColor"); diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameHealCrateAction.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameHealCrateAction.cs index 7c044866be..5318544f89 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameHealCrateAction.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameHealCrateAction.cs @@ -19,7 +19,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "The 'HealUnitsCrateAction' has been renamed to 'HealActorsCrateAction'."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var huca in actorNode.ChildrenMatching("HealUnitsCrateAction")) huca.RenameKey("HealActorsCrateAction"); diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameInfiltrationNotifications.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameInfiltrationNotifications.cs index 2e2f057fb1..97c2f8ecd6 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameInfiltrationNotifications.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameInfiltrationNotifications.cs @@ -19,7 +19,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "The InfiltrateForCash Notification has been renamed to be in line with new notification properties added."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var rp in actorNode.ChildrenMatching("InfiltrateForCash")) rp.RenameChildrenMatching("Notification", "InfiltratedNotification"); diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameSelfHealing.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameSelfHealing.cs index 83f4f4c010..c211c0c70e 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameSelfHealing.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameSelfHealing.cs @@ -21,7 +21,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules "SelfHealing was renamed to ChangesHealth\n" + "HealIfBelow was renamed to StartIfBelow."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var sh in actorNode.ChildrenMatching("SelfHealing")) { diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameSmudgeSmokeFields.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameSmudgeSmokeFields.cs index 92f66c08a8..c63fbcc9a3 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameSmudgeSmokeFields.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameSmudgeSmokeFields.cs @@ -21,7 +21,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules "Renamed smoke-related properties on SmudgeLayer to be in line with comparable properties.\n" + "Additionally, set the *Chance, *Image and *Sequences defaults to null."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var layer in actorNode.ChildrenMatching("SmudgeLayer")) { diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameStances.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameStances.cs index ec79c93f11..e5283f959b 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameStances.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameStances.cs @@ -77,7 +77,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules ("TooltipDescription", "ValidStances", "ValidRelationships") }; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var (traitName, oldName, newName) in traits) foreach (var traitNode in actorNode.ChildrenMatching(traitName)) @@ -86,7 +86,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules yield break; } - public override IEnumerable UpdateWeaponNode(ModData modData, MiniYamlNode weaponNode) + public override IEnumerable UpdateWeaponNode(ModData modData, MiniYamlNodeBuilder weaponNode) { foreach (var projectileNode in weaponNode.ChildrenMatching("Projectile")) projectileNode.RenameChildrenMatching("ValidBounceBlockerStances", "ValidBounceBlockerRelationships"); diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameWithNukeLaunch.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameWithNukeLaunch.cs index c8060f71a0..42f336819d 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameWithNukeLaunch.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/RenameWithNukeLaunch.cs @@ -19,7 +19,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "`WithNukeLaunchAnimation` has been renamed to `WithSupportPowerActivationAnimation` and `WithNukeLaunchOverlay` to `WithSupportPowerActivationOverlay`."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { actorNode.RenameChildrenMatching("WithNukeLaunchAnimation", "WithSupportPowerActivationAnimation", true); actorNode.RenameChildrenMatching("WithNukeLaunchOverlay", "WithSupportPowerActivationOverlay", true); diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ReplaceBurns.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ReplaceBurns.cs index f6fdaa2116..a61df9730e 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ReplaceBurns.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ReplaceBurns.cs @@ -19,9 +19,9 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "Burns can be replaced using WithIdleOverlay and ChangesHealth."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { - var addNodes = new List(); + var addNodes = new List(); foreach (var burns in actorNode.ChildrenMatching("Burns")) { @@ -34,13 +34,13 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules var interval = burns.LastChildMatching("Interval"); var intervalValue = interval != null ? interval.NodeValue() : 8; - var overlay = new MiniYamlNode("WithIdleOverlay@Burns", ""); + var overlay = new MiniYamlNodeBuilder("WithIdleOverlay@Burns", ""); overlay.AddNode("Image", FieldSaver.FormatValue("fire")); overlay.AddNode("Sequence", FieldSaver.FormatValue(animValue)); overlay.AddNode("IsDecoration", FieldSaver.FormatValue(true)); addNodes.Add(overlay); - var changesHealth = new MiniYamlNode("ChangesHealth", ""); + var changesHealth = new MiniYamlNodeBuilder("ChangesHealth", ""); changesHealth.AddNode("Step", FieldSaver.FormatValue(-damageValue)); changesHealth.AddNode("StartIfBelow", FieldSaver.FormatValue(101)); changesHealth.AddNode("Delay", FieldSaver.FormatValue(intervalValue)); diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ReplaceFacingAngles.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ReplaceFacingAngles.cs index 6b9d0be553..314fc5858d 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ReplaceFacingAngles.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ReplaceFacingAngles.cs @@ -70,7 +70,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules } } - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var kv in TraitFields) { @@ -90,7 +90,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules yield break; } - public override IEnumerable UpdateWeaponNode(ModData modData, MiniYamlNode weaponNode) + public override IEnumerable UpdateWeaponNode(ModData modData, MiniYamlNodeBuilder weaponNode) { foreach (var projectileNode in weaponNode.ChildrenMatching("Projectile")) { diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/SpawnActorPowerDefaultEffect.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/SpawnActorPowerDefaultEffect.cs index 0afe0e6828..efe3f797e2 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/SpawnActorPowerDefaultEffect.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/SpawnActorPowerDefaultEffect.cs @@ -19,7 +19,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "The 'EffectSequence' of 'SpawnActorPower' is unset by default."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var spawnActorPower in actorNode.ChildrenMatching("SpawnActorPower")) { diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/SplitDamagedByTerrain.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/SplitDamagedByTerrain.cs index aef1096e36..a97d23c5aa 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/SplitDamagedByTerrain.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/SplitDamagedByTerrain.cs @@ -19,7 +19,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "'DamageThreshold' and 'StartOnThreshold' are no longer supported and removed from 'DamagedByTerrain'."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var damaged in actorNode.ChildrenMatching("DamagedByTerrain", includeRemovals: false)) { diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/UpdateMapInits.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/UpdateMapInits.cs index fb8945e8c7..fc6ad0967a 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/UpdateMapInits.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/UpdateMapInits.cs @@ -33,7 +33,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules } } - public override IEnumerable UpdateMapActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateMapActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { if (actorNode.RemoveNodes("Plugs") > 0) yield return $"Initial plugs for actor {actorNode.Key} will need to be reconfigured using the map editor."; @@ -48,7 +48,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules facing.ReplaceValue(FieldSaver.FormatValue(bodyFacing)); } - var removeNodes = new List(); + var removeNodes = new List(); foreach (var facing in actorNode.ChildrenMatching("TurretFacing")) { var turretFacing = WAngle.FromFacing(facing.NodeValue()) - bodyFacing; @@ -62,7 +62,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules actorNode.Value.Nodes.Remove(node); } - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var turret in actorNode.ChildrenMatching("Turreted")) turret.RemoveNodes("PreviewFacing"); diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/UpdateTilesetColors.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/UpdateTilesetColors.cs index 2448663903..113d01759c 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/UpdateTilesetColors.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/UpdateTilesetColors.cs @@ -19,7 +19,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "The LeftColor and RightColor keys in tilesets have been renamed to MinColor and MaxColor to reflect their proper usage."; - public override IEnumerable UpdateTilesetNode(ModData modData, MiniYamlNode tilesetNode) + public override IEnumerable UpdateTilesetNode(ModData modData, MiniYamlNodeBuilder tilesetNode) { if (tilesetNode.Key == "Templates") { diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/AddControlGroups.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/AddControlGroups.cs index 7658bf36e1..19032a5eab 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/AddControlGroups.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/AddControlGroups.cs @@ -20,11 +20,11 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "A new trait ControlGroups was added, splitting logic away from Selection."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { if (actorNode.ChildrenMatching("Selection").Any(x => !x.IsRemoval()) && !actorNode.ChildrenMatching("ControlGroups").Any()) - actorNode.AddNode(new MiniYamlNode("ControlGroups", "")); + actorNode.AddNode(new MiniYamlNodeBuilder("ControlGroups", "")); yield break; } diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/AttackBomberFacingTolerance.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/AttackBomberFacingTolerance.cs index b242f9ac8a..814915693e 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/AttackBomberFacingTolerance.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/AttackBomberFacingTolerance.cs @@ -19,7 +19,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "The tolerance for attack angle was defined twice on AttackBomber. This override has to be defined in the rules now."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var attackBomber in actorNode.ChildrenMatching("AttackBomber", includeRemovals: false)) { @@ -27,7 +27,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules if (facingTolerance != null) continue; - var facingToleranceNode = new MiniYamlNode("FacingTolerance", FieldSaver.FormatValue(new WAngle(8))); + var facingToleranceNode = new MiniYamlNodeBuilder("FacingTolerance", FieldSaver.FormatValue(new WAngle(8))); attackBomber.AddNode(facingToleranceNode); } diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/AttackFrontalFacingTolerance.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/AttackFrontalFacingTolerance.cs index 27aec16667..9116c2e90e 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/AttackFrontalFacingTolerance.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/AttackFrontalFacingTolerance.cs @@ -19,7 +19,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "The tolerance for the attack angle was defined twice on AttackFrontal. This override has to be defined in the rules now."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var attackFrontal in actorNode.ChildrenMatching("AttackFrontal", includeRemovals: false)) { @@ -27,7 +27,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules if (facingTolerance != null) continue; - var facingToleranceNode = new MiniYamlNode("FacingTolerance", FieldSaver.FormatValue(WAngle.Zero)); + var facingToleranceNode = new MiniYamlNodeBuilder("FacingTolerance", FieldSaver.FormatValue(WAngle.Zero)); attackFrontal.AddNode(facingToleranceNode); } diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ConvertBoundsToWDist.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ConvertBoundsToWDist.cs index 23797ee2dc..05ef197fb4 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ConvertBoundsToWDist.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ConvertBoundsToWDist.cs @@ -24,7 +24,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules readonly string[] traits = { "Interactable", "Selectable", "IsometricSelectable" }; readonly string[] fields = { "Bounds", "DecorationBounds" }; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { var grid = modData.Manifest.Get(); var tileSize = grid.TileSize; diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveDomainIndex.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveDomainIndex.cs index eece21d535..072aa92460 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveDomainIndex.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveDomainIndex.cs @@ -19,12 +19,12 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "The DomainIndex trait was removed from World. Two overlay traits were added at the same time."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { if (actorNode.RemoveNodes("DomainIndex") > 0) { - actorNode.AddNode(new MiniYamlNode("PathFinderOverlay", "")); - actorNode.AddNode(new MiniYamlNode("HierarchicalPathFinderOverlay", "")); + actorNode.AddNode(new MiniYamlNodeBuilder("PathFinderOverlay", "")); + actorNode.AddNode(new MiniYamlNodeBuilder("HierarchicalPathFinderOverlay", "")); } yield break; diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemovePlaceBuildingPalette.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemovePlaceBuildingPalette.cs index 2479463395..569cd22ff1 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemovePlaceBuildingPalette.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemovePlaceBuildingPalette.cs @@ -35,7 +35,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules locations.Clear(); } - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { var removed = 0; foreach (var node in actorNode.ChildrenMatching("ActorPreviewPlaceBuildingPreview")) diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemovePlayerHighlightPalette.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemovePlayerHighlightPalette.cs index 0319fc4a12..a0c346dc9f 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemovePlayerHighlightPalette.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemovePlayerHighlightPalette.cs @@ -19,7 +19,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "PlayerHighlightPalette trait has been removed. Its functionality is now automatically provided by the engine."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { actorNode.RemoveNodes("PlayerHighlightPalette"); yield break; diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveRenderSpritesScale.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveRenderSpritesScale.cs index eb8e9491e7..fe85dd4bd1 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveRenderSpritesScale.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveRenderSpritesScale.cs @@ -19,7 +19,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "The Scale option was removed from RenderSprites. Scale can now be defined on individual sequence definitions."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var renderSprites in actorNode.ChildrenMatching("RenderSprites")) if (renderSprites.RemoveNodes("Scale") > 0) diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveResourceType.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveResourceType.cs index 96432e21ff..b46332c14c 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveResourceType.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveResourceType.cs @@ -21,15 +21,15 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules "The ResourceType trait has been removed, and resource definitions moved to the\n" + "ResourceLayer, EditorResourceLayer, ResourceRenderer, and PlayerResources traits."; - MiniYaml resourceLayer; - MiniYaml resourceRenderer; - MiniYaml values; + MiniYamlBuilder resourceLayer; + MiniYamlBuilder resourceRenderer; + MiniYamlBuilder values; public override IEnumerable BeforeUpdate(ModData modData) { - resourceLayer = new MiniYaml(""); - resourceRenderer = new MiniYaml(""); - values = new MiniYaml(""); + resourceLayer = new MiniYamlBuilder(""); + resourceRenderer = new MiniYamlBuilder(""); + values = new MiniYamlBuilder(""); yield break; } @@ -54,17 +54,17 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules "You must define a custom ResourceLayer subclass if you want to customize the default behaviour."; } - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var resourceNode in actorNode.ChildrenMatching("ResourceType")) { var typeNode = resourceNode.LastChildMatching("Type"); if (typeNode != null) { - var resourceLayerNode = new MiniYamlNode(typeNode.Value.Value, ""); + var resourceLayerNode = new MiniYamlNodeBuilder(new MiniYamlNode(typeNode.Value.Value, "")); resourceLayer.Nodes.Add(resourceLayerNode); - var resourceRendererNode = new MiniYamlNode(typeNode.Value.Value, ""); + var resourceRendererNode = new MiniYamlNodeBuilder(new MiniYamlNode(typeNode.Value.Value, "")); resourceRenderer.Nodes.Add(resourceRendererNode); var indexNode = resourceNode.LastChildMatching("ResourceType"); @@ -88,7 +88,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules var valueNode = resourceNode.LastChildMatching("ValuePerUnit"); if (valueNode != null) - values.Nodes.Add(new MiniYamlNode(typeNode.Value.Value, valueNode.Value.Value)); + values.Nodes.Add(new MiniYamlNodeBuilder(typeNode.Value.Value, valueNode.Value.Value)); var imageNode = resourceNode.LastChildMatching("Image"); if (imageNode != null) diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveSmokeTrailWhenDamaged.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveSmokeTrailWhenDamaged.cs index 4ec94c2251..b620779a06 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveSmokeTrailWhenDamaged.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RemoveSmokeTrailWhenDamaged.cs @@ -38,7 +38,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules locations.Clear(); } - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { var locationKey = $"{actorNode.Key} ({actorNode.Location.Filename})"; var anyConditionalSmokeTrail = false; @@ -94,7 +94,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules if (anyConditionalSmokeTrail) { - var grantCondition = new MiniYamlNode("GrantConditionOnDamageState@SmokeTrail", ""); + var grantCondition = new MiniYamlNodeBuilder("GrantConditionOnDamageState@SmokeTrail", ""); grantCondition.AddNode("Condition", FieldSaver.FormatValue("enable-smoke")); actorNode.AddNode(grantCondition); } diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RenameCloakTypes.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RenameCloakTypes.cs index 0333cca343..4041f3ad9d 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RenameCloakTypes.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RenameCloakTypes.cs @@ -19,7 +19,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "Rename 'CloakTypes' to 'DetectionTypes' in order to make it clearer as well as make space for 'CloakType' in Cloak"; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var traitNode in actorNode.ChildrenMatching("Cloak")) traitNode.RenameChildrenMatching("CloakTypes", "DetectionTypes"); diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RenameContrailProperties.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RenameContrailProperties.cs index 91c2e90883..35f2c285bd 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RenameContrailProperties.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RenameContrailProperties.cs @@ -20,7 +20,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "Rename contrail color properties `Color` to `StartColor` and `UsePlayerColor` to `StartColorUsePlayerColor` in traits and weapons to account for added `EndColor` functionality"; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var traitNode in actorNode.ChildrenMatching("Contrail")) { @@ -31,7 +31,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules yield break; } - public override IEnumerable UpdateWeaponNode(ModData modData, MiniYamlNode weaponNode) + public override IEnumerable UpdateWeaponNode(ModData modData, MiniYamlNodeBuilder weaponNode) { foreach (var traitNode in weaponNode.ChildrenMatching("Projectile").Where(n => n.Value.Value == "Missile")) { diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RenameMPTraits.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RenameMPTraits.cs index c16c8de3ec..804d22e87a 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RenameMPTraits.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RenameMPTraits.cs @@ -21,7 +21,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules "'SpawnMPUnits' was renamed to 'SpawnStartingUnits', 'MPStartUnits' to 'StartingUnits', 'MPStartLocations' to " + "'MapStartingLocations', and 'CreateMPPlayers' to 'CreateMapPlayers'."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { actorNode.RenameChildrenMatching("SpawnMPUnits", "SpawnStartingUnits"); actorNode.RenameChildrenMatching("MPStartUnits", "StartingUnits"); diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RenameSupportPowerDescription.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RenameSupportPowerDescription.cs index 6607b35250..6e0e5e8b96 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RenameSupportPowerDescription.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/RenameSupportPowerDescription.cs @@ -18,7 +18,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Name => "Support powers now use 'Name' and 'Description' fields like units."; public override string Description => "'Description' was renamed to 'Name' and 'LongDesc' was renamed to 'Description'."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var traitNode in actorNode.ChildrenContaining("Power")) { diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceCrateSecondsWithTicks.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceCrateSecondsWithTicks.cs index 88d24af1ab..fda4e99b7a 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceCrateSecondsWithTicks.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceCrateSecondsWithTicks.cs @@ -22,7 +22,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules "by multiplying with 25 internally. Converted to use ticks like everything else.\n" + "Also renamed Lifetime to Duration and ScaredyCat.PanicLength to PanicDuration to match other places."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var crateNode in actorNode.ChildrenMatching("Crate")) { diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceResourceValueModifiers.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceResourceValueModifiers.cs index 0d1c095adc..721d2288df 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceResourceValueModifiers.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceResourceValueModifiers.cs @@ -20,7 +20,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "The HarvesterResourceMultiplier trait has been removed, and the RefineryResourceMultiplier trait renamed to ResourceValueMultiplier."; bool notified; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { if (actorNode.RemoveNodes("HarvesterResourceModifier") > 0 && !notified) { diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceSequenceEmbeddedPalette.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceSequenceEmbeddedPalette.cs index 96678edec2..195e76df9b 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceSequenceEmbeddedPalette.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceSequenceEmbeddedPalette.cs @@ -19,7 +19,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "The EmbeddedPalette sequence option was replaced with a boolean HasEmbeddedPalette."; - public override IEnumerable UpdateSequenceNode(ModData modData, MiniYamlNode sequenceNode) + public override IEnumerable UpdateSequenceNode(ModData modData, MiniYamlNodeBuilder sequenceNode) { foreach (var sequence in sequenceNode.Value.Nodes) if (sequence.RemoveNodes("EmbeddedPalette") > 0) diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceShadowPalette.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceShadowPalette.cs index 3ad2a7433e..a910de648d 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceShadowPalette.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceShadowPalette.cs @@ -33,7 +33,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules locations.Clear(); } - public override IEnumerable UpdateWeaponNode(ModData modData, MiniYamlNode weaponNode) + public override IEnumerable UpdateWeaponNode(ModData modData, MiniYamlNodeBuilder weaponNode) { foreach (var projectileNode in weaponNode.ChildrenMatching("Projectile")) if (projectileNode.RemoveNodes("ShadowPalette") > 0) @@ -42,7 +42,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules yield break; } - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var node in actorNode.ChildrenMatching("WithShadow")) if (node.RemoveNodes("Palette") > 0) diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceWithColoredOverlayPalette.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceWithColoredOverlayPalette.cs index 6257e54fb9..f0b31ba33d 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceWithColoredOverlayPalette.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/ReplaceWithColoredOverlayPalette.cs @@ -30,7 +30,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules locations.Clear(); } - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var node in actorNode.ChildrenMatching("WithColoredOverlay")) if (node.RemoveNodes("Palette") > 0) diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/SplitNukePowerMissileImage.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/SplitNukePowerMissileImage.cs index 890eaf5f69..291ef80519 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/SplitNukePowerMissileImage.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/SplitNukePowerMissileImage.cs @@ -22,7 +22,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules "NukePower used MissileWeapon field for as the name for missile image too.\n" + "This function has been moved to its own MissileImage field."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var nukePowerNode in actorNode.ChildrenMatching("NukePower")) { @@ -30,7 +30,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules if (missileWeaponNode != null) { var weapon = missileWeaponNode.NodeValue(); - nukePowerNode.AddNode(new MiniYamlNode("MissileImage", weapon)); + nukePowerNode.AddNode(new MiniYamlNodeBuilder("MissileImage", weapon)); } } diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/UnhardcodeBaseBuilderBotModule.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/UnhardcodeBaseBuilderBotModule.cs index 8541ae4acf..9fcc4a100e 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/UnhardcodeBaseBuilderBotModule.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/UnhardcodeBaseBuilderBotModule.cs @@ -16,7 +16,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules { public class UnhardcodeBaseBuilderBotModule : UpdateRule, IBeforeUpdateActors { - MiniYamlNode defences; + MiniYamlNodeBuilder defences; // Excludes AttackBomber and AttackTDGunboatTurreted as actors with these AttackBase traits aren't supposed to be controlled. readonly string[] attackBase = { "AttackLeap", "AttackPopupTurreted", "AttackAircraft", "AttackTesla", "AttackCharges", "AttackFollow", "AttackTurreted", "AttackFrontal", "AttackGarrisoned", "AttackOmni", "AttackSwallow" }; @@ -28,7 +28,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "DefenseTypes were added."; - public IEnumerable BeforeUpdateActors(ModData modData, List resolvedActors) + public IEnumerable BeforeUpdateActors(ModData modData, List resolvedActors) { var defences = new List(); @@ -70,7 +70,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules } } - this.defences = new MiniYamlNode("DefenseTypes", FieldSaver.FormatValue(defences)); + this.defences = new MiniYamlNodeBuilder("DefenseTypes", FieldSaver.FormatValue(defences)); yield break; } @@ -83,7 +83,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules anyAdded = false; } - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var squadManager in actorNode.ChildrenMatching("BaseBuilderBotModule", includeRemovals: false)) { diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/UnhardcodeSquadManager.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/UnhardcodeSquadManager.cs index 05ee909279..ef97a77c01 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/UnhardcodeSquadManager.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/UnhardcodeSquadManager.cs @@ -16,7 +16,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules { public class UnhardcodeSquadManager : UpdateRule, IBeforeUpdateActors { - readonly List addNodes = new(); + readonly List addNodes = new(); // Excludes AttackBomber and AttackTDGunboatTurreted as actors with these AttackBase traits aren't supposed to be controlled. readonly string[] attackBase = { "AttackLeap", "AttackPopupTurreted", "AttackAircraft", "AttackTesla", "AttackCharges", "AttackFollow", "AttackTurreted", "AttackFrontal", "AttackGarrisoned", "AttackOmni", "AttackSwallow" }; @@ -28,7 +28,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "AirUnitsTypes and ProtectionTypes were added."; - public IEnumerable BeforeUpdateActors(ModData modData, List resolvedActors) + public IEnumerable BeforeUpdateActors(ModData modData, List resolvedActors) { var aircraft = new List(); var vips = new List(); @@ -106,15 +106,15 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules } } - addNodes.Add(new MiniYamlNode("AirUnitsTypes", FieldSaver.FormatValue(aircraft))); - addNodes.Add(new MiniYamlNode("ProtectionTypes", FieldSaver.FormatValue(vips))); + addNodes.Add(new MiniYamlNodeBuilder("AirUnitsTypes", FieldSaver.FormatValue(aircraft))); + addNodes.Add(new MiniYamlNodeBuilder("ProtectionTypes", FieldSaver.FormatValue(vips))); yield break; } bool anyAdded = false; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var squadManager in actorNode.ChildrenMatching("SquadManagerBotModule", includeRemovals: false)) { diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/UnhardcodeVeteranProductionIconOverlay.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/UnhardcodeVeteranProductionIconOverlay.cs index 8cdaa3c017..8483f27c36 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/UnhardcodeVeteranProductionIconOverlay.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/UnhardcodeVeteranProductionIconOverlay.cs @@ -32,12 +32,12 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules locations.Clear(); } - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var veteranProductionIconOverlay in actorNode.ChildrenMatching("VeteranProductionIconOverlay")) { veteranProductionIconOverlay.RenameKey("ProductionIconOverlayManager"); - veteranProductionIconOverlay.AddNode(new MiniYamlNode("Type", "Veterancy")); + veteranProductionIconOverlay.AddNode(new MiniYamlNodeBuilder("Type", "Veterancy")); } foreach (var producibleWithLevel in actorNode.ChildrenMatching("ProducibleWithLevel")) diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/UseMillisecondsForSounds.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/UseMillisecondsForSounds.cs index 1e0b364f31..78bf78250b 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20210321/UseMillisecondsForSounds.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20210321/UseMillisecondsForSounds.cs @@ -23,7 +23,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules "PowerManager.AdviceInterval and PlayerResources.InsufficientFundsNotificationDelay were using ticks.\n" + "Converted all of those to use real milliseconds instead."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var announce in actorNode.ChildrenMatching("AnnounceOnKill")) { diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/AddColorPickerValueRange.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/AddColorPickerValueRange.cs index d46c383392..a323b450a3 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/AddColorPickerValueRange.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/AddColorPickerValueRange.cs @@ -21,7 +21,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "Each preset color can now have their brightness specified. SimilarityThreshold range was changed."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { var manager = actorNode.LastChildMatching("ColorPickerManager"); if (manager == null) diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/ExplicitSequenceFilenames.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/ExplicitSequenceFilenames.cs index f7d17f17bf..68606095a3 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/ExplicitSequenceFilenames.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/ExplicitSequenceFilenames.cs @@ -26,18 +26,18 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules "Tileset specific overrides can be defined as children of the TilesetFilenames field."; string defaultSpriteExtension = ".shp"; - List resolvedImagesNodes; + List resolvedImagesNodes; readonly Dictionary tilesetExtensions = new(); readonly Dictionary tilesetCodes = new(); bool parseModYaml = true; bool reportModYamlChanges; bool disabled; - public IEnumerable BeforeUpdateSequences(ModData modData, List resolvedImagesNodes) + public IEnumerable BeforeUpdateSequences(ModData modData, List resolvedImagesNodes) { // Keep a resolved copy of the sequences so we can account for values imported through inheritance or Defaults. // This will be modified during processing, so take a deep copy to avoid side-effects on other update rules. - this.resolvedImagesNodes = MiniYaml.FromString(resolvedImagesNodes.WriteToString()); + this.resolvedImagesNodes = MiniYaml.FromString(resolvedImagesNodes.WriteToString()).Select(n => new MiniYamlNodeBuilder(n)).ToList(); var requiredMetadata = new HashSet(); foreach (var imageNode in resolvedImagesNodes) @@ -84,7 +84,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules yield break; } - var spriteSequenceFormatNode = new MiniYamlNode("", spriteSequenceFormatYaml); + var spriteSequenceFormatNode = new MiniYamlNodeBuilder("", new MiniYamlBuilder(spriteSequenceFormatYaml)); var defaultSpriteExtensionNode = spriteSequenceFormatNode.LastChildMatching("DefaultSpriteExtension"); if (defaultSpriteExtensionNode != null) { @@ -121,7 +121,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules reportModYamlChanges = false; } - public override IEnumerable UpdateSequenceNode(ModData modData, MiniYamlNode imageNode) + public override IEnumerable UpdateSequenceNode(ModData modData, MiniYamlNodeBuilder imageNode) { if (disabled) yield break; @@ -154,7 +154,11 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules if (resolvedSequenceNode == resolvedDefaultsNode) continue; - resolvedSequenceNode.Value.Nodes = MiniYaml.Merge(new[] { resolvedDefaultsNode.Value.Nodes, resolvedSequenceNode.Value.Nodes }); + resolvedSequenceNode.Value.Nodes = MiniYaml.Merge(new[] + { + resolvedDefaultsNode.Value.Nodes.Select(n => n.Build()).ToArray(), + resolvedSequenceNode.Value.Nodes.Select(n => n.Build()).ToArray() + }).Select(n => new MiniYamlNodeBuilder(n)).ToList(); resolvedSequenceNode.Value.Value ??= resolvedDefaultsNode.Value.Value; } } @@ -180,8 +184,8 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules } // Identify a suitable default for deduplication - MiniYamlNode defaultFilenameNode = null; - MiniYamlNode defaultTilesetFilenamesNode = null; + MiniYamlNodeBuilder defaultFilenameNode = null; + MiniYamlNodeBuilder defaultTilesetFilenamesNode = null; foreach (var defaultsNode in imageNode.ChildrenMatching("Defaults")) { defaultFilenameNode = defaultsNode.LastChildMatching("Filename") ?? defaultFilenameNode; @@ -222,12 +226,12 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules var defaultsNode = imageNode.LastChildMatching("Defaults"); if (defaultsNode == null) { - defaultsNode = new MiniYamlNode("Defaults", ""); + defaultsNode = new MiniYamlNodeBuilder("Defaults", ""); imageNode.Value.Nodes.Insert(inheritsNodeIndex, defaultsNode); } var nodes = MiniYaml.FromString(duplicateTilesetCount.First(kv => kv.Value == maxDuplicateTilesetCount).Key); - defaultTilesetFilenamesNode = new MiniYamlNode("TilesetFilenames", "", nodes); + defaultTilesetFilenamesNode = new MiniYamlNodeBuilder("TilesetFilenames", "", nodes); defaultsNode.Value.Nodes.Insert(0, defaultTilesetFilenamesNode); } @@ -237,11 +241,11 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules var defaultsNode = imageNode.LastChildMatching("Defaults"); if (defaultsNode == null) { - defaultsNode = new MiniYamlNode("Defaults", ""); + defaultsNode = new MiniYamlNodeBuilder("Defaults", ""); imageNode.Value.Nodes.Insert(inheritsNodeIndex, defaultsNode); } - defaultFilenameNode = new MiniYamlNode("Filename", duplicateCount.First(kv => kv.Value == maxDuplicateCount).Key); + defaultFilenameNode = new MiniYamlNodeBuilder("Filename", duplicateCount.First(kv => kv.Value == maxDuplicateCount).Key); defaultsNode.Value.Nodes.Insert(0, defaultFilenameNode); } } @@ -257,15 +261,15 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules var tilesetFilenamesNode = sequenceNode.LastChildMatching("TilesetFilenames"); if (defaultTilesetFilenamesNode != null && combineNode != null) - sequenceNode.Value.Nodes.Insert(0, new MiniYamlNode("TilesetFilenames", "")); + sequenceNode.Value.Nodes.Insert(0, new MiniYamlNodeBuilder("TilesetFilenames", "")); if (defaultFilenameNode != null && combineNode != null) - sequenceNode.Value.Nodes.Insert(0, new MiniYamlNode("Filename", "")); + sequenceNode.Value.Nodes.Insert(0, new MiniYamlNodeBuilder("Filename", "")); if (defaultTilesetFilenamesNode != null && tilesetFilenamesNode == null && filenameNode != null) { var index = sequenceNode.Value.Nodes.IndexOf(filenameNode) + 1; - sequenceNode.Value.Nodes.Insert(index, new MiniYamlNode("TilesetFilenames", "")); + sequenceNode.Value.Nodes.Insert(index, new MiniYamlNodeBuilder("TilesetFilenames", "")); } // Remove redundant overrides @@ -334,7 +338,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules imageNode.RemoveNode(sequenceNode); } - void ProcessNode(ModData modData, MiniYamlNode sequenceNode, MiniYamlNode resolvedSequenceNode, string imageName) + void ProcessNode(ModData modData, MiniYamlNodeBuilder sequenceNode, MiniYamlNodeBuilder resolvedSequenceNode, string imageName) { // "Filename" was introduced with this update rule, so that means this node was already processed and can be skipped if (sequenceNode.LastChildMatching("Filename") != null) @@ -390,7 +394,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules if (useTilesetExtension || useTilesetCode) { - var tilesetFilenamesNode = new MiniYamlNode("TilesetFilenames", ""); + var tilesetFilenamesNode = new MiniYamlNodeBuilder("TilesetFilenames", ""); var duplicateCount = new Dictionary(); foreach (var tileset in modData.DefaultTerrainInfo.Keys) { @@ -415,7 +419,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules var maxDuplicateCount = duplicateCount.MaxByOrDefault(kv => kv.Value).Value; if (maxDuplicateCount > 1) { - var filenameNode = new MiniYamlNode("Filename", duplicateCount.First(kv => kv.Value == maxDuplicateCount).Key); + var filenameNode = new MiniYamlNodeBuilder("Filename", duplicateCount.First(kv => kv.Value == maxDuplicateCount).Key); foreach (var overrideNode in tilesetFilenamesNode.Value.Nodes.ToList()) if (overrideNode.Value.Value == filenameNode.Value.Value) tilesetFilenamesNode.Value.Nodes.Remove(overrideNode); @@ -431,7 +435,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules if (addExtension) filename += defaultSpriteExtension; - sequenceNode.Value.Nodes.Insert(0, new MiniYamlNode("Filename", filename)); + sequenceNode.Value.Nodes.Insert(0, new MiniYamlNodeBuilder("Filename", filename)); } sequenceNode.ReplaceValue(""); diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/ProductionTabsWidgetAddTabButtonCollection.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/ProductionTabsWidgetAddTabButtonCollection.cs index 23324ba1a3..150bd673fe 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/ProductionTabsWidgetAddTabButtonCollection.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/ProductionTabsWidgetAddTabButtonCollection.cs @@ -20,7 +20,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "Change the field name from Button to TabButton and add ArrowButton, if Button field was set."; - public override IEnumerable UpdateChromeNode(ModData modData, MiniYamlNode chromeNode) + public override IEnumerable UpdateChromeNode(ModData modData, MiniYamlNodeBuilder chromeNode) { if (!chromeNode.KeyMatches("ProductionTabs")) yield break; @@ -33,7 +33,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules } if (buttonCollection != null) - chromeNode.AddNode(new MiniYamlNode("ArrowButton", buttonCollection)); + chromeNode.AddNode(new MiniYamlNodeBuilder("ArrowButton", buttonCollection)); } } } diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveExperienceFromInfiltrates.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveExperienceFromInfiltrates.cs index 9d66ef62ec..e705c2a3d2 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveExperienceFromInfiltrates.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveExperienceFromInfiltrates.cs @@ -32,7 +32,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules locations.Clear(); } - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { var removed = false; foreach (var node in actorNode.ChildrenMatching("Infiltrates")) diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveNegativeSequenceLength.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveNegativeSequenceLength.cs index 1b73548c58..673a020eec 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveNegativeSequenceLength.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveNegativeSequenceLength.cs @@ -21,9 +21,9 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "Negative sequence length is no longer allowed, define individual frames in reverse instead."; - List resolvedImagesNodes; + List resolvedImagesNodes; - public IEnumerable BeforeUpdateSequences(ModData modData, List resolvedImagesNodes) + public IEnumerable BeforeUpdateSequences(ModData modData, List resolvedImagesNodes) { this.resolvedImagesNodes = resolvedImagesNodes; yield break; @@ -31,12 +31,12 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules readonly Queue actionQueue = new(); - static MiniYamlNode GetNode(string key, MiniYamlNode node, MiniYamlNode defaultNode) + static MiniYamlNodeBuilder GetNode(string key, MiniYamlNodeBuilder node, MiniYamlNodeBuilder defaultNode) { return node.LastChildMatching(key, includeRemovals: false) ?? defaultNode?.LastChildMatching(key, includeRemovals: false); } - public override IEnumerable UpdateSequenceNode(ModData modData, MiniYamlNode sequenceNode) + public override IEnumerable UpdateSequenceNode(ModData modData, MiniYamlNodeBuilder sequenceNode) { var defaultNode = sequenceNode.LastChildMatching("Defaults"); var defaultLengthNode = defaultNode == null ? null : GetNode("Length", defaultNode, null); diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveSequenceHasEmbeddedPalette.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveSequenceHasEmbeddedPalette.cs index 92b11480fd..43eb67b751 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveSequenceHasEmbeddedPalette.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveSequenceHasEmbeddedPalette.cs @@ -33,7 +33,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules locations.Clear(); } - public override IEnumerable UpdateSequenceNode(ModData modData, MiniYamlNode imageNode) + public override IEnumerable UpdateSequenceNode(ModData modData, MiniYamlNodeBuilder imageNode) { foreach (var sequenceNode in imageNode.Value.Nodes) sequenceNode.RemoveNodes("HasEmbeddedPalette"); @@ -41,7 +41,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules yield break; } - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var traitNode in actorNode.ChildrenMatching("PaletteFromEmbeddedSpritePalette")) { diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveTSRefinery.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveTSRefinery.cs index eb6a48aff8..94e15d94a6 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveTSRefinery.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RemoveTSRefinery.cs @@ -19,7 +19,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "TiberianSunRefinery was removed, use Refinery instead."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { actorNode.RenameChildrenMatching("TiberianSunRefinery", "Refinery"); diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RenameContrailWidth.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RenameContrailWidth.cs index 5774d43cb3..fc4907b502 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RenameContrailWidth.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RenameContrailWidth.cs @@ -20,7 +20,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "Rename contrail `TrailWidth` to `StartWidth` in traits and weapons to acount for added `EndWidth` functionality"; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var traitNode in actorNode.ChildrenMatching("Contrail")) traitNode.RenameChildrenMatching("TrailWidth", "StartWidth"); @@ -28,7 +28,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules yield break; } - public override IEnumerable UpdateWeaponNode(ModData modData, MiniYamlNode weaponNode) + public override IEnumerable UpdateWeaponNode(ModData modData, MiniYamlNodeBuilder weaponNode) { foreach (var traitNode in weaponNode.ChildrenMatching("Projectile").Where(n => n.Value.Value == "Missile")) traitNode.RenameChildrenMatching("ContrailWidth", "ContrailStartWidth"); diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RenameEngineerRepair.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RenameEngineerRepair.cs index 1c08c80f48..741be44f36 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RenameEngineerRepair.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RenameEngineerRepair.cs @@ -21,7 +21,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules "'EngineerRepair' was renamed to 'InstantlyRepairs' " + "and 'EngineerRepairable' to 'InstantlyRepairable'."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { actorNode.RenameChildrenMatching("EngineerRepair", "InstantlyRepairs"); actorNode.RenameChildrenMatching("EngineerRepairable", "InstantlyRepairable"); diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RenameMcvCrateAction.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RenameMcvCrateAction.cs index b1247c2f2c..149a8dccf3 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RenameMcvCrateAction.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/RenameMcvCrateAction.cs @@ -19,7 +19,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "The 'GiveMcvCrateAction' has been renamed to 'GiveBaseBuilderCrateAction'."; - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { foreach (var node in actorNode.ChildrenMatching("GiveMcvCrateAction")) node.RenameKey("GiveBaseBuilderCrateAction"); diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/TextNotificationsDisplayWidgetRemoveTime.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/TextNotificationsDisplayWidgetRemoveTime.cs index 4ca6d3859f..10698cd9e4 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20230225/TextNotificationsDisplayWidgetRemoveTime.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20230225/TextNotificationsDisplayWidgetRemoveTime.cs @@ -20,7 +20,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules public override string Description => "Change the field name from RemoveTime to DisplayDurationMs and convert the value from ticks to milliseconds"; - public override IEnumerable UpdateChromeNode(ModData modData, MiniYamlNode chromeNode) + public override IEnumerable UpdateChromeNode(ModData modData, MiniYamlNodeBuilder chromeNode) { if (!chromeNode.KeyMatches("TextNotificationsDisplay")) yield break; diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/CopyIsometricSelectableHeight.cs b/OpenRA.Mods.Common/UpdateRules/Rules/CopyIsometricSelectableHeight.cs index b213e0c101..0fa83dda95 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/CopyIsometricSelectableHeight.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/CopyIsometricSelectableHeight.cs @@ -58,7 +58,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules yield break; } - public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { if (complete || actorNode.LastChildMatching("IsometricSelectable") != null) yield break; @@ -70,7 +70,7 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules if (height == 24) yield break; - var selection = new MiniYamlNode("IsometricSelectable", ""); + var selection = new MiniYamlNodeBuilder("IsometricSelectable", ""); selection.AddNode("Height", FieldSaver.FormatValue(height)); actorNode.AddNode(selection); diff --git a/OpenRA.Mods.Common/UpdateRules/UpdateRule.cs b/OpenRA.Mods.Common/UpdateRules/UpdateRule.cs index d56d86c294..b56690d018 100644 --- a/OpenRA.Mods.Common/UpdateRules/UpdateRule.cs +++ b/OpenRA.Mods.Common/UpdateRules/UpdateRule.cs @@ -20,19 +20,19 @@ namespace OpenRA.Mods.Common.UpdateRules /// Defines a transformation that is run on each top-level node in a yaml file set. /// An enumerable of manual steps to be run by the user. - public delegate IEnumerable TopLevelNodeTransform(ModData modData, MiniYamlNode node); + public delegate IEnumerable TopLevelNodeTransform(ModData modData, MiniYamlNodeBuilder node); /// Defines a transformation that is run on each widget node in a chrome yaml file set. /// An enumerable of manual steps to be run by the user. - public delegate IEnumerable ChromeNodeTransform(ModData modData, MiniYamlNode widgetNode); + public delegate IEnumerable ChromeNodeTransform(ModData modData, MiniYamlNodeBuilder widgetNode); - public virtual IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) { yield break; } - public virtual IEnumerable UpdateWeaponNode(ModData modData, MiniYamlNode weaponNode) { yield break; } - public virtual IEnumerable UpdateSequenceNode(ModData modData, MiniYamlNode sequenceNode) { yield break; } - public virtual IEnumerable UpdateChromeNode(ModData modData, MiniYamlNode chromeNode) { yield break; } - public virtual IEnumerable UpdateTilesetNode(ModData modData, MiniYamlNode tilesetNode) { yield break; } - public virtual IEnumerable UpdateChromeProviderNode(ModData modData, MiniYamlNode chromeProviderNode) { yield break; } - public virtual IEnumerable UpdateMapActorNode(ModData modData, MiniYamlNode actorNode) { yield break; } + public virtual IEnumerable UpdateActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { yield break; } + public virtual IEnumerable UpdateWeaponNode(ModData modData, MiniYamlNodeBuilder weaponNode) { yield break; } + public virtual IEnumerable UpdateSequenceNode(ModData modData, MiniYamlNodeBuilder sequenceNode) { yield break; } + public virtual IEnumerable UpdateChromeNode(ModData modData, MiniYamlNodeBuilder chromeNode) { yield break; } + public virtual IEnumerable UpdateTilesetNode(ModData modData, MiniYamlNodeBuilder tilesetNode) { yield break; } + public virtual IEnumerable UpdateChromeProviderNode(ModData modData, MiniYamlNodeBuilder chromeProviderNode) { yield break; } + public virtual IEnumerable UpdateMapActorNode(ModData modData, MiniYamlNodeBuilder actorNode) { yield break; } public virtual IEnumerable BeforeUpdate(ModData modData) { yield break; } public virtual IEnumerable AfterUpdate(ModData modData) { yield break; } @@ -41,16 +41,16 @@ namespace OpenRA.Mods.Common.UpdateRules // These aren't part of the UpdateRule class as to avoid premature yaml merge crashes when updating maps. public interface IBeforeUpdateActors { - IEnumerable BeforeUpdateActors(ModData modData, List resolvedActors) { yield break; } + IEnumerable BeforeUpdateActors(ModData modData, List resolvedActors) { yield break; } } public interface IBeforeUpdateWeapons { - IEnumerable BeforeUpdateWeapons(ModData modData, List resolvedWeapons) { yield break; } + IEnumerable BeforeUpdateWeapons(ModData modData, List resolvedWeapons) { yield break; } } public interface IBeforeUpdateSequences { - IEnumerable BeforeUpdateSequences(ModData modData, List resolvedImages) { yield break; } + IEnumerable BeforeUpdateSequences(ModData modData, List resolvedImages) { yield break; } } } diff --git a/OpenRA.Mods.Common/UpdateRules/UpdateUtils.cs b/OpenRA.Mods.Common/UpdateRules/UpdateUtils.cs index ea921abcb8..76431011dc 100644 --- a/OpenRA.Mods.Common/UpdateRules/UpdateUtils.cs +++ b/OpenRA.Mods.Common/UpdateRules/UpdateUtils.cs @@ -17,7 +17,7 @@ using OpenRA.FileSystem; namespace OpenRA.Mods.Common.UpdateRules { - using YamlFileSet = List<(IReadWritePackage, string, List)>; + using YamlFileSet = List<(IReadWritePackage, string, List)>; public static class UpdateUtils { @@ -35,7 +35,7 @@ namespace OpenRA.Mods.Common.UpdateRules continue; } - yaml.Add(((IReadWritePackage)package, name, MiniYaml.FromStream(package.GetStream(name), name, false))); + yaml.Add(((IReadWritePackage)package, name, MiniYaml.FromStream(package.GetStream(name), name, false).Select(n => new MiniYamlNodeBuilder(n)).ToList())); } return yaml; @@ -44,7 +44,7 @@ namespace OpenRA.Mods.Common.UpdateRules /// /// Loads a YamlFileSet containing any external yaml definitions referenced by a map yaml block. /// - static YamlFileSet LoadExternalMapYaml(ModData modData, MiniYaml yaml, HashSet externalFilenames) + static YamlFileSet LoadExternalMapYaml(ModData modData, MiniYamlBuilder yaml, HashSet externalFilenames) { return FieldLoader.GetValue("value", yaml.Value) .Where(f => f.Contains('|')) @@ -56,7 +56,7 @@ namespace OpenRA.Mods.Common.UpdateRules /// Loads a YamlFileSet containing any internal definitions yaml referenced by a map yaml block. /// External references or internal references to missing files are ignored. /// - static YamlFileSet LoadInternalMapYaml(ModData modData, IReadWritePackage mapPackage, MiniYaml yaml, HashSet externalFilenames) + static YamlFileSet LoadInternalMapYaml(ModData modData, IReadWritePackage mapPackage, MiniYamlBuilder yaml, HashSet externalFilenames) { var fileSet = new YamlFileSet() { @@ -68,7 +68,7 @@ namespace OpenRA.Mods.Common.UpdateRules { // Ignore any files that aren't in the map bundle if (!filename.Contains('|') && mapPackage.Contains(filename)) - fileSet.Add((mapPackage, filename, MiniYaml.FromStream(mapPackage.GetStream(filename), filename, false))); + fileSet.Add((mapPackage, filename, MiniYaml.FromStream(mapPackage.GetStream(filename), filename, false).Select(n => new MiniYamlNodeBuilder(n)).ToList())); else if (modData.ModFiles.Exists(filename)) externalFilenames.Add(filename); } @@ -94,7 +94,7 @@ namespace OpenRA.Mods.Common.UpdateRules return manualSteps; } - var yaml = new MiniYaml(null, MiniYaml.FromStream(mapStream, mapPackage.Name, false)); + var yaml = new MiniYamlBuilder(null, MiniYaml.FromStream(mapStream, mapPackage.Name, false)); files = new YamlFileSet() { (mapPackage, "map.yaml", yaml.Nodes) }; manualSteps.AddRange(rule.BeforeUpdate(modData)); @@ -159,7 +159,7 @@ namespace OpenRA.Mods.Common.UpdateRules return manualSteps; } - public static List LoadMapYaml(IReadOnlyFileSystem fileSystem, IReadOnlyPackage mapPackage, IEnumerable files, MiniYaml mapNode) + public static List LoadMapYaml(IReadOnlyFileSystem fileSystem, IReadOnlyPackage mapPackage, IEnumerable files, MiniYamlBuilder mapNode) { var yaml = files.Select(s => MiniYaml.FromStream(fileSystem.Open(s), s)).ToList(); @@ -177,9 +177,9 @@ namespace OpenRA.Mods.Common.UpdateRules } if (mapNode != null && mapNode.Nodes.Count > 0) - yaml.Add(mapNode.Nodes); + yaml.Add(mapNode.Nodes.Select(n => n.Build()).ToList()); - return MiniYaml.Merge(yaml); + return MiniYaml.Merge(yaml).Select(n => new MiniYamlNodeBuilder(n)).ToList(); } static IEnumerable FilterExternalModFiles(ModData modData, IEnumerable files, HashSet externalFilenames) @@ -215,7 +215,7 @@ namespace OpenRA.Mods.Common.UpdateRules if (mapStream == null) continue; - var yaml = new MiniYaml(null, MiniYaml.FromStream(mapStream, package.Name, false)); + var yaml = new MiniYamlBuilder(new MiniYaml(null, MiniYaml.FromStream(mapStream, package.Name, false))); var mapRulesNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Rules"); if (mapRulesNode != null) foreach (var f in LoadExternalMapYaml(modData, mapRulesNode.Value, externalFilenames)) @@ -240,7 +240,8 @@ namespace OpenRA.Mods.Common.UpdateRules if (rule is IBeforeUpdateActors beforeActors) { - var resolvedActors = MiniYaml.Load(modData.DefaultFileSystem, modData.Manifest.Rules, null); + var resolvedActors = MiniYaml.Load(modData.DefaultFileSystem, modData.Manifest.Rules, null) + .Select(n => new MiniYamlNodeBuilder(n)).ToList(); manualSteps.AddRange(beforeActors.BeforeUpdateActors(modData, resolvedActors)); } @@ -248,7 +249,8 @@ namespace OpenRA.Mods.Common.UpdateRules if (rule is IBeforeUpdateWeapons beforeWeapons) { - var resolvedWeapons = MiniYaml.Load(modData.DefaultFileSystem, modData.Manifest.Weapons, null); + var resolvedWeapons = MiniYaml.Load(modData.DefaultFileSystem, modData.Manifest.Weapons, null) + .Select(n => new MiniYamlNodeBuilder(n)).ToList(); manualSteps.AddRange(beforeWeapons.BeforeUpdateWeapons(modData, resolvedWeapons)); } @@ -256,7 +258,8 @@ namespace OpenRA.Mods.Common.UpdateRules if (rule is IBeforeUpdateSequences beforeSequences) { - var resolvedImages = MiniYaml.Load(modData.DefaultFileSystem, modData.Manifest.Sequences, null); + var resolvedImages = MiniYaml.Load(modData.DefaultFileSystem, modData.Manifest.Sequences, null) + .Select(n => new MiniYamlNodeBuilder(n)).ToList(); manualSteps.AddRange(beforeSequences.BeforeUpdateSequences(modData, resolvedImages)); } @@ -277,7 +280,7 @@ namespace OpenRA.Mods.Common.UpdateRules return manualSteps; } - static IEnumerable ApplyChromeTransformInner(ModData modData, MiniYamlNode current, UpdateRule.ChromeNodeTransform transform) + static IEnumerable ApplyChromeTransformInner(ModData modData, MiniYamlNodeBuilder current, UpdateRule.ChromeNodeTransform transform) { foreach (var manualStep in transform(modData, current)) yield return manualStep; @@ -337,13 +340,13 @@ namespace OpenRA.Mods.Common.UpdateRules } /// Checks if node is a removal (has '-' prefix). - public static bool IsRemoval(this MiniYamlNode node) + public static bool IsRemoval(this MiniYamlNodeBuilder node) { return node.Key[0].ToString() == "-"; } /// Renames a yaml key preserving any @suffix. - public static void RenameKey(this MiniYamlNode node, string newKey, bool preserveSuffix = true, bool includeRemovals = true) + public static void RenameKey(this MiniYamlNodeBuilder node, string newKey, bool preserveSuffix = true, bool includeRemovals = true) { var prefix = includeRemovals && node.IsRemoval() ? "-" : ""; var split = node.Key.IndexOf("@", StringComparison.Ordinal); @@ -353,52 +356,52 @@ namespace OpenRA.Mods.Common.UpdateRules node.Key = prefix + newKey; } - public static T NodeValue(this MiniYamlNode node) + public static T NodeValue(this MiniYamlNodeBuilder node) { return FieldLoader.GetValue(node.Key, node.Value.Value); } - public static void ReplaceValue(this MiniYamlNode node, string value) + public static void ReplaceValue(this MiniYamlNodeBuilder node, string value) { node.Value.Value = value; } - public static void AddNode(this MiniYamlNode node, string key, object value) + public static void AddNode(this MiniYamlNodeBuilder node, string key, object value) { - node.Value.Nodes.Add(new MiniYamlNode(key, FieldSaver.FormatValue(value))); + node.Value.Nodes.Add(new MiniYamlNodeBuilder(key, FieldSaver.FormatValue(value))); } - public static void AddNode(this MiniYamlNode node, MiniYamlNode toAdd) + public static void AddNode(this MiniYamlNodeBuilder node, MiniYamlNodeBuilder toAdd) { node.Value.Nodes.Add(toAdd); } - public static void RemoveNode(this MiniYamlNode node, MiniYamlNode toRemove) + public static void RemoveNode(this MiniYamlNodeBuilder node, MiniYamlNodeBuilder toRemove) { node.Value.Nodes.Remove(toRemove); } - public static void MoveNode(this MiniYamlNode node, MiniYamlNode fromNode, MiniYamlNode toNode) + public static void MoveNode(this MiniYamlNodeBuilder node, MiniYamlNodeBuilder fromNode, MiniYamlNodeBuilder toNode) { toNode.Value.Nodes.Add(node); fromNode.Value.Nodes.Remove(node); } - public static void MoveAndRenameNode(this MiniYamlNode node, - MiniYamlNode fromNode, MiniYamlNode toNode, string newKey, bool preserveSuffix = true, bool includeRemovals = true) + public static void MoveAndRenameNode(this MiniYamlNodeBuilder node, + MiniYamlNodeBuilder fromNode, MiniYamlNodeBuilder toNode, string newKey, bool preserveSuffix = true, bool includeRemovals = true) { node.RenameKey(newKey, preserveSuffix, includeRemovals); node.MoveNode(fromNode, toNode); } /// Removes children with keys equal to [match] or [match]@[arbitrary suffix]. - public static int RemoveNodes(this MiniYamlNode node, string match, bool ignoreSuffix = true, bool includeRemovals = true) + public static int RemoveNodes(this MiniYamlNodeBuilder node, string match, bool ignoreSuffix = true, bool includeRemovals = true) { return node.Value.Nodes.RemoveAll(n => n.KeyMatches(match, ignoreSuffix, includeRemovals)); } /// Returns true if the node is of the form [match] or [match]@[arbitrary suffix]. - public static bool KeyMatches(this MiniYamlNode node, string match, bool ignoreSuffix = true, bool includeRemovals = true) + public static bool KeyMatches(this MiniYamlNodeBuilder node, string match, bool ignoreSuffix = true, bool includeRemovals = true) { if (node.Key == null) return false; @@ -416,7 +419,7 @@ namespace OpenRA.Mods.Common.UpdateRules } /// Returns true if the node is of the form [match], [match]@[arbitrary suffix] or [arbitrary suffix]@[match]. - public static bool KeyContains(this MiniYamlNode node, string match, bool ignoreSuffix = true, bool includeRemovals = true) + public static bool KeyContains(this MiniYamlNodeBuilder node, string match, bool ignoreSuffix = true, bool includeRemovals = true) { if (node.Key == null) return false; @@ -431,23 +434,23 @@ namespace OpenRA.Mods.Common.UpdateRules } /// Returns children with keys equal to [match] or [match]@[arbitrary suffix]. - public static IEnumerable ChildrenMatching(this MiniYamlNode node, string match, bool ignoreSuffix = true, bool includeRemovals = true) + public static IEnumerable ChildrenMatching(this MiniYamlNodeBuilder node, string match, bool ignoreSuffix = true, bool includeRemovals = true) { return node.Value.Nodes.Where(n => n.KeyMatches(match, ignoreSuffix, includeRemovals)); } /// Returns children whose keys contain 'match' (optionally in the suffix). - public static IEnumerable ChildrenContaining(this MiniYamlNode node, string match, bool ignoreSuffix = true, bool includeRemovals = true) + public static IEnumerable ChildrenContaining(this MiniYamlNodeBuilder node, string match, bool ignoreSuffix = true, bool includeRemovals = true) { return node.Value.Nodes.Where(n => n.KeyContains(match, ignoreSuffix, includeRemovals)); } - public static MiniYamlNode LastChildMatching(this MiniYamlNode node, string match, bool includeRemovals = true) + public static MiniYamlNodeBuilder LastChildMatching(this MiniYamlNodeBuilder node, string match, bool includeRemovals = true) { return node.ChildrenMatching(match, includeRemovals: includeRemovals).LastOrDefault(); } - public static void RenameChildrenMatching(this MiniYamlNode node, string match, string newKey, bool preserveSuffix = true, bool includeRemovals = true) + public static void RenameChildrenMatching(this MiniYamlNodeBuilder node, string match, string newKey, bool preserveSuffix = true, bool includeRemovals = true) { var matching = node.ChildrenMatching(match); foreach (var m in matching) diff --git a/OpenRA.Mods.Common/UtilityCommands/ResizeMapCommand.cs b/OpenRA.Mods.Common/UtilityCommands/ResizeMapCommand.cs index 9609c1889a..6b4cc2dab9 100644 --- a/OpenRA.Mods.Common/UtilityCommands/ResizeMapCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/ResizeMapCommand.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; +using System.Linq; using OpenRA.FileSystem; namespace OpenRA.Mods.Common.UtilityCommands @@ -68,8 +69,7 @@ namespace OpenRA.Mods.Common.UtilityCommands } } - foreach (var kv in forRemoval) - map.ActorDefinitions.Remove(kv); + map.ActorDefinitions = map.ActorDefinitions.Except(forRemoval).ToArray(); map.Save((IReadWritePackage)map.Package); } diff --git a/OpenRA.Mods.Common/UtilityCommands/UpdateModCommand.cs b/OpenRA.Mods.Common/UtilityCommands/UpdateModCommand.cs index b589540ce5..4883545e53 100644 --- a/OpenRA.Mods.Common/UtilityCommands/UpdateModCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/UpdateModCommand.cs @@ -18,7 +18,7 @@ using OpenRA.Mods.Common.UpdateRules; namespace OpenRA.Mods.Common.UtilityCommands { - using YamlFileSet = List<(IReadWritePackage, string, List)>; + using YamlFileSet = List<(IReadWritePackage, string, List)>; sealed class UpdateModCommand : IUtilityCommand { diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs index 332da27413..ee589fbb36 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/SaveMapLogic.cs @@ -78,7 +78,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic [ObjectCreator.UseCtor] public SaveMapLogic(Widget widget, ModData modData, Map map, Action onSave, Action onExit, - World world, List playerDefinitions, List actorDefinitions) + World world, IReadOnlyCollection playerDefinitions, IReadOnlyCollection actorDefinitions) { var title = widget.Get("TITLE"); title.Text = map.Title; diff --git a/OpenRA.Mods.Common/Widgets/Logic/ServerListLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/ServerListLogic.cs index b1daa1106f..630bd27516 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/ServerListLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/ServerListLogic.cs @@ -486,7 +486,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic if (string.IsNullOrEmpty(bl.Data)) continue; - var game = MiniYaml.FromString(bl.Data)[0].Value; + var game = new MiniYamlBuilder(MiniYaml.FromString(bl.Data)[0].Value); var idNode = game.Nodes.FirstOrDefault(n => n.Key == "Id"); // Skip beacons created by this instance and replace Id by expected int value @@ -499,9 +499,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic if (addressNode != null) addressNode.Value.Value = bl.Address.ToString().Split(':')[0] + ":" + addressNode.Value.Value.Split(':')[1]; - game.Nodes.Add(new MiniYamlNode("Location", "Local Network")); + game.Nodes.Add(new MiniYamlNodeBuilder("Location", "Local Network")); - lanGames.Add(new GameServer(game)); + lanGames.Add(new GameServer(game.Build())); } } catch diff --git a/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs b/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs index 810ea8bb20..67e2f7158d 100644 --- a/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs +++ b/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs @@ -350,6 +350,8 @@ namespace OpenRA.Mods.D2k.UtilityCommands void FillMap() { + var actorNodes = new List(); + var playerNodes = new List(); while (stream.Position < stream.Length) { var tileInfo = stream.ReadUInt16(); @@ -379,9 +381,10 @@ namespace OpenRA.Mods.D2k.UtilityCommands new OwnerInit(kvp.Owner) }; - map.ActorDefinitions.Add(new MiniYamlNode("Actor" + map.ActorDefinitions.Count, a.Save())); + actorNodes.Add(new MiniYamlNode("Actor" + (map.ActorDefinitions.Count + actorNodes.Count), a.Save())); - if (map.PlayerDefinitions.All(x => x.Value.Nodes.Single(y => y.Key == "Name").Value.Value != kvp.Owner)) + if (map.PlayerDefinitions.Concat(playerNodes).All( + x => x.Value.Nodes.Single(y => y.Key == "Name").Value.Value != kvp.Owner)) { var playerInfo = PlayerReferenceDataByPlayerName[kvp.Owner]; var playerReference = new PlayerReference @@ -394,7 +397,7 @@ namespace OpenRA.Mods.D2k.UtilityCommands }; var node = new MiniYamlNode($"{nameof(PlayerReference)}@{kvp.Owner}", FieldSaver.SaveDifferences(playerReference, new PlayerReference())); - map.PlayerDefinitions.Add(node); + playerNodes.Add(node); } if (kvp.Actor == "mpspawn") @@ -402,6 +405,9 @@ namespace OpenRA.Mods.D2k.UtilityCommands } } } + + map.ActorDefinitions = map.ActorDefinitions.Concat(actorNodes).ToArray(); + map.PlayerDefinitions = map.PlayerDefinitions.Concat(playerNodes).ToArray(); } CPos GetCurrentTilePositionOnMap() diff --git a/OpenRA.Test/OpenRA.Game/MiniYamlTest.cs b/OpenRA.Test/OpenRA.Game/MiniYamlTest.cs index 1f6d5557a5..c9b223b276 100644 --- a/OpenRA.Test/OpenRA.Game/MiniYamlTest.cs +++ b/OpenRA.Test/OpenRA.Game/MiniYamlTest.cs @@ -18,6 +18,112 @@ namespace OpenRA.Test [TestFixture] public class MiniYamlTest { + [TestCase(TestName = "Parse tree roundtrips")] + public void TestParseRoundtrip() + { + var yaml = +@"1: +2: Test +3: # Test +4: + 4.1: +5: Test + 5.1: +6: # Test + 6.1: +7: + 7.1.1: + 7.1.2: Test + 7.1.3: # Test +8: Test + 8.1.1: + 8.1.2: Test + 8.1.3: # Test +9: # Test + 9.1.1: + 9.1.2: Test + 9.1.3: # Test +"; + var serialized = MiniYaml.FromString(yaml, discardCommentsAndWhitespace: false).WriteToString(); + Console.WriteLine(); + Assert.That(serialized, Is.EqualTo(yaml)); + } + + [TestCase(TestName = "Parse tree can handle empty lines")] + public void TestParseEmptyLines() + { + var yaml = +@"1: + +2: Test + +3: # Test + +4: + + 4.1: + +5: Test + + 5.1: + +6: # Test + + 6.1: + +7: + + 7.1.1: + + 7.1.2: Test + + 7.1.3: # Test + +8: Test + + 8.1.1: + + 8.1.2: Test + + 8.1.3: # Test + +9: # Test + + 9.1.1: + + 9.1.2: Test + + 9.1.3: # Test + +"; + + var expectedYaml = +@"1: +2: Test +3: +4: + 4.1: +5: Test + 5.1: +6: + 6.1: +7: + 7.1.1: + 7.1.2: Test + 7.1.3: +8: Test + 8.1.1: + 8.1.2: Test + 8.1.3: +9: + 9.1.1: + 9.1.2: Test + 9.1.3: +"; + var serialized = MiniYaml.FromString(yaml).WriteToString(); + Assert.That(serialized, Is.EqualTo(expectedYaml)); + } + [TestCase(TestName = "Mixed tabs & spaces indents")] public void TestIndents() { @@ -220,7 +326,7 @@ Test: var fieldNodes = traitNode.Value.Nodes; var fieldSubNodes = fieldNodes.Single().Value.Nodes; - Assert.IsTrue(fieldSubNodes.Count == 1, "Collection of strings should only contain the overriding subnode."); + Assert.IsTrue(fieldSubNodes.Length == 1, "Collection of strings should only contain the overriding subnode."); Assert.IsTrue(fieldSubNodes.Single(n => n.Key == "StringC").Value.Value == "C", "CollectionOfStrings value has not been set with the correct override value for StringC."); } @@ -254,7 +360,7 @@ Test: var fieldNodes = traitNode.Value.Nodes; var fieldSubNodes = fieldNodes.Single().Value.Nodes; - Assert.IsTrue(fieldSubNodes.Count == 1, "Collection of strings should only contain the overriding subnode."); + Assert.IsTrue(fieldSubNodes.Length == 1, "Collection of strings should only contain the overriding subnode."); Assert.IsTrue(fieldSubNodes.Single(n => n.Key == "StringC").Value.Value == "C", "CollectionOfStrings value has not been set with the correct override value for StringC."); }