diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
index 012fdd7bd8..946bf86782 100644
--- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
+++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
@@ -856,7 +856,7 @@
-
+
@@ -948,6 +948,7 @@
+
diff --git a/OpenRA.Mods.Common/Traits/Render/WithChargeAnimation.cs b/OpenRA.Mods.Common/Traits/Render/WithChargeAnimation.cs
deleted file mode 100644
index b5253aefe2..0000000000
--- a/OpenRA.Mods.Common/Traits/Render/WithChargeAnimation.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-#region Copyright & License Information
-/*
- * Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
- * 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.Linq;
-using OpenRA.Traits;
-
-namespace OpenRA.Mods.Common.Traits.Render
-{
- [Desc("Render trait that varies the animation frame based on the AttackCharges trait's charge level.")]
- class WithChargeAnimationInfo : ConditionalTraitInfo, Requires, Requires
- {
- [SequenceReference]
- [Desc("Sequence to use for the charge levels.")]
- public readonly string Sequence = "active";
-
- [Desc("Which sprite body to play the animation on.")]
- public readonly string Body = "body";
-
- public override object Create(ActorInitializer init) { return new WithChargeAnimation(init.Self, this); }
- }
-
- class WithChargeAnimation : ConditionalTrait
- {
- readonly WithSpriteBody wsb;
- readonly AttackCharges attackCharges;
-
- public WithChargeAnimation(Actor self, WithChargeAnimationInfo info)
- : base(info)
- {
- wsb = self.TraitsImplementing().Single(w => w.Info.Name == info.Body);
- attackCharges = self.Trait();
- }
-
- protected override void TraitEnabled(Actor self)
- {
- var attackChargesInfo = (AttackChargesInfo)attackCharges.Info;
- wsb.DefaultAnimation.PlayFetchIndex(wsb.NormalizeSequence(self, Info.Sequence),
- () => int2.Lerp(0, wsb.DefaultAnimation.CurrentSequence.Length, attackCharges.ChargeLevel, attackChargesInfo.ChargeLevel + 1));
- }
- }
-}
diff --git a/OpenRA.Mods.Common/Traits/Render/WithChargeSpriteBody.cs b/OpenRA.Mods.Common/Traits/Render/WithChargeSpriteBody.cs
new file mode 100644
index 0000000000..151f3b64da
--- /dev/null
+++ b/OpenRA.Mods.Common/Traits/Render/WithChargeSpriteBody.cs
@@ -0,0 +1,65 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
+ * 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.Collections.Generic;
+using System.Linq;
+using OpenRA.Graphics;
+using OpenRA.Mods.Common.Graphics;
+using OpenRA.Traits;
+
+namespace OpenRA.Mods.Common.Traits.Render
+{
+ [Desc("Render trait that varies the sprite body frame based on the AttackCharges trait's charge level.")]
+ public class WithChargeSpriteBodyInfo : WithSpriteBodyInfo, Requires
+ {
+ public override object Create(ActorInitializer init) { return new WithChargeSpriteBody(init, this); }
+
+ public override IEnumerable RenderPreviewSprites(ActorPreviewInitializer init, RenderSpritesInfo rs, string image, int facings, PaletteReference p)
+ {
+ if (!EnabledByDefault)
+ yield break;
+
+ var anim = new Animation(init.World, image);
+ anim.PlayFetchIndex(RenderSprites.NormalizeSequence(anim, init.GetDamageState(), Sequence), () => 0);
+
+ yield return new SpriteActorPreview(anim, () => WVec.Zero, () => 0, p, rs.Scale);
+ }
+ }
+
+ public class WithChargeSpriteBody : WithSpriteBody
+ {
+ readonly AttackCharges attackCharges;
+
+ public WithChargeSpriteBody(ActorInitializer init, WithChargeSpriteBodyInfo info)
+ : base(init, info, () => 0)
+ {
+ attackCharges = init.Self.Trait();
+ ConfigureAnimation(init.Self);
+ }
+
+ void ConfigureAnimation(Actor self)
+ {
+ var attackChargesInfo = (AttackChargesInfo)attackCharges.Info;
+ DefaultAnimation.PlayFetchIndex(NormalizeSequence(self, Info.Sequence),
+ () => int2.Lerp(0, DefaultAnimation.CurrentSequence.Length, attackCharges.ChargeLevel, attackChargesInfo.ChargeLevel + 1));
+ }
+
+ protected override void TraitEnabled(Actor self)
+ {
+ // Do nothing - we just want to disable the default WithSpriteBody implementation
+ }
+
+ public override void CancelCustomAnimation(Actor self)
+ {
+ ConfigureAnimation(self);
+ }
+ }
+}
diff --git a/OpenRA.Mods.Common/Traits/Render/WithSpriteBody.cs b/OpenRA.Mods.Common/Traits/Render/WithSpriteBody.cs
index 27168d6462..a0867d1fdf 100644
--- a/OpenRA.Mods.Common/Traits/Render/WithSpriteBody.cs
+++ b/OpenRA.Mods.Common/Traits/Render/WithSpriteBody.cs
@@ -92,7 +92,7 @@ namespace OpenRA.Mods.Common.Traits.Render
DefaultAnimation.PlayRepeating(NormalizeSequence(self, Info.Sequence));
}
- public void PlayCustomAnimation(Actor self, string name, Action after = null)
+ public virtual void PlayCustomAnimation(Actor self, string name, Action after = null)
{
DefaultAnimation.PlayThen(NormalizeSequence(self, name), () =>
{
@@ -102,12 +102,12 @@ namespace OpenRA.Mods.Common.Traits.Render
});
}
- public void PlayCustomAnimationRepeating(Actor self, string name)
+ public virtual void PlayCustomAnimationRepeating(Actor self, string name)
{
DefaultAnimation.PlayRepeating(NormalizeSequence(self, name));
}
- public void PlayCustomAnimationBackwards(Actor self, string name, Action after = null)
+ public virtual void PlayCustomAnimationBackwards(Actor self, string name, Action after = null)
{
DefaultAnimation.PlayBackwardsThen(NormalizeSequence(self, name), () =>
{
@@ -117,7 +117,7 @@ namespace OpenRA.Mods.Common.Traits.Render
});
}
- public void CancelCustomAnimation(Actor self)
+ public virtual void CancelCustomAnimation(Actor self)
{
DefaultAnimation.PlayRepeating(NormalizeSequence(self, Info.Sequence));
}
diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20180923/ReplacedWithChargeAnimation.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20180923/ReplacedWithChargeAnimation.cs
new file mode 100644
index 0000000000..1539ae5867
--- /dev/null
+++ b/OpenRA.Mods.Common/UpdateRules/Rules/20180923/ReplacedWithChargeAnimation.cs
@@ -0,0 +1,73 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
+ * 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.Collections.Generic;
+using System.Linq;
+
+namespace OpenRA.Mods.Common.UpdateRules.Rules
+{
+ public class ReplacedWithChargeAnimation : UpdateRule
+ {
+ public override string Name { get { return "Replaced WithChargeAnimation with WithChargeSpriteBody"; } }
+ public override string Description
+ {
+ get
+ {
+ return "Replaced WithChargeAnimation with WithChargeSpriteBody.";
+ }
+ }
+
+ readonly List locations = new List();
+
+ public override IEnumerable AfterUpdate(ModData modData)
+ {
+ if (locations.Any())
+ yield return "WithChargeAnimation has been replaced by WithChargeSpriteBody.\n" +
+ "You may need to disable/remove any previous (including inherited) *SpriteBody traits\n" +
+ "on the following actors:\n" +
+ UpdateUtils.FormatMessageList(locations);
+
+ locations.Clear();
+ }
+
+ public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode)
+ {
+ var chargeAnims = actorNode.ChildrenMatching("WithChargeAnimation");
+
+ foreach (var ca in chargeAnims)
+ {
+ // If it's a trait removal, we only rename it.
+ if (ca.IsRemoval())
+ {
+ ca.RenameKey("WithChargeSpriteBody");
+ continue;
+ }
+
+ var sequence = ca.LastChildMatching("Sequence");
+ var body = ca.LastChildMatching("Body");
+
+ if (sequence == null)
+ {
+ var newSequenceNode = new MiniYamlNode("Sequence", "active");
+ ca.AddNode(newSequenceNode);
+ }
+
+ if (body != null)
+ ca.RemoveNode(body);
+
+ ca.RenameKey("WithChargeSpriteBody");
+ locations.Add("{0} ({1})".F(actorNode.Key, ca.Location.Filename));
+ }
+
+ yield break;
+ }
+ }
+}
diff --git a/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs b/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs
index e5b647f04b..cf74f08295 100644
--- a/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs
+++ b/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs
@@ -111,6 +111,7 @@ namespace OpenRA.Mods.Common.UpdateRules
new DefineLevelUpImageDefault(),
new RemovedAutoCarryallCircleTurnSpeed(),
new RemoveAttackIgnoresVisibility(),
+ new ReplacedWithChargeAnimation(),
})
};
diff --git a/OpenRA.Mods.Common/UpdateRules/UpdateUtils.cs b/OpenRA.Mods.Common/UpdateRules/UpdateUtils.cs
index 28ac004b8d..1fbb1747ba 100644
--- a/OpenRA.Mods.Common/UpdateRules/UpdateUtils.cs
+++ b/OpenRA.Mods.Common/UpdateRules/UpdateUtils.cs
@@ -237,10 +237,16 @@ namespace OpenRA.Mods.Common.UpdateRules
file.Item1.Update(file.Item2, Encoding.UTF8.GetBytes(file.Item3.WriteToString()));
}
+ /// Checks if node is a removal (has '-' prefix)
+ public static bool IsRemoval(this MiniYamlNode 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)
{
- var prefix = includeRemovals && node.Key[0].ToString() == "-" ? "-" : "";
+ var prefix = includeRemovals && node.IsRemoval() ? "-" : "";
var split = node.Key.IndexOf("@", StringComparison.Ordinal);
if (preserveSuffix && split > -1)
node.Key = prefix + newKey + node.Key.Substring(split);
@@ -298,7 +304,7 @@ namespace OpenRA.Mods.Common.UpdateRules
if (node.Key == null)
return false;
- var prefix = includeRemovals && node.Key[0].ToString() == "-" ? "-" : "";
+ var prefix = includeRemovals && node.IsRemoval() ? "-" : "";
if (node.Key == prefix + match)
return true;
@@ -310,12 +316,33 @@ namespace OpenRA.Mods.Common.UpdateRules
return atPosition > 0 && node.Key.Substring(0, atPosition) == prefix + match;
}
+ /// Returns true if the node is of the form <*match*>, <*match*>@arbitrary or @*match*
+ public static bool KeyContains(this MiniYamlNode node, string match, bool ignoreSuffix = true, bool includeRemovals = true)
+ {
+ if (node.Key == null)
+ return false;
+
+ var atPosition = node.Key.IndexOf('@');
+ var relevantPart = ignoreSuffix && atPosition > 0 ? node.Key.Substring(0, atPosition) : node.Key;
+
+ if (relevantPart.Contains(match) && (includeRemovals || !node.IsRemoval()))
+ return true;
+
+ return false;
+ }
+
/// 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)
{
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)
+ {
+ return node.Value.Nodes.Where(n => n.KeyContains(match, ignoreSuffix, includeRemovals));
+ }
+
public static MiniYamlNode LastChildMatching(this MiniYamlNode node, string match, bool includeRemovals = true)
{
return node.ChildrenMatching(match, includeRemovals: includeRemovals).LastOrDefault();
diff --git a/mods/cnc/rules/structures.yaml b/mods/cnc/rules/structures.yaml
index 3240b456b6..141be47808 100644
--- a/mods/cnc/rules/structures.yaml
+++ b/mods/cnc/rules/structures.yaml
@@ -923,8 +923,9 @@ OBLI:
Range: 8c0
WithBuildingBib:
HasMinibib: Yes
- WithChargeAnimation:
- RequiresCondition: !build-incomplete
+ -WithSpriteBody:
+ WithChargeSpriteBody:
+ Sequence: active
Armament:
Weapon: Laser
LocalOffset: 0,-85,1280