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;