diff --git a/OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs b/OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs index d39de8cd47..a69088caa6 100644 --- a/OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs +++ b/OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs @@ -43,14 +43,65 @@ namespace OpenRA.Mods.Common.Warheads [Desc("Chance of impact sound to play.")] public readonly int ImpactSoundChance = 100; - [Desc("Explosions above this altitude that don't impact an actor will check target validity against the 'TargetTypeAir' target types.")] + [Desc("Consider explosion above this altitude an air explosion.", + "If that's the case, this warhead will consider the explosion position to have the 'Air' TargetType (in addition to any nearby actor's TargetTypes).")] public readonly WDist AirThreshold = new WDist(128); - [Desc("Target types to use when the warhead detonated at an altitude greater than 'AirThreshold'.")] + [Desc("Whether to consider actors in determining whether the explosion should happen. If false, only terrain will be considered.")] + public readonly bool ImpactActors = true; + static readonly BitSet TargetTypeAir = new BitSet("Air"); - [Desc("Check for direct hits against nearby actors for use in the target validity checks.")] - public readonly bool ImpactActors = true; + public ImpactType GetImpactType(World world, CPos cell, WPos pos, Actor firedBy) + { + // Matching target actor + if (ImpactActors) + { + var targetType = GetDirectHitTargetType(world, cell, pos, firedBy, true); + if (targetType == ImpactTargetType.ValidActor) + return ImpactType.TargetHit; + if (targetType == ImpactTargetType.InvalidActor) + return ImpactType.None; + } + + var dat = world.Map.DistanceAboveTerrain(pos); + if (dat > AirThreshold) + return ImpactType.Air; + + return ImpactType.Ground; + } + + public ImpactTargetType GetDirectHitTargetType(World world, CPos cell, WPos pos, Actor firedBy, bool checkTargetValidity = false) + { + var victims = world.FindActorsOnCircle(pos, WDist.Zero); + var invalidHit = false; + + foreach (var victim in victims) + { + if (!AffectsParent && victim == firedBy) + continue; + + if (!victim.Info.HasTraitInfo()) + continue; + + // If the impact position is within any HitShape, we have a direct hit + var activeShapes = victim.TraitsImplementing().Where(Exts.IsTraitEnabled); + var directHit = activeShapes.Any(i => i.Info.Type.DistanceFromEdge(pos, victim).Length <= 0); + + // If the warhead landed outside the actor's hit-shape(s), we need to skip the rest so it won't be considered an invalidHit + if (!directHit) + continue; + + if (!checkTargetValidity || IsValidAgainst(victim, firedBy)) + return ImpactTargetType.ValidActor; + + // If we got here, it must be an invalid target + invalidHit = true; + } + + // If there was at least a single direct hit, but none on valid target(s), we return InvalidActor + return invalidHit ? ImpactTargetType.InvalidActor : ImpactTargetType.NoActor; + } public override void DoImpact(Target target, Actor firedBy, IEnumerable damageModifiers) { @@ -72,9 +123,11 @@ namespace OpenRA.Mods.Common.Warheads var explosion = Explosions.RandomOrDefault(world.LocalRandom); if (Image != null && explosion != null) { - var dat = world.Map.DistanceAboveTerrain(pos); if (ForceDisplayAtGroundLevel) - pos -= new WVec(0, 0, dat.Length); + { + var dat = world.Map.DistanceAboveTerrain(pos); + pos = new WPos(pos.X, pos.Y, pos.Z - dat.Length); + } world.AddFrameEndTask(w => w.Add(new SpriteEffect(pos, w, Image, explosion, palette))); } @@ -87,32 +140,23 @@ namespace OpenRA.Mods.Common.Warheads public bool IsValidImpact(WPos pos, Actor firedBy) { var world = firedBy.World; - - if (ImpactActors) - { - // Check whether the explosion overlaps with an actor's hitshape - var potentialVictims = world.FindActorsOnCircle(pos, WDist.Zero); - foreach (var victim in potentialVictims) - { - if (!AffectsParent && victim == firedBy) - continue; - - var activeShapes = victim.TraitsImplementing().Where(Exts.IsTraitEnabled); - if (!activeShapes.Any(i => i.Info.Type.DistanceFromEdge(pos, victim).Length <= 0)) - continue; - - if (IsValidAgainst(victim, firedBy)) - return true; - } - } - var targetTile = world.Map.CellContaining(pos); if (!world.Map.Contains(targetTile)) return false; - var dat = world.Map.DistanceAboveTerrain(pos); - var tileInfo = world.Map.GetTerrainInfo(targetTile); - return IsValidTarget(dat > AirThreshold ? TargetTypeAir : tileInfo.TargetTypes); + var impactType = GetImpactType(world, targetTile, pos, firedBy); + switch (impactType) + { + case ImpactType.TargetHit: + return true; + case ImpactType.Air: + return IsValidTarget(TargetTypeAir); + case ImpactType.Ground: + var tileInfo = world.Map.GetTerrainInfo(targetTile); + return IsValidTarget(tileInfo.TargetTypes); + default: + return false; + } } } } diff --git a/OpenRA.Mods.Common/Warheads/Warhead.cs b/OpenRA.Mods.Common/Warheads/Warhead.cs index fe672db9ad..5b60c3560a 100644 --- a/OpenRA.Mods.Common/Warheads/Warhead.cs +++ b/OpenRA.Mods.Common/Warheads/Warhead.cs @@ -15,6 +15,14 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Warheads { + public enum ImpactType + { + None, + Ground, + Air, + TargetHit + } + public enum ImpactTargetType { NoActor,