diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
index 9822fde854..242b6af3e0 100644
--- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
+++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
@@ -474,6 +474,8 @@
+
+
diff --git a/OpenRA.Mods.Common/Traits/Render/SelectionDecorations.cs b/OpenRA.Mods.Common/Traits/Render/SelectionDecorations.cs
index 9524e8cc33..5fe350c477 100644
--- a/OpenRA.Mods.Common/Traits/Render/SelectionDecorations.cs
+++ b/OpenRA.Mods.Common/Traits/Render/SelectionDecorations.cs
@@ -36,9 +36,6 @@ namespace OpenRA.Mods.Common.Traits.Render
public readonly string Image = "pips";
- [Desc("Sprite sequence used to render the control group 0-9 numbers.")]
- [SequenceReference("Image")] public readonly string GroupSequence = "groups";
-
public object Create(ActorInitializer init) { return new SelectionDecorations(init.Self, this); }
public int[] SelectionBoxBounds { get { return VisualBounds; } }
@@ -97,29 +94,13 @@ namespace OpenRA.Mods.Common.Traits.Render
var b = self.VisualBounds;
var pos = wr.ScreenPxPosition(self.CenterPosition);
- var tl = wr.Viewport.WorldToViewPx(pos + new int2(b.Left, b.Top));
var bl = wr.Viewport.WorldToViewPx(pos + new int2(b.Left, b.Bottom));
var pal = wr.Palette(Info.Palette);
- foreach (var r in DrawControlGroup(wr, self, tl, pal))
- yield return r;
-
foreach (var r in DrawPips(wr, self, bl, pal))
yield return r;
}
- IEnumerable DrawControlGroup(WorldRenderer wr, Actor self, int2 basePosition, PaletteReference palette)
- {
- var group = self.World.Selection.GetControlGroupForActor(self);
- if (group == null)
- yield break;
-
- pipImages.PlayFetchIndex(Info.GroupSequence, () => (int)group);
-
- var pos = basePosition - (0.5f * pipImages.Image.Size.XY).ToInt2() + new int2(9, 5);
- yield return new UISpriteRenderable(pipImages.Image, self.CenterPosition, pos, 0, palette, 1f);
- }
-
IEnumerable DrawPips(WorldRenderer wr, Actor self, int2 basePosition, PaletteReference palette)
{
var pipSources = self.TraitsImplementing();
diff --git a/OpenRA.Mods.Common/Traits/Render/WithSpriteControlGroupDecoration.cs b/OpenRA.Mods.Common/Traits/Render/WithSpriteControlGroupDecoration.cs
new file mode 100644
index 0000000000..5610ad523b
--- /dev/null
+++ b/OpenRA.Mods.Common/Traits/Render/WithSpriteControlGroupDecoration.cs
@@ -0,0 +1,105 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2016 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.Drawing;
+using System.Linq;
+using OpenRA.Graphics;
+using OpenRA.Mods.Common.Graphics;
+using OpenRA.Traits;
+
+namespace OpenRA.Mods.Common.Traits.Render
+{
+ [Desc("Renders Ctrl groups using pixel art.")]
+ public class WithSpriteControlGroupDecorationInfo : ITraitInfo
+ {
+ [PaletteReference] public readonly string Palette = "chrome";
+
+ public readonly string Image = "pips";
+
+ [Desc("Sprite sequence used to render the control group 0-9 numbers.")]
+ [SequenceReference("Image")] public readonly string GroupSequence = "groups";
+
+ [Desc("Point in the actor's selection box used as reference for offsetting the decoration image. " +
+ "Possible values are combinations of Center, Top, Bottom, Left, Right.")]
+ public readonly ReferencePoints ReferencePoint = ReferencePoints.Top | ReferencePoints.Left;
+
+ public object Create(ActorInitializer init) { return new WithSpriteControlGroupDecoration(init.Self, this); }
+ }
+
+ public class WithSpriteControlGroupDecoration : IPostRenderSelection
+ {
+ public readonly WithSpriteControlGroupDecorationInfo Info;
+
+ readonly Actor self;
+ readonly Animation pipImages;
+
+ public WithSpriteControlGroupDecoration(Actor self, WithSpriteControlGroupDecorationInfo info)
+ {
+ this.self = self;
+ Info = info;
+
+ pipImages = new Animation(self.World, Info.Image);
+ }
+
+ IEnumerable IPostRenderSelection.RenderAfterWorld(WorldRenderer wr)
+ {
+ if (self.World.FogObscures(self))
+ yield break;
+
+ if (self.Owner != wr.World.LocalPlayer)
+ yield break;
+
+ var pal = wr.Palette(Info.Palette);
+ foreach (var r in DrawControlGroup(wr, self, pal))
+ yield return r;
+ }
+
+ IEnumerable DrawControlGroup(WorldRenderer wr, Actor self, PaletteReference palette)
+ {
+ var group = self.World.Selection.GetControlGroupForActor(self);
+ if (group == null)
+ yield break;
+
+ pipImages.PlayFetchIndex(Info.GroupSequence, () => (int)group);
+
+ var bounds = self.VisualBounds;
+ var halfSize = (0.5f * pipImages.Image.Size.XY).ToInt2();
+
+ var boundsOffset = new int2(bounds.Left + bounds.Right, bounds.Top + bounds.Bottom) / 2;
+ var sizeOffset = -halfSize;
+ if (Info.ReferencePoint.HasFlag(ReferencePoints.Top))
+ {
+ boundsOffset -= new int2(0, bounds.Height / 2);
+ sizeOffset += new int2(0, halfSize.Y);
+ }
+ else if (Info.ReferencePoint.HasFlag(ReferencePoints.Bottom))
+ {
+ boundsOffset += new int2(0, bounds.Height / 2);
+ sizeOffset -= new int2(0, halfSize.Y);
+ }
+
+ if (Info.ReferencePoint.HasFlag(ReferencePoints.Left))
+ {
+ boundsOffset -= new int2(bounds.Width / 2, 0);
+ sizeOffset += new int2(halfSize.X, 0);
+ }
+ else if (Info.ReferencePoint.HasFlag(ReferencePoints.Right))
+ {
+ boundsOffset += new int2(bounds.Width / 2, 0);
+ sizeOffset -= new int2(halfSize.X, 0);
+ }
+
+ var pxPos = wr.Viewport.WorldToViewPx(wr.ScreenPxPosition(self.CenterPosition) + boundsOffset) + sizeOffset;
+ yield return new UISpriteRenderable(pipImages.Image, self.CenterPosition, pxPos, 0, palette, 1f);
+ }
+ }
+}
diff --git a/OpenRA.Mods.Common/Traits/Render/WithTextControlGroupDecoration.cs b/OpenRA.Mods.Common/Traits/Render/WithTextControlGroupDecoration.cs
new file mode 100644
index 0000000000..bc2c6a99ca
--- /dev/null
+++ b/OpenRA.Mods.Common/Traits/Render/WithTextControlGroupDecoration.cs
@@ -0,0 +1,126 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2016 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.Drawing;
+using OpenRA.Graphics;
+using OpenRA.Mods.Common.Graphics;
+using OpenRA.Traits;
+
+namespace OpenRA.Mods.Common.Traits.Render
+{
+ [Desc("Renders Ctrl groups using typeface.")]
+ public class WithTextControlGroupDecorationInfo : ITraitInfo, IRulesetLoaded
+ {
+ public readonly string Font = "TinyBold";
+
+ [Desc("Display in this color when not using the player color.")]
+ public readonly Color Color = Color.White;
+
+ [Desc("Use the player color of the current owner.")]
+ public readonly bool UsePlayerColor = false;
+
+ [Desc("The Z offset to apply when rendering this decoration.")]
+ public readonly int ZOffset = 1;
+
+ [Desc("Point in the actor's selection box used as reference for offsetting the decoration image. " +
+ "Possible values are combinations of Center, Top, Bottom, Left, Right.")]
+ public readonly ReferencePoints ReferencePoint = ReferencePoints.Bottom | ReferencePoints.Left;
+
+ [Desc("Manual offset in screen pixel.")]
+ public readonly int2 ScreenOffset = new int2(2, -2);
+
+ void IRulesetLoaded.RulesetLoaded(Ruleset rules, ActorInfo info)
+ {
+ if (!Game.ModData.Manifest.Fonts.ContainsKey(Font))
+ throw new YamlException("Font '{0}' is not listed in the mod.yaml's Fonts section".F(Font));
+ }
+
+ public object Create(ActorInitializer init) { return new WithTextControlGroupDecoration(init.Self, this); }
+ }
+
+ public class WithTextControlGroupDecoration : IPostRenderSelection, INotifyCapture
+ {
+ readonly WithTextControlGroupDecorationInfo info;
+ readonly SpriteFont font;
+ readonly Actor self;
+
+ Color color;
+
+ public WithTextControlGroupDecoration(Actor self, WithTextControlGroupDecorationInfo info)
+ {
+ this.self = self;
+ this.info = info;
+
+ if (!Game.Renderer.Fonts.TryGetValue(info.Font, out font))
+ throw new YamlException("Font '{0}' is not listed in the mod.yaml's Fonts section".F(info.Font));
+
+ color = info.UsePlayerColor ? self.Owner.Color.RGB : info.Color;
+ }
+
+ IEnumerable IPostRenderSelection.RenderAfterWorld(WorldRenderer wr)
+ {
+ if (self.World.FogObscures(self))
+ yield break;
+
+ if (self.Owner != wr.World.LocalPlayer)
+ yield break;
+
+ foreach (var r in DrawControlGroup(wr, self))
+ yield return r;
+ }
+
+ IEnumerable DrawControlGroup(WorldRenderer wr, Actor self)
+ {
+ var group = self.World.Selection.GetControlGroupForActor(self);
+ if (group == null)
+ yield break;
+
+ var bounds = self.VisualBounds;
+ var number = group.Value.ToString();
+ var halfSize = font.Measure(number) / 2;
+
+ var boundsOffset = new int2(bounds.Left + bounds.Right, bounds.Top + bounds.Bottom) / 2;
+ var sizeOffset = new int2();
+ if (info.ReferencePoint.HasFlag(ReferencePoints.Top))
+ {
+ boundsOffset -= new int2(0, bounds.Height / 2);
+ sizeOffset += new int2(0, halfSize.Y);
+ }
+ else if (info.ReferencePoint.HasFlag(ReferencePoints.Bottom))
+ {
+ boundsOffset += new int2(0, bounds.Height / 2);
+ sizeOffset -= new int2(0, halfSize.Y);
+ }
+
+ if (info.ReferencePoint.HasFlag(ReferencePoints.Left))
+ {
+ boundsOffset -= new int2(bounds.Width / 2, 0);
+ sizeOffset += new int2(halfSize.X, 0);
+ }
+ else if (info.ReferencePoint.HasFlag(ReferencePoints.Right))
+ {
+ boundsOffset += new int2(bounds.Width / 2, 0);
+ sizeOffset -= new int2(halfSize.X, 0);
+ }
+
+ var screenPos = wr.ScreenPxPosition(self.CenterPosition) + boundsOffset + sizeOffset + info.ScreenOffset;
+
+ yield return new TextRenderable(font, wr.ProjectedPosition(screenPos), info.ZOffset, color, number);
+ }
+
+ void INotifyCapture.OnCapture(Actor self, Actor captor, Player oldOwner, Player newOwner)
+ {
+ if (info.UsePlayerColor)
+ color = newOwner.Color.RGB;
+ }
+ }
+}
diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs
index f764b176da..ecc4c7973f 100644
--- a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs
+++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs
@@ -270,6 +270,16 @@ namespace OpenRA.Mods.Common.UtilityCommands
}
}
+ if (engineVersion < 20160717)
+ {
+ if (depth == 0)
+ {
+ var selectionDecorations = node.Value.Nodes.FirstOrDefault(n => n.Key == "SelectionDecorations");
+ if (selectionDecorations != null)
+ node.Value.Nodes.Add(selectionDecorations = new MiniYamlNode("WithSpriteControlGroup", ""));
+ }
+ }
+
UpgradeActorRules(modData, engineVersion, ref node.Value.Nodes, node, depth + 1);
}
diff --git a/mods/cnc/rules/defaults.yaml b/mods/cnc/rules/defaults.yaml
index 180320f53b..aafad4d330 100644
--- a/mods/cnc/rules/defaults.yaml
+++ b/mods/cnc/rules/defaults.yaml
@@ -69,6 +69,7 @@
Beach: 50
TurnSpeed: 5
SelectionDecorations:
+ WithSpriteControlGroupDecoration:
Selectable:
Bounds: 24,24
Targetable:
@@ -133,6 +134,7 @@
UpgradeTypes: airborne
UpgradeMinEnabledLevel: 1
SelectionDecorations:
+ WithSpriteControlGroupDecoration:
Selectable:
Bounds: 24,24
Aircraft:
@@ -191,6 +193,8 @@
PathingCost: 300
Beach: 80
SelectionDecorations:
+ WithSpriteControlGroupDecoration:
+ ReferencePoint: Top
Selectable:
Bounds: 12,17,0,-6
Targetable:
@@ -351,6 +355,7 @@
Beach: 80
Voice: Move
SelectionDecorations:
+ WithSpriteControlGroupDecoration:
Selectable:
Bounds: 24,24
Targetable:
@@ -398,6 +403,7 @@
BlueTiberium: 100
Beach: 60
SelectionDecorations:
+ WithSpriteControlGroupDecoration:
Selectable:
Bounds: 24,24
Targetable:
@@ -463,6 +469,7 @@
TerrainSpeeds:
Water: 100
SelectionDecorations:
+ WithSpriteControlGroupDecoration:
Selectable:
Targetable:
TargetTypes: Ground, Water
@@ -486,6 +493,7 @@
Inherits@2: ^SpriteActor
Huntable:
SelectionDecorations:
+ WithSpriteControlGroupDecoration:
Selectable:
Priority: 3
Targetable:
diff --git a/mods/d2k/rules/defaults.yaml b/mods/d2k/rules/defaults.yaml
index 255f4e5f86..4396be601b 100644
--- a/mods/d2k/rules/defaults.yaml
+++ b/mods/d2k/rules/defaults.yaml
@@ -74,6 +74,7 @@
Dune: 50
TurnSpeed: 5
SelectionDecorations:
+ WithSpriteControlGroupDecoration:
Selectable:
Bounds: 32,32
Targetable:
@@ -183,6 +184,7 @@
Dune: 80
Rough: 80
SelectionDecorations:
+ WithSpriteControlGroupDecoration:
Selectable:
Bounds: 12,20,0,-4
Targetable:
@@ -245,6 +247,7 @@
Inherits@2: ^SpriteActor
Huntable:
SelectionDecorations:
+ WithSpriteControlGroupDecoration:
Selectable:
Priority: 2
RevealsShroud:
diff --git a/mods/ra/rules/defaults.yaml b/mods/ra/rules/defaults.yaml
index a50aed2194..105f5cf9fb 100644
--- a/mods/ra/rules/defaults.yaml
+++ b/mods/ra/rules/defaults.yaml
@@ -82,6 +82,7 @@
Beach: 40
TurnSpeed: 5
SelectionDecorations:
+ WithSpriteControlGroupDecoration:
Selectable:
Bounds: 24, 24
Targetable:
@@ -189,6 +190,8 @@
Gems: 80
Beach: 80
SelectionDecorations:
+ WithSpriteControlGroupDecoration:
+ ReferencePoint: Top
Selectable:
Bounds: 12,18,0,-8
Targetable:
@@ -316,6 +319,7 @@
TerrainSpeeds:
Water: 100
SelectionDecorations:
+ WithSpriteControlGroupDecoration:
Selectable:
Bounds: 24,24
Targetable:
@@ -357,6 +361,7 @@
AppearsOnRadar:
UseLocation: true
SelectionDecorations:
+ WithSpriteControlGroupDecoration:
Selectable:
Bounds: 24,24
Aircraft:
@@ -420,6 +425,7 @@
Inherits@2: ^IronCurtainable
Inherits@3: ^SpriteActor
SelectionDecorations:
+ WithSpriteControlGroupDecoration:
Selectable:
Priority: 3
Targetable:
diff --git a/mods/ts/bits/pipsra.shp b/mods/ts/bits/pipsra.shp
deleted file mode 100644
index da81c9bf59..0000000000
Binary files a/mods/ts/bits/pipsra.shp and /dev/null differ
diff --git a/mods/ts/rules/defaults.yaml b/mods/ts/rules/defaults.yaml
index 36e1ad260d..7cb6c4bc73 100644
--- a/mods/ts/rules/defaults.yaml
+++ b/mods/ts/rules/defaults.yaml
@@ -93,6 +93,7 @@
Inherits@2: ^SpriteActor
Inherits@3: ^Cloakable
Huntable:
+ WithTextControlGroupDecoration:
SelectionDecorations:
Palette: pips
Selectable:
@@ -268,6 +269,7 @@
BlueTiberium: 90
PathingCost: 90
Veins: 50
+ WithTextControlGroupDecoration:
SelectionDecorations:
Palette: pips
Selectable:
@@ -439,6 +441,7 @@
Voice: Move
Selectable:
Bounds: 40,24
+ WithTextControlGroupDecoration:
SelectionDecorations:
Palette: pips
Voiced:
@@ -540,6 +543,7 @@
UpgradeTypes: airborne
UpgradeMinEnabledLevel: 1
Selectable:
+ WithTextControlGroupDecoration:
SelectionDecorations:
Palette: pips
Aircraft:
@@ -633,6 +637,7 @@
Tiberium: 100
BlueTiberium: 100
Veins: 100
+ WithTextControlGroupDecoration:
SelectionDecorations:
Palette: pips
Selectable:
@@ -747,6 +752,7 @@
TerrainTypes: Clear, Road, DirtRoad, Rough
WithSpriteBody:
WithMakeAnimation:
+ WithTextControlGroupDecoration:
SelectionDecorations:
Palette: pips
Selectable:
@@ -784,6 +790,7 @@
Range: 5c0
Selectable:
Bounds: 40,24
+ WithTextControlGroupDecoration:
SelectionDecorations:
Palette: pips
Voiced:
diff --git a/mods/ts/sequences/misc.yaml b/mods/ts/sequences/misc.yaml
index bbc9bd0c8e..d98cd6e6af 100644
--- a/mods/ts/sequences/misc.yaml
+++ b/mods/ts/sequences/misc.yaml
@@ -87,9 +87,6 @@ darken:
pips:
medic:
Start: 6
- groups: pipsra #TODO: backfall to RA asset
- Start: 8
- Length: 10
pip-empty: pips2
pip-green: pips2
Start: 1