diff --git a/OpenRA.Game/MiniYaml.cs b/OpenRA.Game/MiniYaml.cs index d2dd124eb1..ade6ade4b6 100644 --- a/OpenRA.Game/MiniYaml.cs +++ b/OpenRA.Game/MiniYaml.cs @@ -425,19 +425,23 @@ namespace OpenRA static void MergeIntoResolved(MiniYamlNode overrideNode, List existingNodes, HashSet existingNodeKeys, Dictionary tree, ImmutableDictionary inherited) { - if (existingNodeKeys.Add(overrideNode.Key)) + var existingNodeIndex = -1; + MiniYamlNode existingNode = null; + if (!existingNodeKeys.Add(overrideNode.Key)) { - existingNodes.Add(overrideNode); - return; + existingNodeIndex = IndexOfKey(existingNodes, overrideNode.Key); + existingNode = existingNodes[existingNodeIndex]; } - var existingNodeIndex = IndexOfKey(existingNodes, overrideNode.Key); - var existingNode = existingNodes[existingNodeIndex]; - var value = MergePartial(existingNode.Value, overrideNode.Value); + 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); + + if (existingNode != null) + existingNodes[existingNodeIndex] = existingNode.WithValue(value); + else + existingNodes.Add(overrideNode.WithValue(value)); } static List ResolveInherits(MiniYaml node, Dictionary tree, ImmutableDictionary inherited) diff --git a/OpenRA.Test/OpenRA.Game/MiniYamlTest.cs b/OpenRA.Test/OpenRA.Game/MiniYamlTest.cs index 55686d36a9..91db10a101 100644 --- a/OpenRA.Test/OpenRA.Game/MiniYamlTest.cs +++ b/OpenRA.Test/OpenRA.Game/MiniYamlTest.cs @@ -159,6 +159,71 @@ Root2: Assert.That(tabs, Is.EqualTo(mixed)); } + [TestCase(TestName = "Yaml files should be able to remove nodes")] + public void NodeRemoval() + { + const string BaseString = @" +Parent: + Child: + Key: value + -Key: +"; + + const string ResultString = "Parent:\n\tChild:\n"; + var baseYaml = MiniYaml.FromString(BaseString, ""); + + var resultYaml = MiniYaml.Merge(new[] { baseYaml }); + Assert.AreEqual(ResultString, resultYaml.WriteToString()); + } + + [TestCase(TestName = "Merged yaml files should be able to remove nodes")] + public void MergedNodeRemoval() + { + const string BaseString = @" +Parent: + Child: + Key: value +"; + + const string MergeString = @" +Parent: + Child: + -Key: +"; + + const string ResultString = "Parent:\n\tChild:\n"; + var baseYaml = MiniYaml.FromString(BaseString, ""); + var mergeYaml = MiniYaml.FromString(MergeString, ""); + + var resultYaml = MiniYaml.Merge(new[] { baseYaml, mergeYaml }); + Assert.AreEqual(ResultString, resultYaml.WriteToString()); + } + + [TestCase(TestName = "Merged yaml files should be able to remove nodes from inherited parents")] + public void MergedInheritedNodeRemoval() + { + const string BaseString = @" +^Base: + Child: + Key: value +Parent: + Inherits: ^Base +"; + + const string MergeString = @" +Parent: + Child: + -Key: +"; + + const string ResultString = "^Base:\n\tChild:\n\t\tKey: value\nParent:\n\tChild:\n"; + var baseYaml = MiniYaml.FromString(BaseString, ""); + var mergeYaml = MiniYaml.FromString(MergeString, ""); + + var resultYaml = MiniYaml.Merge(new[] { baseYaml, mergeYaml }); + Assert.AreEqual(ResultString, resultYaml.WriteToString()); + } + [TestCase(TestName = "Inheritance and removal can be composed")] public void InheritanceAndRemovalCanBeComposed() { @@ -365,6 +430,33 @@ Test: "CollectionOfStrings value has not been set with the correct override value for StringC."); } + [TestCase(TestName = "Inheritance works for nested nodes")] + public void InheritanceWorksForNestedNodes() + { + const string BaseYaml = @" +^DefaultKey: + Key: value +"; + const string ExtendedYaml = @" +Parent: + Child: + Inherits: ^DefaultKey +"; + + const string ResultString = +@"^DefaultKey: + Key: value +Parent: + Child: + Key: value +"; + var baseYaml = MiniYaml.FromString(BaseYaml, ""); + var mergeYaml = MiniYaml.FromString(ExtendedYaml, ""); + + var resultYaml = MiniYaml.Merge(new[] { baseYaml, mergeYaml }); + Assert.AreEqual(ResultString, resultYaml.WriteToString()); + } + [TestCase(TestName = "Empty lines should count toward line numbers")] public void EmptyLinesShouldCountTowardLineNumbers() { @@ -436,7 +528,7 @@ Test: Assert.That(mergeNode.Nodes[0].Value.Value, Is.EqualTo("override"), "Merge node Child value should be 'override', but is not"); } - [TestCase(TestName = "Duplicated child nodes do not throw if parent does not require merging")] + [TestCase(TestName = "Duplicated child nodes throw merge error if parent does not require merging")] public void TestMergeConflictsNoMerge() { const string BaseYaml = @" @@ -446,10 +538,10 @@ Test: Child: "; - var result = MiniYaml.Merge(new[] { BaseYaml }.Select(s => MiniYaml.FromString(s, ""))); - var testNodes = result.First(n => n.Key == "Test").Value.Nodes; - var mergeNode = testNodes.First(n => n.Key == "Merge").Value; - Assert.That(mergeNode.Nodes.Count, Is.EqualTo(2)); + static void Merge() => MiniYaml.Merge(new[] { BaseYaml }.Select(s => MiniYaml.FromString(s, "test-filename"))); + + Assert.That(Merge, Throws.Exception.TypeOf().And.Message.EqualTo( + "MiniYaml.Merge, duplicate values found for the following keys: Child: [Child (at test-filename:4),Child (at test-filename:5)]")); } [TestCase(TestName = "Duplicated child nodes throw merge error if first parent requires merging")] diff --git a/mods/d2k/sequences/infantry.yaml b/mods/d2k/sequences/infantry.yaml index a681686310..3802275d29 100644 --- a/mods/d2k/sequences/infantry.yaml +++ b/mods/d2k/sequences/infantry.yaml @@ -83,7 +83,6 @@ light_inf: Filename: DATA.R16 Start: 4272 Offset: -30,-24 - -Remap: trooper: Defaults: @@ -169,7 +168,6 @@ trooper: Filename: DATA.R16 Start: 4273 Offset: -30,-24 - -Remap: engineer: Defaults: @@ -244,7 +242,6 @@ engineer: Filename: DATA.R16 Start: 4274 Offset: -30,-24 - -Remap: thumper: Defaults: @@ -304,7 +301,6 @@ thumper: Length: 5 Tick: 480 BlendMode: Multiply - -Remap: die1: Filename: DATA.R16 Frames: 1543, 1550, 1557, 1564, 1571, 1578, 1585, 1592, 1599, 1600, 1601, 1602 @@ -335,7 +331,6 @@ thumper: Filename: DATA.R16 Start: 4275 Offset: -30,-24 - -Remap: fremen: Defaults: @@ -422,7 +417,6 @@ fremen: Filename: DATA.R16 Start: 4293 Offset: -30,-24 - -Remap: saboteur: Defaults: @@ -497,7 +491,6 @@ saboteur: Filename: DATA.R16 Start: 4295 Offset: -30,-24 - -Remap: sardaukar: Defaults: @@ -586,7 +579,6 @@ sardaukar: Filename: DATA.R16 Start: 4276 Offset: -30,-24 - -Remap: grenadier: Defaults: @@ -662,7 +654,6 @@ grenadier: Filename: DATA.R16 Start: 4297 Offset: -30,-24 - -Remap: sandworm: mouth: