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."); }