Skip to content

Commit

Permalink
Optimize AutoTarget.ChooseTarget.
Browse files Browse the repository at this point in the history
Avoid using LINQ for filtering, grouping and dictionary building. Instead, we do these ourselves to reduce overhead.

Actors are checked for AutoTargetIgnoreInfo (rather than the trait) since info checks are marginally faster. This check can now be done immediately to allow us to skip such actors right away.

The ClosestTo calculation is now only run on the group on actors for the selected armament, rather than all potential armaments.
  • Loading branch information
RoosterDragon committed Dec 12, 2015
1 parent 487727c commit 7fbbaa2
Showing 1 changed file with 39 additions and 25 deletions.
64 changes: 39 additions & 25 deletions OpenRA.Mods.Common/Traits/AutoTarget.cs
Expand Up @@ -164,43 +164,57 @@ void Attack(Actor self, Actor targetActor, bool allowMove)

Actor ChooseTarget(Actor self, WDist range, bool allowMove)
{
var inRange = self.World.FindActorsInCircle(self.CenterPosition, range)
.Where(a =>
!a.TraitsImplementing<IPreventsAutoTarget>().Any(t => t.PreventsAutoTarget(a, self)));
var actorsByArmament = new Dictionary<Armament, List<Actor>>();
var actorsInRange = self.World.FindActorsInCircle(self.CenterPosition, range);
foreach (var actor in actorsInRange)
{
if (PreventsAutoTarget(self, actor) || !self.Owner.CanTargetActor(actor))
continue;

// Select only the first compatible armament for each actor: if this actor is selected
// it will be thanks to the first armament anyways, since that is the first selection
// criterion
var target = Target.FromActor(actor);
var armaments = attack.ChooseArmamentsForTarget(target, false);
if (!allowMove)
armaments = armaments.Where(arm =>
target.IsInRange(self.CenterPosition, arm.MaxRange()) &&
!target.IsInRange(self.CenterPosition, arm.Weapon.MinRange));

var armament = armaments.FirstOrDefault();
if (armament == null)
continue;

List<Actor> actors;
if (actorsByArmament.TryGetValue(armament, out actors))
actors.Add(actor);
else
actorsByArmament.Add(armament, new List<Actor> { actor });
}

// Armaments are enumerated in attack.Armaments in construct order
// When autotargeting, first choose targets according to the used armament construct order
// And then according to distance from actor
// This enables preferential treatment of certain armaments
// (e.g. tesla trooper's tesla zap should have precedence over tesla charge)
var actorByArmament = inRange

// Select only the first compatible armament for each actor: if this actor is selected
// it will be thanks to the first armament anyways, since that is the first selection
// criterion
.Select(a =>
{
var target = Target.FromActor(a);
return new KeyValuePair<Armament, Actor>(
attack.ChooseArmamentsForTarget(target, false)
.FirstOrDefault(arm => allowMove
|| (target.IsInRange(self.CenterPosition, arm.MaxRange())
&& !target.IsInRange(self.CenterPosition, arm.Weapon.MinRange))), a);
})

.Where(kv => kv.Key != null && self.Owner.CanTargetActor(kv.Value))
.GroupBy(kv => kv.Key, kv => kv.Value)
.ToDictionary(kv => kv.Key, kv => kv.ClosestTo(self));

foreach (var arm in attack.Armaments)
{
Actor actor;
if (actorByArmament.TryGetValue(arm, out actor))
return actor;
List<Actor> actors;
if (actorsByArmament.TryGetValue(arm, out actors))
return actors.ClosestTo(self);
}

return null;
}

bool PreventsAutoTarget(Actor attacker, Actor target)
{
foreach (var pat in target.TraitsImplementing<IPreventsAutoTarget>())
if (pat.PreventsAutoTarget(target, attacker))
return true;

return false;
}
}

[Desc("Will not get automatically targeted by enemy (like walls)")]
Expand Down

0 comments on commit 7fbbaa2

Please sign in to comment.