Merge pull request #9734 from RoosterDragon/auto-target-perf
Improve AutoTarget performance
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)")]
|
||||
|
||||
@@ -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>();
|
||||
|
||||
Reference in New Issue
Block a user