diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 3835365f5c..a36ebcb597 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -296,6 +296,7 @@ + diff --git a/OpenRA.Mods.Common/Projectiles/Bullet.cs b/OpenRA.Mods.Common/Projectiles/Bullet.cs index 91fd33fe62..b63aa54b9e 100644 --- a/OpenRA.Mods.Common/Projectiles/Bullet.cs +++ b/OpenRA.Mods.Common/Projectiles/Bullet.cs @@ -12,6 +12,7 @@ using System; using System.Collections.Generic; using System.Drawing; +using System.Linq; using OpenRA.Effects; using OpenRA.GameRules; using OpenRA.Graphics; @@ -302,12 +303,9 @@ namespace OpenRA.Mods.Common.Projectiles if (!info.ValidBounceBlockerStances.HasStance(victim.Owner.Stances[firedBy.Owner])) continue; - var healthInfo = victim.Info.TraitInfoOrDefault(); - if (healthInfo == null) - continue; - // If the impact position is within any actor's HitShape, we have a direct hit - if (healthInfo.Shape.DistanceFromEdge(pos, victim).Length <= 0) + var activeShapes = victim.TraitsImplementing().Where(Exts.IsTraitEnabled); + if (activeShapes.Any(i => i.Info.Type.DistanceFromEdge(pos, victim).Length <= 0)) return true; } diff --git a/OpenRA.Mods.Common/Traits/CombatDebugOverlay.cs b/OpenRA.Mods.Common/Traits/CombatDebugOverlay.cs index 8511e678ec..6d8a31f852 100644 --- a/OpenRA.Mods.Common/Traits/CombatDebugOverlay.cs +++ b/OpenRA.Mods.Common/Traits/CombatDebugOverlay.cs @@ -34,6 +34,7 @@ namespace OpenRA.Mods.Common.Traits readonly HealthInfo healthInfo; readonly Lazy coords; + HitShape[] shapes; IBlocksProjectiles[] allBlockers; ITargetablePositions[] targetablePositions; @@ -48,6 +49,7 @@ namespace OpenRA.Mods.Common.Traits void INotifyCreated.Created(Actor self) { + shapes = self.TraitsImplementing().ToArray(); allBlockers = self.TraitsImplementing().ToArray(); targetablePositions = self.TraitsImplementing().ToArray(); } @@ -60,9 +62,6 @@ namespace OpenRA.Mods.Common.Traits var wcr = Game.Renderer.WorldRgbaColorRenderer; var iz = 1 / wr.Viewport.Zoom; - if (healthInfo != null) - healthInfo.Shape.DrawCombatOverlay(wr, wcr, self); - var blockers = allBlockers.Where(Exts.IsTraitEnabled).ToList(); if (blockers.Count > 0) { @@ -75,6 +74,10 @@ namespace OpenRA.Mods.Common.Traits TargetLineRenderable.DrawTargetMarker(wr, hc, hb); } + var activeShapes = shapes.Where(Exts.IsTraitEnabled); + foreach (var s in activeShapes) + s.Info.Type.DrawCombatOverlay(wr, wcr, self); + var tc = Color.Lime; var enabledPositions = targetablePositions.Where(Exts.IsTraitEnabled); var positions = enabledPositions.SelectMany(tp => tp.TargetablePositions(self)); diff --git a/OpenRA.Mods.Common/Traits/Health.cs b/OpenRA.Mods.Common/Traits/Health.cs index 3e5a1ee2b6..14384fc9f7 100644 --- a/OpenRA.Mods.Common/Traits/Health.cs +++ b/OpenRA.Mods.Common/Traits/Health.cs @@ -10,7 +10,6 @@ #endregion using System.Linq; -using OpenRA.Mods.Common.HitShapes; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits @@ -22,36 +21,6 @@ namespace OpenRA.Mods.Common.Traits [Desc("Trigger interfaces such as AnnounceOnKill?")] public readonly bool NotifyAppliedDamage = true; - [FieldLoader.LoadUsing("LoadShape")] - public readonly IHitShape Shape; - - static object LoadShape(MiniYaml yaml) - { - IHitShape ret; - - var shapeNode = yaml.Nodes.Find(n => n.Key == "Shape"); - var shape = shapeNode != null ? shapeNode.Value.Value : string.Empty; - - if (!string.IsNullOrEmpty(shape)) - { - ret = Game.CreateObject(shape + "Shape"); - - try - { - FieldLoader.Load(ret, shapeNode.Value); - } - catch (YamlException e) - { - throw new YamlException("HitShape {0}: {1}".F(shape, e.Message)); - } - } - else - ret = new CircleShape(); - - ret.Initialize(); - return ret; - } - public virtual object Create(ActorInitializer init) { return new Health(init, this); } } diff --git a/OpenRA.Mods.Common/Traits/HitShape.cs b/OpenRA.Mods.Common/Traits/HitShape.cs new file mode 100644 index 0000000000..6778dcdc16 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/HitShape.cs @@ -0,0 +1,59 @@ +#region Copyright & License Information +/* + * Copyright 2007-2017 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.Linq; +using OpenRA.Mods.Common.HitShapes; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + [Desc("Shape of actor for damage calculations.")] + public class HitShapeInfo : ConditionalTraitInfo + { + [FieldLoader.LoadUsing("LoadShape")] + public readonly IHitShape Type; + + static object LoadShape(MiniYaml yaml) + { + IHitShape ret; + + var shapeNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Type"); + var shape = shapeNode != null ? shapeNode.Value.Value : string.Empty; + + if (!string.IsNullOrEmpty(shape)) + { + try + { + ret = Game.CreateObject(shape + "Shape"); + FieldLoader.Load(ret, shapeNode.Value); + } + catch (YamlException e) + { + throw new YamlException("HitShape {0}: {1}".F(shape, e.Message)); + } + } + else + ret = new CircleShape(); + + ret.Initialize(); + return ret; + } + + public override object Create(ActorInitializer init) { return new HitShape(init.Self, this); } + } + + public class HitShape : ConditionalTrait + { + public HitShape(Actor self, HitShapeInfo info) + : base(info) { } + } +} diff --git a/OpenRA.Mods.Common/Util.cs b/OpenRA.Mods.Common/Util.cs index c1b8f33521..bb9fd7f392 100644 --- a/OpenRA.Mods.Common/Util.cs +++ b/OpenRA.Mods.Common/Util.cs @@ -180,14 +180,14 @@ namespace OpenRA.Mods.Common // TODO: Investigate caching this or moving it to ActorMapInfo public static WDist MinimumRequiredVictimScanRadius(Ruleset rules) { - return rules.Actors.SelectMany(a => a.Value.TraitInfos()).Max(h => h.Shape.OuterRadius); + return rules.Actors.SelectMany(a => a.Value.TraitInfos()).Max(h => h.Type.OuterRadius); } // TODO: Investigate caching this or moving it to ActorMapInfo public static WDist MinimumRequiredBlockerScanRadius(Ruleset rules) { return rules.Actors.Where(a => a.Value.HasTraitInfo()) - .SelectMany(a => a.Value.TraitInfos()).Max(h => h.Shape.OuterRadius); + .SelectMany(a => a.Value.TraitInfos()).Max(h => h.Type.OuterRadius); } } } diff --git a/OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs b/OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs index d33e4b3997..243b2fb876 100644 --- a/OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs +++ b/OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs @@ -10,6 +10,7 @@ #endregion using System.Collections.Generic; +using System.Linq; using OpenRA.GameRules; using OpenRA.Mods.Common.Effects; using OpenRA.Mods.Common.Traits; @@ -88,12 +89,9 @@ namespace OpenRA.Mods.Common.Warheads if (checkTargetType && !IsValidAgainst(victim, firedBy)) continue; - var healthInfo = victim.Info.TraitInfoOrDefault(); - if (healthInfo == null) - continue; - - // If the impact position is within any actor's HitShape, we have a direct hit - if (healthInfo.Shape.DistanceFromEdge(pos, victim).Length <= 0) + // If the impact position is within any HitShape, we have a direct hit + var activeShapes = victim.TraitsImplementing().Where(Exts.IsTraitEnabled); + if (activeShapes.Any(i => i.Info.Type.DistanceFromEdge(pos, victim).Length <= 0)) return true; } diff --git a/OpenRA.Mods.Common/Warheads/SpreadDamageWarhead.cs b/OpenRA.Mods.Common/Warheads/SpreadDamageWarhead.cs index f4c4c0e6d0..db3bf5dfa4 100644 --- a/OpenRA.Mods.Common/Warheads/SpreadDamageWarhead.cs +++ b/OpenRA.Mods.Common/Warheads/SpreadDamageWarhead.cs @@ -10,6 +10,7 @@ #endregion using System.Collections.Generic; +using System.Linq; using OpenRA.GameRules; using OpenRA.Mods.Common.Traits; using OpenRA.Traits; @@ -66,11 +67,17 @@ namespace OpenRA.Mods.Common.Warheads foreach (var victim in hitActors) { + // Cannot be damaged without a Health trait var healthInfo = victim.Info.TraitInfoOrDefault(); if (healthInfo == null) continue; - var distance = healthInfo.Shape.DistanceFromEdge(pos, victim); + // Cannot be damaged without an active HitShape + var activeShapes = victim.TraitsImplementing().Where(Exts.IsTraitEnabled); + if (!activeShapes.Any()) + continue; + + var distance = activeShapes.Min(t => t.Info.Type.DistanceFromEdge(pos, victim)); var localModifiers = damageModifiers.Append(GetDamageFalloff(distance.Length)); DoImpact(victim, firedBy, localModifiers); diff --git a/OpenRA.Mods.Common/WorldExtensions.cs b/OpenRA.Mods.Common/WorldExtensions.cs index 589d7687dc..b0c6d903e0 100644 --- a/OpenRA.Mods.Common/WorldExtensions.cs +++ b/OpenRA.Mods.Common/WorldExtensions.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; +using System.Linq; using OpenRA.Mods.Common.Traits; namespace OpenRA.Mods.Common @@ -45,9 +46,9 @@ namespace OpenRA.Mods.Common foreach (var currActor in actorsInSquare) { var actorWidth = 0; - var healthInfo = currActor.Info.TraitInfoOrDefault(); - if (healthInfo != null) - actorWidth = healthInfo.Shape.OuterRadius.Length; + var shapes = currActor.TraitsImplementing().Where(Exts.IsTraitEnabled); + if (shapes.Any()) + actorWidth = shapes.Max(h => h.Info.Type.OuterRadius.Length); var projection = MinimumPointLineProjection(lineStart, lineEnd, currActor.CenterPosition); var distance = (currActor.CenterPosition - projection).HorizontalLength;