diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20221203/RemoveNegativeSequenceLength.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20221203/RemoveNegativeSequenceLength.cs new file mode 100644 index 0000000000..8f7df1905d --- /dev/null +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20221203/RemoveNegativeSequenceLength.cs @@ -0,0 +1,92 @@ +#region Copyright & License Information +/* + * Copyright (c) The OpenRA Developers and Contributors + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace OpenRA.Mods.Common.UpdateRules.Rules +{ + public class RemoveNegativeSequenceLength : UpdateRule + { + public override string Name => "Negative sequence length is no longer allowed."; + + public override string Description => "Negative sequence length is no longer allowed, define individual frames in reverse instead."; + + List resolvedImagesNodes; + + public override IEnumerable BeforeUpdateSequences(ModData modData, List resolvedImagesNodes) + { + this.resolvedImagesNodes = resolvedImagesNodes; + yield break; + } + + readonly Queue actionQueue = new(); + + static MiniYamlNode GetNode(string key, MiniYamlNode node, MiniYamlNode defaultNode) + { + return node.LastChildMatching(key, includeRemovals: false) ?? defaultNode?.LastChildMatching(key, includeRemovals: false); + } + + public override IEnumerable UpdateSequenceNode(ModData modData, MiniYamlNode sequenceNode) + { + var defaultNode = sequenceNode.LastChildMatching("Defaults"); + var defaultLengthNode = defaultNode == null ? null : GetNode("Length", defaultNode, null); + foreach (var node in sequenceNode.Value.Nodes) + { + var lengthNode = node.LastChildMatching("Length"); + if (lengthNode != null && lengthNode.IsRemoval()) + continue; + + lengthNode ??= defaultLengthNode; + if (lengthNode == null || lengthNode.Value.Value == "*") + continue; + + var length = FieldLoader.GetValue(lengthNode.Key, lengthNode.Value.Value); + if (length >= 0) + continue; + + var resolvedImage = resolvedImagesNodes.First(n => n.Key == sequenceNode.Key); + var resolvedNode = resolvedImage.LastChildMatching(node.Key); + var resolvedDefaultNode = resolvedNode.Value.Value == "Defaults" ? null : resolvedImage.LastChildMatching("Defaults"); + + var startNode = GetNode("Start", node, defaultNode) ?? GetNode("Start", resolvedNode, resolvedDefaultNode); + if (startNode == null) + { + actionQueue.Enqueue(() => node.RemoveNodes("Length")); + continue; + } + + var facingsNode = GetNode("Facings", node, defaultNode) ?? GetNode("Facings", resolvedNode, resolvedDefaultNode); + var facings = facingsNode == null ? 1 : FieldLoader.GetValue(facingsNode.Key, facingsNode.Value.Value); + + length = -length * facings; + var start = FieldLoader.GetValue(startNode.Key, startNode.Value.Value) - 1; + + var frames = new int[length]; + for (var i = 0; i < length; i++) + frames[i] = start - i; + + actionQueue.Enqueue(() => + { + node.RemoveNodes("Start"); + node.RemoveNodes("Length"); + node.AddNode("Frames", frames); + }); + } + + while (actionQueue.Count != 0) + actionQueue.Dequeue().Invoke(); + + yield break; + } + } +} diff --git a/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs b/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs index 3f4cd99aa4..4d71409702 100644 --- a/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs +++ b/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs @@ -97,6 +97,7 @@ namespace OpenRA.Mods.Common.UpdateRules new RenameMcvCrateAction(), new RemoveSequenceHasEmbeddedPalette(), new RenameContrailWidth(), + new RemoveNegativeSequenceLength(), }) };