diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index 030faa281c..cb656f8400 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -267,6 +267,7 @@ namespace OpenRA.Traits public interface ISelectionDecorations { IEnumerable RenderSelectionAnnotations(Actor self, WorldRenderer worldRenderer, Color color); + int2 GetDecorationOrigin(Actor self, WorldRenderer wr, string pos, int2 margin); } public interface IMapPreviewSignatureInfo : ITraitInfoInterface diff --git a/OpenRA.Mods.Common/Traits/Render/IsometricSelectionDecorations.cs b/OpenRA.Mods.Common/Traits/Render/IsometricSelectionDecorations.cs index e96b03a825..6e6e7b647f 100644 --- a/OpenRA.Mods.Common/Traits/Render/IsometricSelectionDecorations.cs +++ b/OpenRA.Mods.Common/Traits/Render/IsometricSelectionDecorations.cs @@ -32,20 +32,38 @@ namespace OpenRA.Mods.Common.Traits.Render selectable = self.Trait(); } - protected override int2 GetDecorationPosition(Actor self, WorldRenderer wr, DecorationPosition pos) + int2 GetDecorationPosition(Actor self, WorldRenderer wr, string pos) { var bounds = selectable.DecorationBounds(self, wr); switch (pos) { - case DecorationPosition.TopLeft: return bounds.Vertices[1]; - case DecorationPosition.TopRight: return bounds.Vertices[5]; - case DecorationPosition.BottomLeft: return bounds.Vertices[2]; - case DecorationPosition.BottomRight: return bounds.Vertices[4]; - case DecorationPosition.Top: return new int2((bounds.Vertices[1].X + bounds.Vertices[5].X) / 2, bounds.Vertices[1].Y); + case "TopLeft": return bounds.Vertices[1]; + case "TopRight": return bounds.Vertices[5]; + case "BottomLeft": return bounds.Vertices[2]; + case "BottomRight": return bounds.Vertices[4]; + case "Top": return new int2((bounds.Vertices[1].X + bounds.Vertices[5].X) / 2, bounds.Vertices[1].Y); default: return bounds.BoundingRect.TopLeft + new int2(bounds.BoundingRect.Size.Width / 2, bounds.BoundingRect.Size.Height / 2); } } + static int2 GetDecorationMargin(string pos, int2 margin) + { + switch (pos) + { + case "TopLeft": return margin; + case "TopRight": return new int2(-margin.X, margin.Y); + case "BottomLeft": return new int2(margin.X, -margin.Y); + case "BottomRight": return -margin; + case "Top": return new int2(0, margin.Y); + default: return int2.Zero; + } + } + + protected override int2 GetDecorationOrigin(Actor self, WorldRenderer wr, string pos, int2 margin) + { + return wr.Viewport.WorldToViewPx(GetDecorationPosition(self, wr, pos)) + GetDecorationMargin(pos, margin); + } + protected override IEnumerable RenderSelectionBox(Actor self, WorldRenderer wr, Color color) { var bounds = selectable.DecorationBounds(self, wr); diff --git a/OpenRA.Mods.Common/Traits/Render/SelectionDecorations.cs b/OpenRA.Mods.Common/Traits/Render/SelectionDecorations.cs index 54a3ff630a..76ec6c1f14 100644 --- a/OpenRA.Mods.Common/Traits/Render/SelectionDecorations.cs +++ b/OpenRA.Mods.Common/Traits/Render/SelectionDecorations.cs @@ -32,20 +32,38 @@ namespace OpenRA.Mods.Common.Traits.Render interactable = self.Trait(); } - protected override int2 GetDecorationPosition(Actor self, WorldRenderer wr, DecorationPosition pos) + int2 GetDecorationPosition(Actor self, WorldRenderer wr, string pos) { var bounds = interactable.DecorationBounds(self, wr); switch (pos) { - case DecorationPosition.TopLeft: return bounds.TopLeft; - case DecorationPosition.TopRight: return bounds.TopRight; - case DecorationPosition.BottomLeft: return bounds.BottomLeft; - case DecorationPosition.BottomRight: return bounds.BottomRight; - case DecorationPosition.Top: return new int2(bounds.Left + bounds.Size.Width / 2, bounds.Top); + case "TopLeft": return bounds.TopLeft; + case "TopRight": return bounds.TopRight; + case "BottomLeft": return bounds.BottomLeft; + case "BottomRight": return bounds.BottomRight; + case "Top": return new int2(bounds.Left + bounds.Size.Width / 2, bounds.Top); default: return bounds.TopLeft + new int2(bounds.Size.Width / 2, bounds.Size.Height / 2); } } + static int2 GetDecorationMargin(string pos, int2 margin) + { + switch (pos) + { + case "TopLeft": return margin; + case "TopRight": return new int2(-margin.X, margin.Y); + case "BottomLeft": return new int2(margin.X, -margin.Y); + case "BottomRight": return -margin; + case "Top": return new int2(0, margin.Y); + default: return int2.Zero; + } + } + + protected override int2 GetDecorationOrigin(Actor self, WorldRenderer wr, string pos, int2 margin) + { + return wr.Viewport.WorldToViewPx(GetDecorationPosition(self, wr, pos)) + GetDecorationMargin(pos, margin); + } + protected override IEnumerable RenderSelectionBox(Actor self, WorldRenderer wr, Color color) { var bounds = interactable.DecorationBounds(self, wr); diff --git a/OpenRA.Mods.Common/Traits/Render/SelectionDecorationsBase.cs b/OpenRA.Mods.Common/Traits/Render/SelectionDecorationsBase.cs index b859595f56..5b5c480bfe 100644 --- a/OpenRA.Mods.Common/Traits/Render/SelectionDecorationsBase.cs +++ b/OpenRA.Mods.Common/Traits/Render/SelectionDecorationsBase.cs @@ -24,8 +24,8 @@ namespace OpenRA.Mods.Common.Traits.Render public abstract class SelectionDecorationsBase : ISelectionDecorations, IRenderAnnotations, INotifyCreated { - Dictionary decorations; - Dictionary selectedDecorations; + IDecoration[] decorations; + IDecoration[] selectedDecorations; protected readonly SelectionDecorationsBaseInfo info; @@ -36,22 +36,8 @@ namespace OpenRA.Mods.Common.Traits.Render void INotifyCreated.Created(Actor self) { - var groupedDecorations = new Dictionary>(); - var groupedSelectionDecorations = new Dictionary>(); - foreach (var d in self.TraitsImplementing()) - { - groupedSelectionDecorations.GetOrAdd(d.Position).Add(d); - if (!d.RequiresSelection) - groupedDecorations.GetOrAdd(d.Position).Add(d); - } - - decorations = groupedDecorations.ToDictionary( - d => d.Key, - d => d.Value.ToArray()); - - selectedDecorations = groupedSelectionDecorations.ToDictionary( - d => d.Key, - d => d.Value.ToArray()); + selectedDecorations = self.TraitsImplementing().ToArray(); + decorations = selectedDecorations.Where(d => !d.RequiresSelection).ToArray(); } IEnumerable ActivityTargetPath(Actor self) @@ -116,13 +102,9 @@ namespace OpenRA.Mods.Common.Traits.Render yield break; var renderDecorations = selected ? selectedDecorations : decorations; - foreach (var kv in renderDecorations) - { - var pos = GetDecorationPosition(self, wr, kv.Key); - foreach (var r in kv.Value) - foreach (var rr in r.RenderDecoration(self, wr, pos)) - yield return rr; - } + foreach (var r in renderDecorations) + foreach (var rr in r.RenderDecoration(self, wr, this)) + yield return rr; } IEnumerable ISelectionDecorations.RenderSelectionAnnotations(Actor self, WorldRenderer worldRenderer, Color color) @@ -130,7 +112,12 @@ namespace OpenRA.Mods.Common.Traits.Render return RenderSelectionBox(self, worldRenderer, color); } - protected abstract int2 GetDecorationPosition(Actor self, WorldRenderer wr, DecorationPosition pos); + int2 ISelectionDecorations.GetDecorationOrigin(Actor self, WorldRenderer wr, string pos, int2 margin) + { + return GetDecorationOrigin(self, wr, pos, margin); + } + + protected abstract int2 GetDecorationOrigin(Actor self, WorldRenderer wr, string pos, int2 margin); protected abstract IEnumerable RenderSelectionBox(Actor self, WorldRenderer wr, Color color); protected abstract IEnumerable RenderSelectionBars(Actor self, WorldRenderer wr, bool displayHealth, bool displayExtra); } diff --git a/OpenRA.Mods.Common/Traits/Render/WithDecorationBase.cs b/OpenRA.Mods.Common/Traits/Render/WithDecorationBase.cs index 7a0853304a..cf3fe8b76c 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithDecorationBase.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithDecorationBase.cs @@ -22,7 +22,7 @@ namespace OpenRA.Mods.Common.Traits.Render public abstract class WithDecorationBaseInfo : ConditionalTraitInfo { [Desc("Position in the actor's selection box to draw the decoration.")] - public readonly DecorationPosition Position = DecorationPosition.TopLeft; + public readonly string Position = "TopLeft"; [Desc("Player stances who can view the decoration.")] public readonly Stance ValidStances = Stance.Ally; @@ -89,20 +89,16 @@ namespace OpenRA.Mods.Common.Traits.Render return true; } - DecorationPosition IDecoration.Position { get { return Info.Position; } } - - bool IDecoration.Enabled { get { return !IsTraitDisabled && self.IsInWorld && ShouldRender(self); } } - bool IDecoration.RequiresSelection { get { return Info.RequiresSelection; } } protected abstract IEnumerable RenderDecoration(Actor self, WorldRenderer wr, int2 pos); - IEnumerable IDecoration.RenderDecoration(Actor self, WorldRenderer wr, int2 pos) + IEnumerable IDecoration.RenderDecoration(Actor self, WorldRenderer wr, ISelectionDecorations container) { if (IsTraitDisabled || self.IsDead || !self.IsInWorld || !ShouldRender(self)) return Enumerable.Empty(); - var screenPos = wr.Viewport.WorldToViewPx(pos) + Info.Position.CreateMargin(Info.Margin) + conditionalOffset; + var screenPos = container.GetDecorationOrigin(self, wr, Info.Position, Info.Margin) + conditionalOffset; return RenderDecoration(self, wr, screenPos); } diff --git a/OpenRA.Mods.Common/Traits/Render/WithSpriteControlGroupDecoration.cs b/OpenRA.Mods.Common/Traits/Render/WithSpriteControlGroupDecoration.cs index 9cc73fc2a0..a88172d222 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithSpriteControlGroupDecoration.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithSpriteControlGroupDecoration.cs @@ -29,7 +29,7 @@ namespace OpenRA.Mods.Common.Traits.Render public readonly string GroupSequence = "groups"; [Desc("Position in the actor's selection box to draw the decoration.")] - public readonly DecorationPosition Position = DecorationPosition.TopLeft; + public readonly string Position = "TopLeft"; [Desc("Offset sprite center position from the selection box edge.")] public readonly int2 Margin = int2.Zero; @@ -51,13 +51,9 @@ namespace OpenRA.Mods.Common.Traits.Render anim = new Animation(self.World, Info.Image); } - DecorationPosition IDecoration.Position { get { return Info.Position; } } - - bool IDecoration.Enabled { get { return self.Owner == self.World.LocalPlayer && self.World.Selection.GetControlGroupForActor(self) != null; } } - bool IDecoration.RequiresSelection { get { return true; } } - IEnumerable IDecoration.RenderDecoration(Actor self, WorldRenderer wr, int2 pos) + IEnumerable IDecoration.RenderDecoration(Actor self, WorldRenderer wr, ISelectionDecorations container) { var group = self.World.Selection.GetControlGroupForActor(self); if (group == null) @@ -65,7 +61,7 @@ namespace OpenRA.Mods.Common.Traits.Render anim.PlayFetchIndex(Info.GroupSequence, () => (int)group); - var screenPos = wr.Viewport.WorldToViewPx(pos) + Info.Position.CreateMargin(Info.Margin) - (0.5f * anim.Image.Size.XY).ToInt2(); + var screenPos = container.GetDecorationOrigin(self, wr, Info.Position, Info.Margin) - (0.5f * anim.Image.Size.XY).ToInt2(); var palette = wr.Palette(Info.Palette); return new IRenderable[] { diff --git a/OpenRA.Mods.Common/Traits/Render/WithTextControlGroupDecoration.cs b/OpenRA.Mods.Common/Traits/Render/WithTextControlGroupDecoration.cs index 60b0933c52..8764a4cf42 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithTextControlGroupDecoration.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithTextControlGroupDecoration.cs @@ -31,7 +31,7 @@ namespace OpenRA.Mods.Common.Traits.Render public readonly bool UsePlayerColor = false; [Desc("Position in the actor's selection box to draw the decoration.")] - public readonly DecorationPosition Position = DecorationPosition.TopLeft; + public readonly string Position = "TopLeft"; [Desc("Offset text center position from the selection box edge.")] public readonly int2 Margin = int2.Zero; @@ -63,20 +63,16 @@ namespace OpenRA.Mods.Common.Traits.Render label = new CachedTransform(g => g.ToString()); } - DecorationPosition IDecoration.Position { get { return info.Position; } } - - bool IDecoration.Enabled { get { return self.Owner == self.World.LocalPlayer && self.World.Selection.GetControlGroupForActor(self) != null; } } - bool IDecoration.RequiresSelection { get { return true; } } - IEnumerable IDecoration.RenderDecoration(Actor self, WorldRenderer wr, int2 pos) + IEnumerable IDecoration.RenderDecoration(Actor self, WorldRenderer wr, ISelectionDecorations container) { var group = self.World.Selection.GetControlGroupForActor(self); if (group == null) return Enumerable.Empty(); var text = label.Update(group.Value); - var screenPos = wr.Viewport.WorldToViewPx(pos) + info.Position.CreateMargin(info.Margin); + var screenPos = container.GetDecorationOrigin(self, wr, info.Position, info.Margin); return new IRenderable[] { new UITextRenderable(font, self.CenterPosition, screenPos, 0, color, text) diff --git a/OpenRA.Mods.Common/TraitsInterfaces.cs b/OpenRA.Mods.Common/TraitsInterfaces.cs index 806e1bf796..51d09ca06d 100644 --- a/OpenRA.Mods.Common/TraitsInterfaces.cs +++ b/OpenRA.Mods.Common/TraitsInterfaces.cs @@ -639,37 +639,8 @@ namespace OpenRA.Mods.Common.Traits public interface IDecoration { - DecorationPosition Position { get; } bool RequiresSelection { get; } - bool Enabled { get; } - - IEnumerable RenderDecoration(Actor self, WorldRenderer wr, int2 pos); - } - - public enum DecorationPosition - { - Center, - TopLeft, - TopRight, - BottomLeft, - BottomRight, - Top - } - - public static class DecorationExtensions - { - public static int2 CreateMargin(this DecorationPosition pos, int2 margin) - { - switch (pos) - { - case DecorationPosition.TopLeft: return margin; - case DecorationPosition.TopRight: return new int2(-margin.X, margin.Y); - case DecorationPosition.BottomLeft: return new int2(margin.X, -margin.Y); - case DecorationPosition.BottomRight: return -margin; - case DecorationPosition.Top: return new int2(0, margin.Y); - default: return int2.Zero; - } - } + IEnumerable RenderDecoration(Actor self, WorldRenderer wr, ISelectionDecorations container); } } diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ModernizeDecorationTraits.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ModernizeDecorationTraits.cs index 45f614e6cc..98dfe8e349 100644 --- a/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ModernizeDecorationTraits.cs +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20200503/ModernizeDecorationTraits.cs @@ -45,14 +45,14 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules Right = 8, } - static readonly Dictionary PositionMap = new Dictionary() + static readonly Dictionary PositionMap = new Dictionary() { - { LegacyReferencePoints.Center, DecorationPosition.Center }, - { LegacyReferencePoints.Top, DecorationPosition.Top }, - { LegacyReferencePoints.Top | LegacyReferencePoints.Left, DecorationPosition.TopLeft }, - { LegacyReferencePoints.Top | LegacyReferencePoints.Right, DecorationPosition.TopRight }, - { LegacyReferencePoints.Bottom | LegacyReferencePoints.Left, DecorationPosition.BottomLeft }, - { LegacyReferencePoints.Bottom | LegacyReferencePoints.Right, DecorationPosition.BottomRight } + { LegacyReferencePoints.Center, "Center" }, + { LegacyReferencePoints.Top, "Top" }, + { LegacyReferencePoints.Top | LegacyReferencePoints.Left, "TopLeft" }, + { LegacyReferencePoints.Top | LegacyReferencePoints.Right, "TopRight" }, + { LegacyReferencePoints.Bottom | LegacyReferencePoints.Left, "BottomLeft" }, + { LegacyReferencePoints.Bottom | LegacyReferencePoints.Right, "BottomRight" } }; readonly Dictionary> locations = new Dictionary>(); @@ -83,9 +83,9 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules if (positionNode != null) { if (!PositionMap.TryGetValue(positionNode.NodeValue(), out var value)) - value = DecorationPosition.TopLeft; + value = "TopLeft"; - if (value != DecorationPosition.TopLeft) + if (value != "TopLeft") { positionNode.RenameKey("Position"); positionNode.ReplaceValue(FieldSaver.FormatValue(value));