diff --git a/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs b/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs index 6acfeb4aea..681840406e 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs @@ -20,16 +20,14 @@ namespace OpenRA.Mods.Common.Traits [Flags] public enum ReferencePoints { - Top = 0, - VCenter = 1, + Center = 0, + Top = 1, Bottom = 2, - - Left = 0 << 2, - HCenter = 1 << 2, - Right = 2 << 2, + Left = 4, + Right = 8, } - [Desc("Displays a custom animation if conditions are satisfied.")] + [Desc("Displays a custom UI overlay relative to the selection box.")] public class WithDecorationInfo : UpgradableTraitInfo { [Desc("Image used for this decoration. Defaults to the actor's type.")] @@ -41,131 +39,96 @@ namespace OpenRA.Mods.Common.Traits [Desc("Palette to render the sprite in. Reference the world actor's PaletteFrom* traits.")] [PaletteReference] public readonly string Palette = "chrome"; - [Desc("Point in the actor's bounding box used as reference for offsetting the decoration image. " + - "Possible values are any combination of Top, VCenter, Bottom and Left, HCenter, Right separated by a comma.")] + [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; - [Desc("Pixel offset relative to the actor's bounding box' reference point.")] - 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; + [Desc("Player stances who can view the decoration.")] + public readonly Stance Stances = Stance.Ally; [Desc("Should this be visible only when selected?")] - public readonly bool SelectionDecoration = false; + public readonly bool RequiresSelection = false; public override object Create(ActorInitializer init) { return new WithDecoration(init.Self, this); } } - public class WithDecoration : UpgradableTrait, IRender, IPostRenderSelection + public class WithDecoration : UpgradableTrait, ITick, IRender, IPostRenderSelection { - readonly WithDecorationInfo info; + protected readonly Animation Anim; + readonly string image; - readonly Animation anim; readonly Actor self; public WithDecoration(Actor self, WithDecorationInfo info) : base(info) { - this.info = info; this.self = self; image = info.Image ?? self.Info.Name; - anim = new Animation(self.World, image, () => self.World.Paused); - anim.PlayRepeating(info.Sequence); - } - - public void PlaySingleFrame(int frame) - { - anim.PlayFetchIndex(info.Sequence, () => frame); + Anim = new Animation(self.World, image, () => self.World.Paused); + Anim.PlayRepeating(info.Sequence); } public virtual bool ShouldRender(Actor self) { return true; } public IEnumerable Render(Actor self, WorldRenderer wr) { - return !info.SelectionDecoration ? RenderInner(self, wr, self.Bounds) : Enumerable.Empty(); + return !Info.RequiresSelection ? RenderInner(self, wr) : Enumerable.Empty(); } public IEnumerable RenderAfterWorld(WorldRenderer wr) { - return info.SelectionDecoration ? RenderInner(self, wr, self.VisualBounds) : Enumerable.Empty(); + return Info.RequiresSelection ? RenderInner(self, wr) : Enumerable.Empty(); } - IEnumerable RenderInner(Actor self, WorldRenderer wr, Rectangle actorBounds) + IEnumerable RenderInner(Actor self, WorldRenderer wr) { - if (IsTraitDisabled) + if (IsTraitDisabled || self.IsDead || !self.IsInWorld || Anim == null) return Enumerable.Empty(); - if (self.IsDead || !self.IsInWorld) - return Enumerable.Empty(); - - if (anim == null) - return Enumerable.Empty(); - - var allied = self.Owner.IsAlliedWith(self.World.RenderPlayer); - - if (!allied && !info.ShowToEnemies) - return Enumerable.Empty(); - - if (allied && !info.ShowToAllies) - return Enumerable.Empty(); - - if (!ShouldRender(self)) - return Enumerable.Empty(); - - if (self.World.FogObscures(self)) - return Enumerable.Empty(); - - var pxPos = wr.ScreenPxPosition(self.CenterPosition); - actorBounds.Offset(pxPos.X, pxPos.Y); - - var img = anim.Image; - var imgSize = img.Size.ToInt2(); - - switch (info.ReferencePoint & (ReferencePoints)3) + if (self.World.RenderPlayer != null) { - case ReferencePoints.Top: - pxPos = pxPos.WithY(actorBounds.Top + imgSize.Y / 2); - break; - case ReferencePoints.VCenter: - pxPos = pxPos.WithY((actorBounds.Top + actorBounds.Bottom) / 2); - break; - case ReferencePoints.Bottom: - pxPos = pxPos.WithY(actorBounds.Bottom - imgSize.Y / 2); - break; + var stance = self.Owner.Stances[self.World.RenderPlayer]; + if (!Info.Stances.HasStance(stance)) + return Enumerable.Empty(); } - switch (info.ReferencePoint & (ReferencePoints)(3 << 2)) + if (!ShouldRender(self) || self.World.FogObscures(self)) + return Enumerable.Empty(); + + var bounds = self.VisualBounds; + var halfSize = (0.5f * Anim.Image.Size).ToInt2(); + + var boundsOffset = new int2(bounds.Left + bounds.Right, bounds.Top + bounds.Bottom) / 2; + var sizeOffset = -halfSize; + if (Info.ReferencePoint.HasFlag(ReferencePoints.Top)) { - case ReferencePoints.Left: - pxPos = pxPos.WithX(actorBounds.Left + imgSize.X / 2); - break; - case ReferencePoints.HCenter: - pxPos = pxPos.WithX((actorBounds.Left + actorBounds.Right) / 2); - break; - case ReferencePoints.Right: - pxPos = pxPos.WithX(actorBounds.Right - imgSize.X / 2); - break; + 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); } - pxPos += info.Offset; + 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); + } - // HACK: Because WorldRenderer.Position() does not care about terrain height at the location - var renderPos = wr.ProjectedPosition(pxPos); - renderPos = new WPos(renderPos.X, renderPos.Y + self.CenterPosition.Z, self.CenterPosition.Z); - - anim.Tick(); - - return new IRenderable[] { new SpriteRenderable(img, renderPos, WVec.Zero, info.ZOffset, wr.Palette(info.Palette), info.Scale, true) }; + var pxPos = wr.Viewport.WorldToViewPx(wr.ScreenPxPosition(self.CenterPosition) + boundsOffset) + sizeOffset; + return new IRenderable[] { new UISpriteRenderable(Anim.Image, self.CenterPosition, pxPos, Info.ZOffset, wr.Palette(Info.Palette), 1f) }; } + + public void Tick(Actor self) { Anim.Tick(); } } } diff --git a/OpenRA.Mods.Common/Traits/Render/WithRankDecoration.cs b/OpenRA.Mods.Common/Traits/Render/WithRankDecoration.cs index a9a5f65f3f..f25f22dd4a 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithRankDecoration.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithRankDecoration.cs @@ -21,7 +21,7 @@ namespace OpenRA.Mods.Common.Traits.Render protected override void UpgradeLevelChanged(Actor self, int oldLevel, int newLevel) { - PlaySingleFrame(newLevel - 1); + Anim.PlayFetchIndex(Info.Sequence, () => newLevel - 1); } } } diff --git a/OpenRA.Mods.Common/Traits/VeteranProductionIconOverlay.cs b/OpenRA.Mods.Common/Traits/VeteranProductionIconOverlay.cs index b08b9d2da1..ac2ade0a80 100644 --- a/OpenRA.Mods.Common/Traits/VeteranProductionIconOverlay.cs +++ b/OpenRA.Mods.Common/Traits/VeteranProductionIconOverlay.cs @@ -30,15 +30,9 @@ namespace OpenRA.Mods.Common.Traits [PaletteReference] public readonly string Palette = "chrome"; [Desc("Point on the production icon's used as reference for offsetting the overlay. ", - "Possible values are any combination of Top, VCenter, Bottom and Left, HCenter, Right separated by a comma.")] + "Possible values are combinations of Center, Top, Bottom, Left, Right.")] public readonly ReferencePoints ReferencePoint = ReferencePoints.Top | ReferencePoints.Left; - [Desc("Pixel offset relative to the icon's reference point.")] - public readonly int2 Offset = int2.Zero; - - [Desc("Visual scale of the overlay.")] - public readonly float Scale = 1f; - public object Create(ActorInitializer init) { return new VeteranProductionIconOverlay(init, this); } } @@ -76,55 +70,30 @@ namespace OpenRA.Mods.Common.Traits } } - public Sprite Sprite() + Sprite IProductionIconOverlay.Sprite { get { return sprite; } } + string IProductionIconOverlay.Palette { get { return info.Palette; } } + float2 IProductionIconOverlay.Offset(float2 iconSize) { - return sprite; + float x = 0; + float y = 0; + if (info.ReferencePoint.HasFlag(ReferencePoints.Top)) + y -= iconSize.Y / 2 - sprite.Size.Y / 2; + else if (info.ReferencePoint.HasFlag(ReferencePoints.Bottom)) + y += iconSize.Y / 2 - sprite.Size.Y / 2; + + if (info.ReferencePoint.HasFlag(ReferencePoints.Left)) + x -= iconSize.X / 2 - sprite.Size.X / 2; + else if (info.ReferencePoint.HasFlag(ReferencePoints.Right)) + x += iconSize.X / 2 - sprite.Size.X / 2; + + return new float2(x, y); } - public string Palette() - { - return info.Palette; - } - - public float Scale() - { - return info.Scale; - } - - public float2 Offset(float2 iconSize) - { - float offsetX = 0, offsetY = 0; - switch (info.ReferencePoint & (ReferencePoints)3) - { - case ReferencePoints.Top: - offsetY = (-iconSize.Y + sprite.Size.Y) / 2; - break; - case ReferencePoints.VCenter: - break; - case ReferencePoints.Bottom: - offsetY = (iconSize.Y - sprite.Size.Y) / 2; - break; - } - - switch (info.ReferencePoint & (ReferencePoints)(3 << 2)) - { - case ReferencePoints.Left: - offsetX = (-iconSize.X + sprite.Size.X) / 2; - break; - case ReferencePoints.HCenter: - break; - case ReferencePoints.Right: - offsetX = (iconSize.X - sprite.Size.X) / 2; - break; - } - - return new float2(offsetX, offsetY) + info.Offset; - } - - public bool IsOverlayActive(ActorInfo ai) + bool IProductionIconOverlay.IsOverlayActive(ActorInfo ai) { bool isActive; - overlayActive.TryGetValue(ai, out isActive); + if (!overlayActive.TryGetValue(ai, out isActive)) + return false; return isActive; } diff --git a/OpenRA.Mods.Common/TraitsInterfaces.cs b/OpenRA.Mods.Common/TraitsInterfaces.cs index a8b336a8e4..d23226482d 100644 --- a/OpenRA.Mods.Common/TraitsInterfaces.cs +++ b/OpenRA.Mods.Common/TraitsInterfaces.cs @@ -77,9 +77,8 @@ namespace OpenRA.Mods.Common.Traits public interface IProductionIconOverlay { - Sprite Sprite(); - string Palette(); - float Scale(); + Sprite Sprite { get; } + string Palette { get; } float2 Offset(float2 iconSize); bool IsOverlayActive(ActorInfo ai); } diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs index 81d69d5a62..36faf6f5b1 100644 --- a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs +++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs @@ -14,6 +14,7 @@ using System.Drawing; using System.Globalization; using System.Linq; using OpenRA.Graphics; +using OpenRA.Traits; namespace OpenRA.Mods.Common.UtilityCommands { @@ -2702,6 +2703,44 @@ namespace OpenRA.Mods.Common.UtilityCommands node.Value.Nodes.Add(new MiniYamlNode("ParticleSize", "1, 1")); } + // Overhauled the actor decorations traits + if (engineVersion < 20151226) + { + if (depth == 1 && (node.Key.StartsWith("WithDecoration") || node.Key.StartsWith("WithRankDecoration"))) + { + node.Value.Nodes.RemoveAll(n => n.Key == "Scale"); + node.Value.Nodes.RemoveAll(n => n.Key == "Offset"); + var sd = node.Value.Nodes.FirstOrDefault(n => n.Key == "SelectionDecoration"); + if (sd != null) + sd.Key = "RequiresSelection"; + + var reference = node.Value.Nodes.FirstOrDefault(n => n.Key == "ReferencePoint"); + if (reference != null) + { + var values = FieldLoader.GetValue("ReferencePoint", reference.Value.Value); + values = values.Where(v => v != "HCenter" && v != "VCenter").ToArray(); + if (values.Length == 0) + values = new[] { "Center" }; + + reference.Value.Value = FieldSaver.FormatValue(values); + } + + var stance = Stance.Ally; + var showToAllies = node.Value.Nodes.FirstOrDefault(n => n.Key == "ShowToAllies"); + if (showToAllies != null && !FieldLoader.GetValue("ShowToAllies", showToAllies.Value.Value)) + stance ^= Stance.Ally; + var showToEnemies = node.Value.Nodes.FirstOrDefault(n => n.Key == "ShowToEnemies"); + if (showToEnemies != null && FieldLoader.GetValue("ShowToEnemies", showToEnemies.Value.Value)) + stance |= Stance.Enemy; + + if (stance != Stance.Ally) + node.Value.Nodes.Add(new MiniYamlNode("Stance", FieldSaver.FormatValue(stance))); + + node.Value.Nodes.RemoveAll(n => n.Key == "ShowToAllies"); + node.Value.Nodes.RemoveAll(n => n.Key == "ShowToEnemies"); + } + } + UpgradeActorRules(engineVersion, ref node.Value.Nodes, node, depth + 1); } } diff --git a/OpenRA.Mods.Common/Widgets/ObserverProductionIconsWidget.cs b/OpenRA.Mods.Common/Widgets/ObserverProductionIconsWidget.cs index a36203143e..13bc78a887 100644 --- a/OpenRA.Mods.Common/Widgets/ObserverProductionIconsWidget.cs +++ b/OpenRA.Mods.Common/Widgets/ObserverProductionIconsWidget.cs @@ -91,10 +91,11 @@ namespace OpenRA.Mods.Common.Widgets var location = new float2(RenderBounds.Location) + new float2(queue.i * (IconWidth + IconSpacing), 0); WidgetUtils.DrawSHPCentered(icon.Image, location + 0.5f * iconSize, worldRenderer.Palette(bi.IconPalette), 0.5f); - var pio = queue.Trait.Actor.Owner.PlayerActor.TraitsImplementing().FirstOrDefault(); - if (pio != null && pio.IsOverlayActive(actor)) - WidgetUtils.DrawSHPCentered(pio.Sprite(), location + 0.5f * iconSize + pio.Offset(0.5f * iconSize), - worldRenderer.Palette(pio.Palette()), 0.5f * pio.Scale()); + var pio = queue.Trait.Actor.Owner.PlayerActor.TraitsImplementing() + .FirstOrDefault(p => p.IsOverlayActive(actor)); + if (pio != null) + WidgetUtils.DrawSHPCentered(pio.Sprite, location + 0.5f * iconSize + pio.Offset(0.5f * iconSize), + worldRenderer.Palette(pio.Palette), 0.5f); var clock = clocks[queue.Trait]; clock.PlayFetchIndex(ClockSequence, diff --git a/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs b/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs index 750848a9be..c4a5e5339f 100644 --- a/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs +++ b/OpenRA.Mods.Common/Widgets/ProductionPaletteWidget.cs @@ -374,8 +374,7 @@ namespace OpenRA.Mods.Common.Widgets var buildableItems = CurrentQueue.BuildableItems(); - var pio = currentQueue.Actor.Owner.PlayerActor.TraitsImplementing().FirstOrDefault(); - var pioOffset = pio != null ? pio.Offset(IconSize) : new float2(0, 0); + var pios = currentQueue.Actor.Owner.PlayerActor.TraitsImplementing(); // Icons foreach (var icon in icons.Values) @@ -383,8 +382,9 @@ namespace OpenRA.Mods.Common.Widgets WidgetUtils.DrawSHPCentered(icon.Sprite, icon.Pos + iconOffset, icon.Palette); // Draw the ProductionIconOverlay's sprite - if (pio != null && pio.IsOverlayActive(icon.Actor)) - WidgetUtils.DrawSHPCentered(pio.Sprite(), icon.Pos + iconOffset + pioOffset, worldRenderer.Palette(pio.Palette()), pio.Scale()); + var pio = pios.FirstOrDefault(p => p.IsOverlayActive(icon.Actor)); + if (pio != null) + WidgetUtils.DrawSHPCentered(pio.Sprite, icon.Pos + iconOffset + pio.Offset(IconSize), worldRenderer.Palette(pio.Palette), 1f); // Build progress if (icon.Queued.Count > 0) diff --git a/mods/cnc/rules/defaults.yaml b/mods/cnc/rules/defaults.yaml index b8c216af15..7e5ca40f69 100644 --- a/mods/cnc/rules/defaults.yaml +++ b/mods/cnc/rules/defaults.yaml @@ -48,7 +48,6 @@ Sequence: rank Palette: effect ReferencePoint: Bottom, Right - Offset: 2, 2 UpgradeTypes: rank ZOffset: 256 UpgradeMinEnabledLevel: 1 diff --git a/mods/d2k/rules/defaults.yaml b/mods/d2k/rules/defaults.yaml index 689e59978d..93b3974a14 100644 --- a/mods/d2k/rules/defaults.yaml +++ b/mods/d2k/rules/defaults.yaml @@ -48,7 +48,6 @@ Sequence: rank Palette: effect ReferencePoint: Bottom, Right - Offset: 2, 2 UpgradeTypes: rank ZOffset: 256 UpgradeMinEnabledLevel: 1 diff --git a/mods/d2k/rules/structures.yaml b/mods/d2k/rules/structures.yaml index 6ffa3bae67..02fcc2128f 100644 --- a/mods/d2k/rules/structures.yaml +++ b/mods/d2k/rules/structures.yaml @@ -88,7 +88,7 @@ construction_yard: Prerequisites: upgrade.conyard Upgrades: stardecoration WithDecoration@upgraded: - SelectionDecoration: true + RequiresSelection: true Image: pips Sequence: tag-upgraded ReferencePoint: Top, Right @@ -191,7 +191,7 @@ barracks: Prerequisites: upgrade.barracks Upgrades: stardecoration WithDecoration@upgraded: - SelectionDecoration: true + RequiresSelection: true Image: pips Sequence: tag-upgraded ReferencePoint: Top, Right @@ -359,7 +359,7 @@ light_factory: Prerequisites: upgrade.light Upgrades: stardecoration WithDecoration@upgraded: - SelectionDecoration: true + RequiresSelection: true Image: pips Sequence: tag-upgraded ReferencePoint: Top, Right @@ -432,7 +432,7 @@ heavy_factory: Prerequisites: upgrade.heavy Upgrades: stardecoration WithDecoration@upgraded: - SelectionDecoration: true + RequiresSelection: true Image: pips Sequence: tag-upgraded ReferencePoint: Top, Right @@ -782,7 +782,7 @@ high_tech_factory: Prerequisites: upgrade.hightech Upgrades: stardecoration WithDecoration@upgraded: - SelectionDecoration: true + RequiresSelection: true Image: pips Sequence: tag-upgraded ReferencePoint: Top, Right diff --git a/mods/ra/rules/defaults.yaml b/mods/ra/rules/defaults.yaml index 1a9b053632..cb3d9c215b 100644 --- a/mods/ra/rules/defaults.yaml +++ b/mods/ra/rules/defaults.yaml @@ -47,7 +47,6 @@ Sequence: rank Palette: effect ReferencePoint: Bottom, Right - Offset: 2, 2 UpgradeTypes: rank ZOffset: 256 UpgradeMinEnabledLevel: 1 diff --git a/mods/ra/rules/infantry.yaml b/mods/ra/rules/infantry.yaml index 8c1461e515..59e1d52dd2 100644 --- a/mods/ra/rules/infantry.yaml +++ b/mods/ra/rules/infantry.yaml @@ -218,7 +218,6 @@ SPY: Sequence: pip-disguise Palette: effect ReferencePoint: Top, Right - Offset: 4, -2 ZOffset: 256 UpgradeTypes: disguise UpgradeMinEnabledLevel: 1 diff --git a/mods/ra/rules/player.yaml b/mods/ra/rules/player.yaml index 59c823338f..9361dd786e 100644 --- a/mods/ra/rules/player.yaml +++ b/mods/ra/rules/player.yaml @@ -66,7 +66,6 @@ Player: GlobalUpgradeManager: EnemyWatcher: VeteranProductionIconOverlay: - Offset: 2, 2 Image: iconchevrons Sequence: veteran diff --git a/mods/ra/sequences/misc.yaml b/mods/ra/sequences/misc.yaml index fa64748390..2e3df3bee9 100644 --- a/mods/ra/sequences/misc.yaml +++ b/mods/ra/sequences/misc.yaml @@ -73,6 +73,7 @@ pips: pip-disguise: pip-disguise Length: * Tick: 300 + Offset: 0, -6 v2: idle: @@ -370,6 +371,7 @@ rank: iconchevrons: veteran: + Offset: 2, 2 atomic: up: atomicup diff --git a/mods/ts/rules/civilian-infantry.yaml b/mods/ts/rules/civilian-infantry.yaml index 6affd0532c..6e2d74cfb9 100644 --- a/mods/ts/rules/civilian-infantry.yaml +++ b/mods/ts/rules/civilian-infantry.yaml @@ -65,7 +65,6 @@ CHAMSPY: Sequence: pip-disguise Palette: pips ReferencePoint: Top, Right - Offset: 4, -2 ZOffset: 256 UpgradeTypes: disguise UpgradeMinEnabledLevel: 1 diff --git a/mods/ts/rules/defaults.yaml b/mods/ts/rules/defaults.yaml index b827cfd787..0da8cfcfaa 100644 --- a/mods/ts/rules/defaults.yaml +++ b/mods/ts/rules/defaults.yaml @@ -44,7 +44,6 @@ Sequence: rank Palette: ra ReferencePoint: Bottom, Right - Offset: 2, 2 UpgradeTypes: rank ZOffset: 256 UpgradeMinEnabledLevel: 1 diff --git a/mods/ts/sequences/misc.yaml b/mods/ts/sequences/misc.yaml index c890696a23..82ba9a3275 100644 --- a/mods/ts/sequences/misc.yaml +++ b/mods/ts/sequences/misc.yaml @@ -107,6 +107,8 @@ pips: pip-disguise: pip-disguise Length: * Tick: 300 + Offset: 0, -6 + # TODO: pip-empty-building: pip-green-building: