diff --git a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs index 186484521f..e9daf54e2a 100644 --- a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs +++ b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs @@ -302,6 +302,12 @@ namespace OpenRA.Traits .SelectMany(ff => ff.Render(wr)); } + public IEnumerable ScreenBounds(Actor self, WorldRenderer wr) + { + // Player-actor render traits don't require screen bounds + yield break; + } + public FrozenActor FromID(uint id) { FrozenActor fa; diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index 2be4fe1e40..307587481d 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -96,7 +96,11 @@ namespace OpenRA.Traits public interface ITick { void Tick(Actor self); } [RequireExplicitImplementation] public interface ITickRender { void TickRender(WorldRenderer wr, Actor self); } - public interface IRender { IEnumerable Render(Actor self, WorldRenderer wr); } + public interface IRender + { + IEnumerable Render(Actor self, WorldRenderer wr); + IEnumerable ScreenBounds(Actor self, WorldRenderer wr); + } public interface IAutoSelectionSizeInfo : ITraitInfoInterface { } public interface IAutoSelectionSize { int2 SelectionSize(Actor self); } diff --git a/OpenRA.Mods.Cnc/Traits/Render/WithCargo.cs b/OpenRA.Mods.Cnc/Traits/Render/WithCargo.cs index 229ae968c2..14e3bdbf4d 100644 --- a/OpenRA.Mods.Cnc/Traits/Render/WithCargo.cs +++ b/OpenRA.Mods.Cnc/Traits/Render/WithCargo.cs @@ -10,6 +10,7 @@ #endregion using System.Collections.Generic; +using System.Drawing; using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common; @@ -98,6 +99,14 @@ namespace OpenRA.Mods.Cnc.Traits.Render } } + IEnumerable IRender.ScreenBounds(Actor self, WorldRenderer wr) + { + var pos = self.CenterPosition; + foreach (var p in previews.Values.SelectMany(p => p)) + foreach (var b in p.ScreenBounds(wr, pos)) + yield return b; + } + void INotifyPassengerEntered.OnPassengerEntered(Actor self, Actor passenger) { if (info.DisplayTypes.Contains(passenger.Trait().Info.CargoType)) diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackGarrisoned.cs b/OpenRA.Mods.Common/Traits/Attack/AttackGarrisoned.cs index b7d373fc23..f340bcf3b2 100644 --- a/OpenRA.Mods.Common/Traits/Attack/AttackGarrisoned.cs +++ b/OpenRA.Mods.Common/Traits/Attack/AttackGarrisoned.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Traits.Render; @@ -189,7 +190,7 @@ namespace OpenRA.Mods.Common.Traits } } - public IEnumerable Render(Actor self, WorldRenderer wr) + IEnumerable IRender.Render(Actor self, WorldRenderer wr) { var pal = wr.Palette(Info.MuzzlePalette); @@ -199,6 +200,12 @@ namespace OpenRA.Mods.Common.Traits yield return r; } + IEnumerable IRender.ScreenBounds(Actor self, WorldRenderer wr) + { + // Muzzle flashes don't contribute to actor bounds + yield break; + } + protected override void Tick(Actor self) { base.Tick(self); diff --git a/OpenRA.Mods.Common/Traits/Buildings/Bridge.cs b/OpenRA.Mods.Common/Traits/Buildings/Bridge.cs index 7b390faccb..4e572de0c0 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/Bridge.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/Bridge.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using OpenRA.Effects; using OpenRA.GameRules; @@ -216,6 +217,25 @@ namespace OpenRA.Mods.Common.Traits return renderables[template]; } + public IEnumerable ScreenBounds(Actor self, WorldRenderer wr) + { + foreach (var kv in footprint) + { + var xy = wr.ScreenPxPosition(wr.World.Map.CenterOfCell(kv.Key)); + var size = wr.Theater.TileSprite(new TerrainTile(template, kv.Value)).Bounds.Size; + + // Add an extra pixel padding to avoid issues with odd-sized sprites + var halfWidth = size.Width / 2 + 1; + var halfHeight = size.Height / 2 + 1; + + yield return Rectangle.FromLTRB( + xy.X - halfWidth, + xy.Y - halfHeight, + xy.X + halfWidth, + xy.Y + halfHeight); + } + } + void KillUnitsOnBridge() { foreach (var c in footprint.Keys) diff --git a/OpenRA.Mods.Common/Traits/Carryall.cs b/OpenRA.Mods.Common/Traits/Carryall.cs index 3145b5b0eb..0a2af06724 100644 --- a/OpenRA.Mods.Common/Traits/Carryall.cs +++ b/OpenRA.Mods.Common/Traits/Carryall.cs @@ -192,6 +192,17 @@ namespace OpenRA.Mods.Common.Traits } } + IEnumerable IRender.ScreenBounds(Actor self, WorldRenderer wr) + { + if (carryablePreview == null) + yield break; + + var pos = self.CenterPosition; + foreach (var p in carryablePreview) + foreach (var b in p.ScreenBounds(wr, pos)) + yield return b; + } + IEnumerable IIssueOrder.Orders { get diff --git a/OpenRA.Mods.Common/Traits/Contrail.cs b/OpenRA.Mods.Common/Traits/Contrail.cs index 1dd82dd956..08d5d65b3b 100644 --- a/OpenRA.Mods.Common/Traits/Contrail.cs +++ b/OpenRA.Mods.Common/Traits/Contrail.cs @@ -71,6 +71,12 @@ namespace OpenRA.Mods.Common.Traits return new IRenderable[] { trail }; } + IEnumerable IRender.ScreenBounds(Actor self, WorldRenderer wr) + { + // Contrails don't contribute to actor bounds + yield break; + } + void INotifyAddedToWorld.AddedToWorld(Actor self) { trail = new ContrailRenderable(self.World, color, info.TrailWidth, info.TrailLength, 0, info.ZOffset); diff --git a/OpenRA.Mods.Common/Traits/Render/CustomTerrainDebugOverlay.cs b/OpenRA.Mods.Common/Traits/Render/CustomTerrainDebugOverlay.cs index 0d866260dd..f0f3876d4f 100644 --- a/OpenRA.Mods.Common/Traits/Render/CustomTerrainDebugOverlay.cs +++ b/OpenRA.Mods.Common/Traits/Render/CustomTerrainDebugOverlay.cs @@ -75,5 +75,11 @@ namespace OpenRA.Mods.Common.Traits yield return new TextRenderable(font, center, 0, info.Color, info.Type); } } + + IEnumerable IRender.ScreenBounds(Actor self, WorldRenderer wr) + { + // World-actor render traits don't require screen bounds + yield break; + } } } diff --git a/OpenRA.Mods.Common/Traits/Render/RenderNameTag.cs b/OpenRA.Mods.Common/Traits/Render/RenderNameTag.cs index d0e0ad0fcb..d6a2e405f9 100644 --- a/OpenRA.Mods.Common/Traits/Render/RenderNameTag.cs +++ b/OpenRA.Mods.Common/Traits/Render/RenderNameTag.cs @@ -54,5 +54,11 @@ namespace OpenRA.Mods.Common.Traits.Render return new IRenderable[] { new TextRenderable(font, effectPos, 0, color, name) }; } + + IEnumerable IRender.ScreenBounds(Actor self, WorldRenderer wr) + { + // Name tags don't contribute to actor bounds + yield break; + } } } diff --git a/OpenRA.Mods.Common/Traits/Render/RenderSprites.cs b/OpenRA.Mods.Common/Traits/Render/RenderSprites.cs index dbefb874f3..0153ae5128 100644 --- a/OpenRA.Mods.Common/Traits/Render/RenderSprites.cs +++ b/OpenRA.Mods.Common/Traits/Render/RenderSprites.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Graphics; @@ -181,6 +182,13 @@ namespace OpenRA.Mods.Common.Traits.Render } } + public virtual IEnumerable ScreenBounds(Actor self, WorldRenderer wr) + { + foreach (var a in anims) + if (a.IsVisible) + yield return a.Animation.ScreenBounds(self, wr, info.Scale); + } + void ITick.Tick(Actor self) { Tick(self); diff --git a/OpenRA.Mods.Common/Traits/Render/RenderVoxels.cs b/OpenRA.Mods.Common/Traits/Render/RenderVoxels.cs index ae03812e3e..56afd11ec4 100644 --- a/OpenRA.Mods.Common/Traits/Render/RenderVoxels.cs +++ b/OpenRA.Mods.Common/Traits/Render/RenderVoxels.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Graphics; @@ -91,7 +92,7 @@ namespace OpenRA.Mods.Common.Traits.Render public void OnOwnerChanged(Actor self, Player oldOwner, Player newOwner) { initializePalettes = true; } protected PaletteReference colorPalette, normalsPalette, shadowPalette; - public IEnumerable Render(Actor self, WorldRenderer wr) + IEnumerable IRender.Render(Actor self, WorldRenderer wr) { if (initializePalettes) { @@ -108,6 +109,14 @@ namespace OpenRA.Mods.Common.Traits.Render colorPalette, normalsPalette, shadowPalette) }; } + IEnumerable IRender.ScreenBounds(Actor self, WorldRenderer wr) + { + var pos = self.CenterPosition; + foreach (var c in components) + if (c.IsVisible) + yield return c.ScreenBounds(pos, wr, info.Scale); + } + public string Image { get { return info.Image ?? self.Info.Name; } } public void Add(ModelAnimation v) { components.Add(v); } public void Remove(ModelAnimation v) { components.Remove(v); } diff --git a/OpenRA.Mods.Common/Traits/Render/WithMuzzleOverlay.cs b/OpenRA.Mods.Common/Traits/Render/WithMuzzleOverlay.cs index fe27703882..70f3a5edd8 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithMuzzleOverlay.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithMuzzleOverlay.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using OpenRA.Graphics; using OpenRA.Traits; @@ -86,7 +87,7 @@ namespace OpenRA.Mods.Common.Traits.Render void INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel) { } - public IEnumerable Render(Actor self, WorldRenderer wr) + IEnumerable IRender.Render(Actor self, WorldRenderer wr) { foreach (var arm in armaments) { @@ -103,6 +104,12 @@ namespace OpenRA.Mods.Common.Traits.Render } } + IEnumerable IRender.ScreenBounds(Actor self, WorldRenderer wr) + { + // Muzzle flashes don't contribute to actor bounds + yield break; + } + void ITick.Tick(Actor self) { foreach (var a in anims.Values) diff --git a/OpenRA.Mods.Common/Traits/Render/WithParachute.cs b/OpenRA.Mods.Common/Traits/Render/WithParachute.cs index 1cf0884533..31087dfd51 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithParachute.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithParachute.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Graphics; @@ -154,7 +155,7 @@ namespace OpenRA.Mods.Common.Traits.Render shadow.Tick(); } - public IEnumerable Render(Actor self, WorldRenderer wr) + IEnumerable IRender.Render(Actor self, WorldRenderer wr) { if (info.ShadowImage == null) return Enumerable.Empty(); @@ -170,5 +171,21 @@ namespace OpenRA.Mods.Common.Traits.Render var palette = wr.Palette(info.ShadowPalette); return new IRenderable[] { new SpriteRenderable(shadow.Image, pos, info.ShadowOffset, info.ShadowZOffset, palette, 1, true) }; } + + IEnumerable IRender.ScreenBounds(Actor self, WorldRenderer wr) + { + if (info.ShadowImage == null) + return Enumerable.Empty(); + + if (IsTraitDisabled || self.IsDead || !self.IsInWorld) + return Enumerable.Empty(); + + if (self.World.FogObscures(self)) + return Enumerable.Empty(); + + var dat = self.World.Map.DistanceAboveTerrain(self.CenterPosition); + var pos = self.CenterPosition - new WVec(0, 0, dat.Length); + return new Rectangle[] { shadow.ScreenBounds(wr, pos, info.ShadowOffset, 1) }; + } } } diff --git a/OpenRA.Mods.Common/Traits/Render/WithTextDecoration.cs b/OpenRA.Mods.Common/Traits/Render/WithTextDecoration.cs index 719e081503..6b9224e359 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithTextDecoration.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithTextDecoration.cs @@ -75,6 +75,12 @@ namespace OpenRA.Mods.Common.Traits.Render return !Info.RequiresSelection ? RenderInner(self, wr) : SpriteRenderable.None; } + IEnumerable IRender.ScreenBounds(Actor self, WorldRenderer wr) + { + // Text decorations don't contribute to actor bounds + yield break; + } + IEnumerable IRenderAboveShroudWhenSelected.RenderAboveShroud(Actor self, WorldRenderer wr) { return Info.RequiresSelection ? RenderInner(self, wr) : SpriteRenderable.None; diff --git a/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs b/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs index daddb1b447..c8d8b83765 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs @@ -98,6 +98,12 @@ namespace OpenRA.Mods.Common.Traits .SelectMany(p => p.Render()); } + IEnumerable IRender.ScreenBounds(Actor self, WorldRenderer wr) + { + // World-actor render traits don't require screen bounds + yield break; + } + public EditorActorPreview Add(ActorReference reference) { return Add(NextActorName(), reference); } EditorActorPreview Add(string id, ActorReference reference, bool initialSetup = false)