Optimize AutoTarget.ChooseTarget.

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.
This commit is contained in:
RoosterDragon
2015-10-17 01:01:39 +01:00
parent 487727c9d0
commit 7fbbaa2221

View File

@@ -164,43 +164,57 @@ namespace OpenRA.Mods.Common.Traits
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)")]