diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
index 925629b8ac..c6f815cdf8 100644
--- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
+++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
@@ -627,6 +627,7 @@
+
diff --git a/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs b/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs
new file mode 100644
index 0000000000..da624216fc
--- /dev/null
+++ b/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs
@@ -0,0 +1,108 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2015 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. For more information,
+ * see COPYING.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using OpenRA.Graphics;
+using OpenRA.Traits;
+
+namespace OpenRA.Mods.Common.Traits
+{
+ [Desc("Displays a custom animation if conditions are satisfied.")]
+ public class WithDecorationInfo : UpgradableTraitInfo, ITraitInfo
+ {
+ [Desc("Image used for this decoration. Defaults to the actor's type.")]
+ public readonly string Image = null;
+
+ [Desc("Sequence used for this decoration (can be animated).")]
+ public readonly string Sequence = null;
+
+ [Desc("Palette to render the sprite in. Reference the world actor's PaletteFrom* traits.")]
+ public readonly string Palette = "chrome";
+
+ [Desc("Pixel offset relative to the top-left point of the actor's bounds.")]
+ public readonly int2 Offset = int2.Zero;
+
+ [Desc("The Z offset to apply when rendering this decoration.")]
+ public readonly int ZOffset = 1;
+
+ [Desc("Visual scale of the image.")]
+ public readonly float Scale = 1f;
+
+ [Desc("Should this be visible to allied players?")]
+ public readonly bool ShowToAllies = true;
+
+ [Desc("Should this be visible to enemy players?")]
+ public readonly bool ShowToEnemies = false;
+
+ public virtual object Create(ActorInitializer init) { return new WithDecoration(init.Self, this); }
+ }
+
+ public class WithDecoration : UpgradableTrait, IRender
+ {
+ readonly WithDecorationInfo info;
+ readonly string image;
+ readonly Animation anim;
+
+ public WithDecoration(Actor self, WithDecorationInfo info)
+ : base(info)
+ {
+ this.info = info;
+ image = info.Image ?? self.Info.Name;
+ anim = new Animation(self.World, image);
+ anim.Paused = () => self.World.Paused;
+ anim.PlayRepeating(info.Sequence);
+ }
+
+ public virtual bool ShouldRender(Actor self) { return true; }
+
+ public IEnumerable Render(Actor self, WorldRenderer wr)
+ {
+ if (IsTraitDisabled)
+ yield break;
+
+ if (self.IsDead || !self.IsInWorld)
+ yield break;
+
+ if (anim == null)
+ yield break;
+
+ var allied = self.Owner.IsAlliedWith(self.World.RenderPlayer);
+
+ if (!allied && !info.ShowToEnemies)
+ yield break;
+
+ if (allied && !info.ShowToAllies)
+ yield break;
+
+ if (!ShouldRender(self))
+ yield break;
+
+ if (self.World.FogObscures(self))
+ yield break;
+
+ var pxPos = wr.ScreenPxPosition(self.CenterPosition);
+ var actorBounds = self.Bounds;
+ actorBounds.Offset(pxPos.X, pxPos.Y);
+ pxPos = new int2(actorBounds.Left, actorBounds.Top);
+
+ var img = anim.Image;
+ var imgSize = img.Size.ToInt2();
+ pxPos = pxPos.WithX(pxPos.X + imgSize.X / 2).WithY(pxPos.Y + imgSize.Y / 2);
+
+ pxPos += info.Offset;
+ var renderPos = wr.Position(pxPos);
+
+ anim.Tick();
+
+ yield return new SpriteRenderable(img, renderPos, WVec.Zero, info.ZOffset, wr.Palette(info.Palette), info.Scale, true);
+ }
+ }
+}
diff --git a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj
index 28bf2f6044..62e1e86805 100644
--- a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj
+++ b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj
@@ -97,6 +97,7 @@
+
diff --git a/OpenRA.Mods.D2k/Traits/Render/WithDecorationCarryable.cs b/OpenRA.Mods.D2k/Traits/Render/WithDecorationCarryable.cs
new file mode 100644
index 0000000000..68f3369833
--- /dev/null
+++ b/OpenRA.Mods.D2k/Traits/Render/WithDecorationCarryable.cs
@@ -0,0 +1,38 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2015 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. For more information,
+ * see COPYING.
+ */
+#endregion
+
+using System;
+using OpenRA.Mods.Common.Traits;
+using OpenRA.Traits;
+
+namespace OpenRA.Mods.D2k.Traits
+{
+ [Desc("Displays a sprite when the carryable actor is waiting for pickup.")]
+ public class WithDecorationCarryableInfo : WithDecorationInfo, Requires
+ {
+ public override object Create(ActorInitializer init) { return new WithDecorationCarryable(init.Self, this); }
+ }
+
+ public class WithDecorationCarryable : WithDecoration
+ {
+ readonly Carryable carryable;
+
+ public WithDecorationCarryable(Actor self, WithDecorationCarryableInfo info)
+ : base(self, info)
+ {
+ carryable = self.Trait();
+ }
+
+ public override bool ShouldRender(Actor self)
+ {
+ return carryable.Reserved;
+ }
+ }
+}
diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj
index 9e1e6d43b0..01f94612b9 100644
--- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj
+++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj
@@ -106,6 +106,7 @@
+
diff --git a/OpenRA.Mods.RA/Traits/Render/WithDecorationDisguised.cs b/OpenRA.Mods.RA/Traits/Render/WithDecorationDisguised.cs
new file mode 100644
index 0000000000..b08d6697dd
--- /dev/null
+++ b/OpenRA.Mods.RA/Traits/Render/WithDecorationDisguised.cs
@@ -0,0 +1,42 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2015 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. For more information,
+ * see COPYING.
+ */
+#endregion
+
+using System;
+using OpenRA.Mods.Common.Traits;
+using OpenRA.Traits;
+
+namespace OpenRA.Mods.RA.Traits
+{
+ public class WithDecorationDisguisedInfo : WithDecorationInfo, Requires
+ {
+ [Desc("Require an active disguise to render this decoration?")]
+ public readonly bool RequireDisguise = true;
+
+ public override object Create(ActorInitializer init) { return new WithDecorationDisguised(init.Self, this); }
+ }
+
+ public class WithDecorationDisguised : WithDecoration
+ {
+ readonly WithDecorationDisguisedInfo info;
+ readonly Disguise disguise;
+
+ public WithDecorationDisguised(Actor self, WithDecorationDisguisedInfo info)
+ : base(self, info)
+ {
+ this.info = info;
+ disguise = self.Trait();
+ }
+
+ public override bool ShouldRender(Actor self)
+ {
+ return !info.RequireDisguise || disguise.Disguised;
+ }
+ }
+}
\ No newline at end of file
diff --git a/mods/d2k/rules/vehicles.yaml b/mods/d2k/rules/vehicles.yaml
index 91b43bded4..1c178d084d 100644
--- a/mods/d2k/rules/vehicles.yaml
+++ b/mods/d2k/rules/vehicles.yaml
@@ -62,6 +62,10 @@ harvester:
SearchFromProcRadius: 24
SearchFromOrderRadius: 12
Carryable:
+ WithDecorationCarryable:
+ Image: pips
+ Sequence: pickup-indicator
+ Offset: -12, -12
Health:
HP: 1000
Armor:
diff --git a/mods/d2k/sequences/misc.yaml b/mods/d2k/sequences/misc.yaml
index 3b7c17adbb..ce15cac2cc 100644
--- a/mods/d2k/sequences/misc.yaml
+++ b/mods/d2k/sequences/misc.yaml
@@ -112,6 +112,8 @@ pips:
groups: DATA.R8
Start: 17
Length: 10
+ pickup-indicator: DATA.R8
+ Start: 112
tag-primary: DATA.R8
Start: 110
pip-empty: DATA.R8