Merge pull request #9734 from RoosterDragon/auto-target-perf

Improve AutoTarget performance
This commit is contained in:
Oliver Brakmann
2015-12-20 19:53:17 +01:00
18 changed files with 174 additions and 89 deletions

View File

@@ -189,7 +189,12 @@ namespace OpenRA.Mods.Common.Traits
if (Info.AttackRequiresEnteringCell && !positionable.Value.CanEnterCell(t.Actor.Location, null, false))
return false;
return Armaments.Any(a => a.Weapon.IsValidAgainst(t, self.World, self));
// PERF: Avoid LINQ.
foreach (var armament in Armaments)
if (armament.Weapon.IsValidAgainst(t, self.World, self))
return true;
return false;
}
public WDist GetMinimumRange()
@@ -197,9 +202,17 @@ namespace OpenRA.Mods.Common.Traits
if (IsTraitDisabled)
return WDist.Zero;
var min = Armaments.Where(a => !a.IsTraitDisabled)
.Select(a => a.Weapon.MinRange)
.Append(WDist.MaxValue).Min();
// PERF: Avoid LINQ.
var min = WDist.MaxValue;
foreach (var armament in Armaments)
{
if (armament.IsTraitDisabled)
continue;
var range = armament.Weapon.MinRange;
if (min > range)
min = range;
}
return min != WDist.MaxValue ? min : WDist.Zero;
}
@@ -208,23 +221,32 @@ namespace OpenRA.Mods.Common.Traits
if (IsTraitDisabled)
return WDist.Zero;
return Armaments.Where(a => !a.IsTraitDisabled)
.Select(a => a.MaxRange())
.Append(WDist.Zero).Max();
// PERF: Avoid LINQ.
var max = WDist.Zero;
foreach (var armament in Armaments)
{
if (armament.IsTraitDisabled)
continue;
var range = armament.MaxRange();
if (max < range)
max = range;
}
return max;
}
// Enumerates all armaments, that this actor possesses, that can be used against Target t
public IEnumerable<Armament> ChooseArmamentsForTarget(Target t, bool forceAttack, bool onlyEnabled = true)
public IEnumerable<Armament> ChooseArmamentsForTarget(Target t, bool forceAttack)
{
// If force-fire is not used, and the target requires force-firing or the target is
// terrain or invalid, no armaments can be used
if (!forceAttack && (t.RequiresForceFire || t.Type == TargetType.Terrain || t.Type == TargetType.Invalid))
if (!forceAttack && (t.Type == TargetType.Terrain || t.Type == TargetType.Invalid || t.RequiresForceFire))
return Enumerable.Empty<Armament>();
// Get target's owner; in case of terrain or invalid target there will be no problems
// with owner == null since forceFire will have to be true in this part of the method
// (short-circuiting in the logical expression below)
var owner = null as Player;
Player owner = null;
if (t.Type == TargetType.FrozenActor)
{
owner = t.FrozenActor.Owner;
@@ -241,10 +263,11 @@ namespace OpenRA.Mods.Common.Traits
owner = t.Actor.Owner;
}
return Armaments.Where(a => (!a.IsTraitDisabled || !onlyEnabled)
&& a.Weapon.IsValidAgainst(t, self.World, self)
return Armaments.Where(a =>
!a.IsTraitDisabled
&& (owner == null || (forceAttack ? a.Info.ForceTargetStances : a.Info.TargetStances)
.HasStance(self.Owner.Stances[owner])));
.HasStance(self.Owner.Stances[owner]))
&& a.Weapon.IsValidAgainst(t, self.World, self));
}
public void AttackTarget(Target target, bool queued, bool allowMove, bool forceAttack = false)

View File

@@ -135,24 +135,21 @@ namespace OpenRA.Mods.Common.Traits
--nextScanTime;
}
public Actor ScanForTarget(Actor self, Actor currentTarget, bool allowMove)
public Actor ScanForTarget(Actor self, bool allowMove)
{
if (nextScanTime <= 0)
{
nextScanTime = self.World.SharedRandom.Next(info.MinimumScanTimeInterval, info.MaximumScanTimeInterval);
var range = info.ScanRadius > 0 ? WDist.FromCells(info.ScanRadius) : attack.GetMaximumRange();
if (self.IsIdle || currentTarget == null || !Target.FromActor(currentTarget).IsInRange(self.CenterPosition, range))
{
nextScanTime = self.World.SharedRandom.Next(info.MinimumScanTimeInterval, info.MaximumScanTimeInterval);
return ChooseTarget(self, range, allowMove);
}
return ChooseTarget(self, range, allowMove);
}
return currentTarget;
return null;
}
public void ScanAndAttack(Actor self, bool allowMove)
{
var targetActor = ScanForTarget(self, null, allowMove);
var targetActor = ScanForTarget(self, allowMove);
if (targetActor != null)
Attack(self, targetActor, allowMove);
}
@@ -167,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)")]

View File

@@ -58,8 +58,7 @@ namespace OpenRA.Mods.Common.Traits
if (info.ValidFactions.Any() && !info.ValidFactions.Contains(collector.Owner.Faction.InternalName))
return false;
var targetable = collector.TraitsImplementing<ITargetable>().Where(Exts.IsTraitEnabled);
if (!info.ValidTargets.Overlaps(targetable.SelectMany(t => t.TargetTypes)))
if (!info.ValidTargets.Overlaps(collector.GetEnabledTargetTypes()))
return false;
var positionable = collector.TraitOrDefault<IPositionable>();