diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs b/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs index c941d439ce..f375a35257 100644 --- a/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs +++ b/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs @@ -287,6 +287,17 @@ namespace OpenRA.Mods.Common.Traits && (target.IsInRange(self.CenterPosition, GetMaximumRange()) || (allowMove && self.Info.HasTraitInfo())); } + public Stance UnforcedAttackTargetStances() + { + // PERF: Avoid LINQ. + var stances = Stance.None; + foreach (var armament in Armaments) + if (!armament.IsTraitDisabled) + stances |= armament.Info.TargetStances; + + return stances; + } + class AttackOrderTargeter : IOrderTargeter { readonly AttackBase ab; diff --git a/OpenRA.Mods.Common/Traits/AutoTarget.cs b/OpenRA.Mods.Common/Traits/AutoTarget.cs index 38882c3822..c1a4a146f3 100644 --- a/OpenRA.Mods.Common/Traits/AutoTarget.cs +++ b/OpenRA.Mods.Common/Traits/AutoTarget.cs @@ -140,8 +140,14 @@ namespace OpenRA.Mods.Common.Traits if (nextScanTime <= 0) { nextScanTime = self.World.SharedRandom.Next(info.MinimumScanTimeInterval, info.MaximumScanTimeInterval); - var range = info.ScanRadius > 0 ? WDist.FromCells(info.ScanRadius) : attack.GetMaximumRange(); - return ChooseTarget(self, range, allowMove); + + // If we can't attack right now, there's no need to try and find a target. + var attackStances = attack.UnforcedAttackTargetStances(); + if (attackStances != OpenRA.Traits.Stance.None) + { + var range = info.ScanRadius > 0 ? WDist.FromCells(info.ScanRadius) : attack.GetMaximumRange(); + return ChooseTarget(self, attackStances, range, allowMove); + } } return null; @@ -162,12 +168,19 @@ namespace OpenRA.Mods.Common.Traits attack.AttackTarget(target, false, allowMove); } - Actor ChooseTarget(Actor self, WDist range, bool allowMove) + Actor ChooseTarget(Actor self, Stance attackStances, WDist range, bool allowMove) { var actorsByArmament = new Dictionary>(); var actorsInRange = self.World.FindActorsInCircle(self.CenterPosition, range); foreach (var actor in actorsInRange) { + // PERF: Most units can only attack enemy units. If this is the case but the target is not an enemy, we + // can bail early and avoid the more expensive targeting checks and armament selection. For groups of + // allied units, this helps significantly reduce the cost of auto target scans. This is important as + // these groups will continuously rescan their allies until an enemy finally comes into range. + if (attackStances == OpenRA.Traits.Stance.Enemy && !actor.AppearsHostileTo(self)) + continue; + if (PreventsAutoTarget(self, actor) || !self.Owner.CanTargetActor(actor)) continue;