diff --git a/OpenRA.Game/Traits/Selectable.cs b/OpenRA.Game/Traits/Selectable.cs index 8b8c5f685c..8cf9d90368 100644 --- a/OpenRA.Game/Traits/Selectable.cs +++ b/OpenRA.Game/Traits/Selectable.cs @@ -16,7 +16,7 @@ using OpenRA.Graphics; namespace OpenRA.Traits { [Desc("This actor is selectable. Defines bounds of selectable area, selection class and selection priority.")] - public class SelectableInfo : ITraitInfo + public class SelectableInfo : ITraitInfo, IDecorationBoundsInfo { public readonly int Priority = 10; @@ -35,7 +35,7 @@ namespace OpenRA.Traits public object Create(ActorInitializer init) { return new Selectable(init.Self, this); } } - public class Selectable : IMouseBounds + public class Selectable : IMouseBounds, IDecorationBounds { public readonly string Class = null; @@ -61,5 +61,20 @@ namespace OpenRA.Traits var xy = wr.ScreenPxPosition(self.CenterPosition) + offset; return new Rectangle(xy.X, xy.Y, size.X + 2 * Info.Margin, size.Y + 2 * Info.Margin); } + + Rectangle IDecorationBounds.DecorationBounds(Actor self, WorldRenderer wr) + { + if (Info.Bounds == null) + return Rectangle.Empty; + + var size = new int2(Info.Bounds[0], Info.Bounds[1]); + + var offset = -size / 2; + if (Info.Bounds.Length > 2) + offset += new int2(Info.Bounds[2], Info.Bounds[3]); + + var xy = wr.ScreenPxPosition(self.CenterPosition) + offset; + return new Rectangle(xy.X, xy.Y, size.X, size.Y); + } } } diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index 32f059159b..da60974d4a 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -109,6 +109,10 @@ namespace OpenRA.Traits public interface IMouseBounds { Rectangle MouseoverBounds(Actor self, WorldRenderer wr); } public interface IAutoMouseBounds { Rectangle AutoMouseoverBounds(Actor self, WorldRenderer wr); } + // HACK: This provides a shim for legacy code until it can be rewritten + public interface IDecorationBounds { Rectangle DecorationBounds(Actor self, WorldRenderer wr); } + public interface IDecorationBoundsInfo : ITraitInfoInterface { } + public interface IAutoRenderSizeInfo : ITraitInfoInterface { } public interface IAutoRenderSize { int2 RenderSize(Actor self); } diff --git a/OpenRA.Mods.Cnc/Traits/SupportPowers/ChronoshiftPower.cs b/OpenRA.Mods.Cnc/Traits/SupportPowers/ChronoshiftPower.cs index 887f70e007..49907a134f 100644 --- a/OpenRA.Mods.Cnc/Traits/SupportPowers/ChronoshiftPower.cs +++ b/OpenRA.Mods.Cnc/Traits/SupportPowers/ChronoshiftPower.cs @@ -160,8 +160,16 @@ namespace OpenRA.Mods.Cnc.Traits var targetUnits = power.UnitsInRange(xy).Where(a => !world.FogObscures(a)); foreach (var unit in targetUnits) + { if (unit.CanBeViewedByPlayer(manager.Self.Owner)) - yield return new SelectionBoxRenderable(unit, Color.Red); + { + var bounds = unit.TraitsImplementing() + .Select(b => b.DecorationBounds(unit, wr)) + .FirstOrDefault(b => !b.IsEmpty); + + yield return new SelectionBoxRenderable(unit, bounds, Color.Red); + } + } } public IEnumerable Render(WorldRenderer wr, World world) @@ -270,8 +278,16 @@ namespace OpenRA.Mods.Cnc.Traits } foreach (var unit in power.UnitsInRange(sourceLocation)) + { if (unit.CanBeViewedByPlayer(manager.Self.Owner)) - yield return new SelectionBoxRenderable(unit, Color.Red); + { + var bounds = unit.TraitsImplementing() + .Select(b => b.DecorationBounds(unit, wr)) + .FirstOrDefault(b => !b.IsEmpty); + + yield return new SelectionBoxRenderable(unit, bounds, Color.Red); + } + } } public IEnumerable Render(WorldRenderer wr, World world) diff --git a/OpenRA.Mods.Common/Graphics/SelectionBarsRenderable.cs b/OpenRA.Mods.Common/Graphics/SelectionBarsRenderable.cs index 37e083122e..470a8b9f1a 100644 --- a/OpenRA.Mods.Common/Graphics/SelectionBarsRenderable.cs +++ b/OpenRA.Mods.Common/Graphics/SelectionBarsRenderable.cs @@ -21,19 +21,21 @@ namespace OpenRA.Mods.Common.Graphics readonly Actor actor; readonly bool displayHealth; readonly bool displayExtra; + readonly Rectangle decorationBounds; - public SelectionBarsRenderable(Actor actor, bool displayHealth, bool displayExtra) - : this(actor.CenterPosition, actor) + public SelectionBarsRenderable(Actor actor, Rectangle decorationBounds, bool displayHealth, bool displayExtra) + : this(actor.CenterPosition, actor, decorationBounds) { this.displayHealth = displayHealth; this.displayExtra = displayExtra; } - public SelectionBarsRenderable(WPos pos, Actor actor) + public SelectionBarsRenderable(WPos pos, Actor actor, Rectangle decorationBounds) : this() { this.pos = pos; this.actor = actor; + this.decorationBounds = decorationBounds; } public WPos Pos { get { return pos; } } @@ -46,7 +48,7 @@ namespace OpenRA.Mods.Common.Graphics public IRenderable WithPalette(PaletteReference newPalette) { return this; } public IRenderable WithZOffset(int newOffset) { return this; } - public IRenderable OffsetBy(WVec vec) { return new SelectionBarsRenderable(pos + vec, actor); } + public IRenderable OffsetBy(WVec vec) { return new SelectionBarsRenderable(pos + vec, actor, decorationBounds); } public IRenderable AsDecoration() { return this; } void DrawExtraBars(WorldRenderer wr, float3 start, float3 end) @@ -150,11 +152,8 @@ namespace OpenRA.Mods.Common.Graphics var health = actor.TraitOrDefault(); var screenPos = wr.Screen3DPxPosition(pos); - var bounds = actor.SelectionOverlayBounds; - bounds.Offset((int)screenPos.X, (int)screenPos.Y); - - var start = new float3(bounds.Left + 1, bounds.Top, screenPos.Z); - var end = new float3(bounds.Right - 1, bounds.Top, screenPos.Z); + var start = new float3(decorationBounds.Left + 1, decorationBounds.Top, screenPos.Z); + var end = new float3(decorationBounds.Right - 1, decorationBounds.Top, screenPos.Z); if (DisplayHealth) DrawHealthBar(wr, health, start, end); diff --git a/OpenRA.Mods.Common/Graphics/SelectionBoxRenderable.cs b/OpenRA.Mods.Common/Graphics/SelectionBoxRenderable.cs index ebdfd5dae8..28a745871e 100644 --- a/OpenRA.Mods.Common/Graphics/SelectionBoxRenderable.cs +++ b/OpenRA.Mods.Common/Graphics/SelectionBoxRenderable.cs @@ -17,16 +17,16 @@ namespace OpenRA.Mods.Common.Graphics public struct SelectionBoxRenderable : IRenderable, IFinalizedRenderable { readonly WPos pos; - readonly Rectangle visualBounds; + readonly Rectangle decorationBounds; readonly Color color; - public SelectionBoxRenderable(Actor actor, Color color) - : this(actor.CenterPosition, actor.SelectionOverlayBounds, color) { } + public SelectionBoxRenderable(Actor actor, Rectangle decorationBounds, Color color) + : this(actor.CenterPosition, decorationBounds, color) { } - public SelectionBoxRenderable(WPos pos, Rectangle visualBounds, Color color) + public SelectionBoxRenderable(WPos pos, Rectangle decorationBounds, Color color) { this.pos = pos; - this.visualBounds = visualBounds; + this.decorationBounds = decorationBounds; this.color = color; } @@ -38,18 +38,18 @@ namespace OpenRA.Mods.Common.Graphics public IRenderable WithPalette(PaletteReference newPalette) { return this; } public IRenderable WithZOffset(int newOffset) { return this; } - public IRenderable OffsetBy(WVec vec) { return new SelectionBoxRenderable(pos + vec, visualBounds, color); } + public IRenderable OffsetBy(WVec vec) { return new SelectionBoxRenderable(pos + vec, decorationBounds, color); } public IRenderable AsDecoration() { return this; } public IFinalizedRenderable PrepareRender(WorldRenderer wr) { return this; } public void Render(WorldRenderer wr) { var iz = 1 / wr.Viewport.Zoom; - var screenPos = wr.Screen3DPxPosition(pos); - var tl = screenPos + new float2(visualBounds.Left, visualBounds.Top); - var br = screenPos + new float2(visualBounds.Right, visualBounds.Bottom); - var tr = new float3(br.X, tl.Y, screenPos.Z); - var bl = new float3(tl.X, br.Y, screenPos.Z); + var screenDepth = wr.Screen3DPxPosition(pos).Z; + var tl = new float2(decorationBounds.Left, decorationBounds.Top); + var br = new float2(decorationBounds.Right, decorationBounds.Bottom); + var tr = new float3(br.X, tl.Y, screenDepth); + var bl = new float3(tl.X, br.Y, screenDepth); var u = new float2(4 * iz, 0); var v = new float2(0, 4 * iz); diff --git a/OpenRA.Mods.Common/Traits/Render/AutoRenderSize.cs b/OpenRA.Mods.Common/Traits/Render/AutoRenderSize.cs index 6959a8016f..caa43be7fd 100644 --- a/OpenRA.Mods.Common/Traits/Render/AutoRenderSize.cs +++ b/OpenRA.Mods.Common/Traits/Render/AutoRenderSize.cs @@ -18,12 +18,12 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits.Render { [Desc("Automatically calculates the screen map boundaries from the sprite size.")] - public class AutoRenderSizeInfo : ITraitInfo, Requires, IAutoRenderSizeInfo + public class AutoRenderSizeInfo : ITraitInfo, Requires, IAutoRenderSizeInfo, IDecorationBoundsInfo { public object Create(ActorInitializer init) { return new AutoRenderSize(init.Self); } } - public class AutoRenderSize : IAutoRenderSize, IMouseBounds + public class AutoRenderSize : IAutoRenderSize, IMouseBounds, IDecorationBounds { readonly RenderSprites rs; @@ -37,11 +37,21 @@ namespace OpenRA.Mods.Common.Traits.Render return rs.AutoRenderSize(self); } - Rectangle IMouseBounds.MouseoverBounds(Actor self, WorldRenderer wr) + Rectangle Bounds(Actor self, WorldRenderer wr) { return self.TraitsImplementing() .Select(s => s.AutoMouseoverBounds(self, wr)) .FirstOrDefault(r => !r.IsEmpty); } + + Rectangle IMouseBounds.MouseoverBounds(Actor self, WorldRenderer wr) + { + return Bounds(self, wr); + } + + Rectangle IDecorationBounds.DecorationBounds(Actor self, WorldRenderer wr) + { + return Bounds(self, wr); + } } } diff --git a/OpenRA.Mods.Common/Traits/Render/CustomRenderSize.cs b/OpenRA.Mods.Common/Traits/Render/CustomRenderSize.cs index af8a0d6a17..ceff71a3dd 100644 --- a/OpenRA.Mods.Common/Traits/Render/CustomRenderSize.cs +++ b/OpenRA.Mods.Common/Traits/Render/CustomRenderSize.cs @@ -16,15 +16,19 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("Special case trait for actors that need to define targetable area and screen map bounds manually.")] - public class CustomRenderSizeInfo : ITraitInfo, IAutoRenderSizeInfo + public class CustomRenderSizeInfo : ITraitInfo, IAutoRenderSizeInfo, IDecorationBoundsInfo { [FieldLoader.Require] public readonly int[] CustomBounds = null; + [Desc("Defines a custom rectangle for Decorations.", + "If null, CustomBounds will be used instead")] + public readonly int[] DecorationBounds = null; + public object Create(ActorInitializer init) { return new CustomRenderSize(this); } } - public class CustomRenderSize : IAutoRenderSize, IMouseBounds + public class CustomRenderSize : IAutoRenderSize, IMouseBounds, IDecorationBounds { readonly CustomRenderSizeInfo info; public CustomRenderSize(CustomRenderSizeInfo info) { this.info = info; } @@ -48,5 +52,21 @@ namespace OpenRA.Mods.Common.Traits var xy = wr.ScreenPxPosition(self.CenterPosition); return new Rectangle(xy.X, xy.Y, size.X, size.Y); } + + Rectangle IDecorationBounds.DecorationBounds(Actor self, WorldRenderer wr) + { + var bounds = info.DecorationBounds ?? info.CustomBounds; + if (bounds == null) + return Rectangle.Empty; + + var size = new int2(bounds[0], bounds[1]); + + var offset = -size / 2; + if (bounds.Length > 2) + offset += new int2(bounds[2], bounds[3]); + + var xy = wr.ScreenPxPosition(self.CenterPosition); + return new Rectangle(xy.X, xy.Y, size.X, size.Y); + } } } diff --git a/OpenRA.Mods.Common/Traits/Render/RenderNameTag.cs b/OpenRA.Mods.Common/Traits/Render/RenderNameTag.cs index d6a2e405f9..d37f022bc2 100644 --- a/OpenRA.Mods.Common/Traits/Render/RenderNameTag.cs +++ b/OpenRA.Mods.Common/Traits/Render/RenderNameTag.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using System.Drawing; +using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Graphics; using OpenRA.Traits; @@ -18,7 +19,7 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits.Render { [Desc("Displays the player name above the unit")] - class RenderNameTagInfo : ITraitInfo + class RenderNameTagInfo : ITraitInfo, Requires { public readonly int MaxLength = 10; @@ -32,6 +33,7 @@ namespace OpenRA.Mods.Common.Traits.Render readonly SpriteFont font; readonly Color color; readonly string name; + readonly IDecorationBounds[] decorationBounds; public RenderNameTag(Actor self, RenderNameTagInfo info) { @@ -42,15 +44,15 @@ namespace OpenRA.Mods.Common.Traits.Render name = self.Owner.PlayerName.Substring(0, info.MaxLength); else name = self.Owner.PlayerName; + + decorationBounds = self.TraitsImplementing().ToArray(); } public IEnumerable Render(Actor self, WorldRenderer wr) { - var pos = wr.ScreenPxPosition(self.CenterPosition); - var bounds = self.SelectionOverlayBounds; - bounds.Offset(pos.X, pos.Y); + var bounds = decorationBounds.Select(b => b.DecorationBounds(self, wr)).FirstOrDefault(b => !b.IsEmpty); var spaceBuffer = (int)(10 / wr.Viewport.Zoom); - var effectPos = wr.ProjectedPosition(new int2(pos.X, bounds.Y - spaceBuffer)); + var effectPos = wr.ProjectedPosition(new int2((bounds.Left + bounds.Right) / 2, bounds.Y - spaceBuffer)); return new IRenderable[] { new TextRenderable(font, effectPos, 0, color, name) }; } diff --git a/OpenRA.Mods.Common/Traits/Render/SelectionDecorations.cs b/OpenRA.Mods.Common/Traits/Render/SelectionDecorations.cs index 3216044ca0..8ffafcbddb 100644 --- a/OpenRA.Mods.Common/Traits/Render/SelectionDecorations.cs +++ b/OpenRA.Mods.Common/Traits/Render/SelectionDecorations.cs @@ -18,7 +18,7 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits.Render { - public class SelectionDecorationsInfo : ITraitInfo, ISelectionDecorationsInfo + public class SelectionDecorationsInfo : ITraitInfo, ISelectionDecorationsInfo, Requires { [PaletteReference] public readonly string Palette = "chrome"; @@ -48,6 +48,7 @@ namespace OpenRA.Mods.Common.Traits.Render public readonly SelectionDecorationsInfo Info; + readonly IDecorationBounds[] decorationBounds; readonly Animation pipImages; IPips[] pipSources; @@ -55,6 +56,7 @@ namespace OpenRA.Mods.Common.Traits.Render { Info = info; + decorationBounds = self.TraitsImplementing().ToArray(); pipImages = new Animation(self.World, Info.Image); } @@ -92,6 +94,7 @@ namespace OpenRA.Mods.Common.Traits.Render var selected = self.World.Selection.Contains(self); var regularWorld = self.World.Type == WorldType.Regular; var statusBars = Game.Settings.Game.StatusBars; + var bounds = decorationBounds.Select(b => b.DecorationBounds(self, wr)).FirstOrDefault(b => !b.IsEmpty); // Health bars are shown when: // * actor is selected @@ -107,10 +110,10 @@ namespace OpenRA.Mods.Common.Traits.Render var displayExtra = selected || (regularWorld && statusBars != StatusBarsType.Standard); if (Info.RenderSelectionBox && selected) - yield return new SelectionBoxRenderable(self, Info.SelectionBoxColor); + yield return new SelectionBoxRenderable(self, bounds, Info.SelectionBoxColor); if (Info.RenderSelectionBars && (displayHealth || displayExtra)) - yield return new SelectionBarsRenderable(self, displayHealth, displayExtra); + yield return new SelectionBarsRenderable(self, bounds, displayHealth, displayExtra); // Target lines and pips are always only displayed for selected allied actors if (!selected || !self.Owner.IsAlliedWith(wr.World.RenderPlayer)) @@ -119,31 +122,28 @@ namespace OpenRA.Mods.Common.Traits.Render if (self.World.LocalPlayer != null && self.World.LocalPlayer.PlayerActor.Trait().PathDebug) yield return new TargetLineRenderable(ActivityTargetPath(self), Color.Green); - foreach (var r in DrawPips(self, wr)) + foreach (var r in DrawPips(self, bounds, wr)) yield return r; } - IEnumerable DrawPips(Actor self, WorldRenderer wr) + IEnumerable DrawPips(Actor self, Rectangle bounds, WorldRenderer wr) { if (pipSources.Length == 0) return Enumerable.Empty(); - var b = self.SelectionOverlayBounds; - var pos = wr.ScreenPxPosition(self.CenterPosition); - var bl = wr.Viewport.WorldToViewPx(pos + new int2(b.Left, b.Bottom)); - var pal = wr.Palette(Info.Palette); - - return DrawPips(self, bl, pal); + return DrawPipsInner(self, bounds, wr); } - IEnumerable DrawPips(Actor self, int2 basePosition, PaletteReference palette) + IEnumerable DrawPipsInner(Actor self, Rectangle bounds, WorldRenderer wr) { pipImages.PlayRepeating(PipStrings[0]); + var palette = wr.Palette(Info.Palette); + var basePosition = wr.Viewport.WorldToViewPx(new int2(bounds.Left, bounds.Bottom)); var pipSize = pipImages.Image.Size.XY.ToInt2(); var pipxyBase = basePosition + new int2(1 - pipSize.X / 2, -(3 + pipSize.Y / 2)); var pipxyOffset = new int2(0, 0); - var width = self.SelectionOverlayBounds.Width; + var width = bounds.Width; foreach (var pips in pipSources) { diff --git a/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs b/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs index afc0ffcdcd..7078689f81 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs @@ -27,8 +27,8 @@ namespace OpenRA.Mods.Common.Traits.Render Right = 8, } - [Desc("Displays a custom UI overlay relative to the selection box.")] - public class WithDecorationInfo : ConditionalTraitInfo + [Desc("Displays a custom UI overlay relative to the actor's mouseover bounds.")] + public class WithDecorationInfo : ConditionalTraitInfo, Requires { [Desc("Image used for this decoration. Defaults to the actor's type.")] public readonly string Image = null; @@ -58,7 +58,7 @@ namespace OpenRA.Mods.Common.Traits.Render public class WithDecoration : ConditionalTrait, ITick, IRenderAboveShroud, IRenderAboveShroudWhenSelected { protected readonly Animation Anim; - + readonly IDecorationBounds[] decorationBounds; readonly string image; public WithDecoration(Actor self, WithDecorationInfo info) @@ -67,6 +67,7 @@ namespace OpenRA.Mods.Common.Traits.Render image = info.Image ?? self.Info.Name; Anim = new Animation(self.World, image, () => self.World.Paused); Anim.PlayRepeating(info.Sequence); + decorationBounds = self.TraitsImplementing().ToArray(); } protected virtual bool ShouldRender(Actor self) @@ -99,7 +100,7 @@ namespace OpenRA.Mods.Common.Traits.Render if (!ShouldRender(self) || self.World.FogObscures(self)) return Enumerable.Empty(); - var bounds = self.SelectionOverlayBounds; + var bounds = decorationBounds.Select(b => b.DecorationBounds(self, wr)).FirstOrDefault(b => !b.IsEmpty); var halfSize = (0.5f * Anim.Image.Size.XY).ToInt2(); var boundsOffset = new int2(bounds.Left + bounds.Right, bounds.Top + bounds.Bottom) / 2; @@ -126,7 +127,7 @@ namespace OpenRA.Mods.Common.Traits.Render sizeOffset -= new int2(halfSize.X, 0); } - var pxPos = wr.Viewport.WorldToViewPx(wr.ScreenPxPosition(self.CenterPosition) + boundsOffset) + sizeOffset; + var pxPos = wr.Viewport.WorldToViewPx(boundsOffset) + sizeOffset; return new IRenderable[] { new UISpriteRenderable(Anim.Image, self.CenterPosition, pxPos, Info.ZOffset, wr.Palette(Info.Palette), 1f) }; } diff --git a/OpenRA.Mods.Common/Traits/Render/WithSpriteControlGroupDecoration.cs b/OpenRA.Mods.Common/Traits/Render/WithSpriteControlGroupDecoration.cs index 87b732cb45..f4abc84ab1 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithSpriteControlGroupDecoration.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithSpriteControlGroupDecoration.cs @@ -19,7 +19,7 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits.Render { [Desc("Renders Ctrl groups using pixel art.")] - public class WithSpriteControlGroupDecorationInfo : ITraitInfo + public class WithSpriteControlGroupDecorationInfo : ITraitInfo, Requires { [PaletteReference] public readonly string Palette = "chrome"; @@ -38,12 +38,14 @@ namespace OpenRA.Mods.Common.Traits.Render public class WithSpriteControlGroupDecoration : IRenderAboveShroudWhenSelected { public readonly WithSpriteControlGroupDecorationInfo Info; + readonly IDecorationBounds[] decorationBounds; readonly Animation pipImages; public WithSpriteControlGroupDecoration(Actor self, WithSpriteControlGroupDecorationInfo info) { Info = info; + decorationBounds = self.TraitsImplementing().ToArray(); pipImages = new Animation(self.World, Info.Image); } @@ -68,7 +70,7 @@ namespace OpenRA.Mods.Common.Traits.Render pipImages.PlayFetchIndex(Info.GroupSequence, () => (int)group); - var bounds = self.SelectionOverlayBounds; + var bounds = decorationBounds.Select(b => b.DecorationBounds(self, wr)).FirstOrDefault(b => !b.IsEmpty); var boundsOffset = 0.5f * new float2(bounds.Left + bounds.Right, bounds.Top + bounds.Bottom); if (Info.ReferencePoint.HasFlag(ReferencePoints.Top)) boundsOffset -= new float2(0, 0.5f * bounds.Height); @@ -82,7 +84,7 @@ namespace OpenRA.Mods.Common.Traits.Render if (Info.ReferencePoint.HasFlag(ReferencePoints.Right)) boundsOffset += new float2(0.5f * bounds.Width, 0); - var pxPos = wr.Viewport.WorldToViewPx(wr.ScreenPxPosition(self.CenterPosition) + boundsOffset.ToInt2()) - (0.5f * pipImages.Image.Size.XY).ToInt2(); + var pxPos = wr.Viewport.WorldToViewPx(boundsOffset.ToInt2()) - (0.5f * pipImages.Image.Size.XY).ToInt2(); 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 index 8985a41a04..de08f395a7 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithTextControlGroupDecoration.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithTextControlGroupDecoration.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using System.Drawing; +using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Graphics; using OpenRA.Traits; @@ -18,7 +19,7 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits.Render { [Desc("Renders Ctrl groups using typeface.")] - public class WithTextControlGroupDecorationInfo : ITraitInfo, IRulesetLoaded + public class WithTextControlGroupDecorationInfo : ITraitInfo, IRulesetLoaded, Requires { public readonly string Font = "TinyBold"; @@ -50,6 +51,7 @@ namespace OpenRA.Mods.Common.Traits.Render public class WithTextControlGroupDecoration : IRenderAboveShroudWhenSelected, INotifyOwnerChanged { readonly WithTextControlGroupDecorationInfo info; + readonly IDecorationBounds[] decorationBounds; readonly SpriteFont font; Color color; @@ -61,6 +63,7 @@ namespace OpenRA.Mods.Common.Traits.Render 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)); + decorationBounds = self.TraitsImplementing().ToArray(); color = info.UsePlayerColor ? self.Owner.Color.RGB : info.Color; } @@ -82,7 +85,7 @@ namespace OpenRA.Mods.Common.Traits.Render if (group == null) yield break; - var bounds = self.SelectionOverlayBounds; + var bounds = decorationBounds.Select(b => b.DecorationBounds(self, wr)).FirstOrDefault(b => !b.IsEmpty); var number = group.Value.ToString(); var halfSize = font.Measure(number) / 2; @@ -110,7 +113,7 @@ namespace OpenRA.Mods.Common.Traits.Render sizeOffset -= new int2(halfSize.X, 0); } - var screenPos = wr.ScreenPxPosition(self.CenterPosition) + boundsOffset + sizeOffset + info.ScreenOffset; + var screenPos = boundsOffset + sizeOffset + info.ScreenOffset; yield return new TextRenderable(font, wr.ProjectedPosition(screenPos), info.ZOffset, color, number); } diff --git a/OpenRA.Mods.Common/Traits/Render/WithTextDecoration.cs b/OpenRA.Mods.Common/Traits/Render/WithTextDecoration.cs index 6b9224e359..3aa22bbf4a 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithTextDecoration.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithTextDecoration.cs @@ -20,7 +20,7 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits.Render { [Desc("Displays a text overlay relative to the selection box.")] - public class WithTextDecorationInfo : ConditionalTraitInfo + public class WithTextDecorationInfo : ConditionalTraitInfo, Requires { [FieldLoader.Require] [Translate] public readonly string Text = null; @@ -59,12 +59,14 @@ namespace OpenRA.Mods.Common.Traits.Render public class WithTextDecoration : ConditionalTrait, IRender, IRenderAboveShroudWhenSelected, INotifyOwnerChanged { readonly SpriteFont font; + readonly IDecorationBounds[] decorationBounds; Color color; public WithTextDecoration(Actor self, WithTextDecorationInfo info) : base(info) { font = Game.Renderer.Fonts[info.Font]; + decorationBounds = self.TraitsImplementing().ToArray(); color = Info.UsePlayerColor ? self.Owner.Color.RGB : Info.Color; } @@ -101,7 +103,7 @@ namespace OpenRA.Mods.Common.Traits.Render if (!ShouldRender(self) || self.World.FogObscures(self)) return Enumerable.Empty(); - var bounds = self.SelectionOverlayBounds; + var bounds = decorationBounds.Select(b => b.DecorationBounds(self, wr)).FirstOrDefault(b => !b.IsEmpty); var halfSize = font.Measure(Info.Text) / 2; var boundsOffset = new int2(bounds.Left + bounds.Right, bounds.Top + bounds.Bottom) / 2; @@ -128,8 +130,7 @@ namespace OpenRA.Mods.Common.Traits.Render sizeOffset -= new int2(halfSize.X, 0); } - var screenPos = wr.ScreenPxPosition(self.CenterPosition) + boundsOffset + sizeOffset; - return new IRenderable[] { new TextRenderable(font, wr.ProjectedPosition(screenPos), Info.ZOffset, color, Info.Text) }; + return new IRenderable[] { new TextRenderable(font, wr.ProjectedPosition(boundsOffset + sizeOffset), Info.ZOffset, color, Info.Text) }; } void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner) diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/GrantExternalConditionPower.cs b/OpenRA.Mods.Common/Traits/SupportPowers/GrantExternalConditionPower.cs index f6f5f9d415..bc995f84b6 100644 --- a/OpenRA.Mods.Common/Traits/SupportPowers/GrantExternalConditionPower.cs +++ b/OpenRA.Mods.Common/Traits/SupportPowers/GrantExternalConditionPower.cs @@ -134,7 +134,12 @@ namespace OpenRA.Mods.Common.Traits { var xy = wr.Viewport.ViewToWorld(Viewport.LastMousePos); foreach (var unit in power.UnitsInRange(xy)) - yield return new SelectionBoxRenderable(unit, Color.Red); + { + var bounds = unit.TraitsImplementing() + .Select(b => b.DecorationBounds(unit, wr)) + .FirstOrDefault(b => !b.IsEmpty); + yield return new SelectionBoxRenderable(unit, bounds, Color.Red); + } } public IEnumerable Render(WorldRenderer wr, World world) diff --git a/OpenRA.Mods.Common/Widgets/WorldInteractionControllerWidget.cs b/OpenRA.Mods.Common/Widgets/WorldInteractionControllerWidget.cs index abe38220ad..a45b1752f4 100644 --- a/OpenRA.Mods.Common/Widgets/WorldInteractionControllerWidget.cs +++ b/OpenRA.Mods.Common/Widgets/WorldInteractionControllerWidget.cs @@ -50,7 +50,13 @@ namespace OpenRA.Mods.Common.Widgets { // TODO: Integrate this with SelectionDecorations to unhardcode the *Renderable if (unit.Info.HasTraitInfo()) - new SelectionBarsRenderable(unit, true, true).Render(worldRenderer); + { + var bounds = unit.TraitsImplementing() + .Select(b => b.DecorationBounds(unit, worldRenderer)) + .FirstOrDefault(b => !b.IsEmpty); + + new SelectionBarsRenderable(unit, bounds, true, true).Render(worldRenderer); + } } public override void Draw()