Merge pull request #9734 from RoosterDragon/auto-target-perf
Improve AutoTarget performance
This commit is contained in:
@@ -44,6 +44,7 @@ namespace OpenRA
|
|||||||
public Rectangle VisualBounds { get; private set; }
|
public Rectangle VisualBounds { get; private set; }
|
||||||
public IEffectiveOwner EffectiveOwner { get; private set; }
|
public IEffectiveOwner EffectiveOwner { get; private set; }
|
||||||
public IOccupySpace OccupiesSpace { get; private set; }
|
public IOccupySpace OccupiesSpace { get; private set; }
|
||||||
|
public ITargetable[] Targetables { get; private set; }
|
||||||
|
|
||||||
public bool IsIdle { get { return currentActivity == null; } }
|
public bool IsIdle { get { return currentActivity == null; } }
|
||||||
public bool IsDead { get { return Disposed || (health != null && health.IsDead); } }
|
public bool IsDead { get { return Disposed || (health != null && health.IsDead); } }
|
||||||
@@ -110,6 +111,7 @@ namespace OpenRA
|
|||||||
disables = TraitsImplementing<IDisable>().ToArray();
|
disables = TraitsImplementing<IDisable>().ToArray();
|
||||||
visibilityModifiers = TraitsImplementing<IVisibilityModifier>().ToArray();
|
visibilityModifiers = TraitsImplementing<IVisibilityModifier>().ToArray();
|
||||||
defaultVisibility = Trait<IDefaultVisibility>();
|
defaultVisibility = Trait<IDefaultVisibility>();
|
||||||
|
Targetables = TraitsImplementing<ITargetable>().ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle DetermineBounds()
|
Rectangle DetermineBounds()
|
||||||
@@ -334,6 +336,33 @@ namespace OpenRA
|
|||||||
return defaultVisibility.IsVisible(this, player);
|
return defaultVisibility.IsVisible(this, player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> GetAllTargetTypes()
|
||||||
|
{
|
||||||
|
// PERF: Avoid LINQ.
|
||||||
|
foreach (var targetable in Targetables)
|
||||||
|
foreach (var targetType in targetable.TargetTypes)
|
||||||
|
yield return targetType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> GetEnabledTargetTypes()
|
||||||
|
{
|
||||||
|
// PERF: Avoid LINQ.
|
||||||
|
foreach (var targetable in Targetables)
|
||||||
|
if (targetable.IsTraitEnabled())
|
||||||
|
foreach (var targetType in targetable.TargetTypes)
|
||||||
|
yield return targetType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsTargetableBy(Actor byActor)
|
||||||
|
{
|
||||||
|
// PERF: Avoid LINQ.
|
||||||
|
foreach (var targetable in Targetables)
|
||||||
|
if (targetable.IsTraitEnabled() && targetable.TargetableBy(this, byActor))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
#region Scripting interface
|
#region Scripting interface
|
||||||
|
|
||||||
Lazy<ScriptActorInterface> luaInterface;
|
Lazy<ScriptActorInterface> luaInterface;
|
||||||
|
|||||||
@@ -127,14 +127,17 @@ namespace OpenRA.GameRules
|
|||||||
/// <summary>Checks if the weapon is valid against (can target) the actor.</summary>
|
/// <summary>Checks if the weapon is valid against (can target) the actor.</summary>
|
||||||
public bool IsValidAgainst(Actor victim, Actor firedBy)
|
public bool IsValidAgainst(Actor victim, Actor firedBy)
|
||||||
{
|
{
|
||||||
var targetable = victim.TraitsImplementing<ITargetable>().Where(Exts.IsTraitEnabled);
|
var targetTypes = victim.GetEnabledTargetTypes();
|
||||||
if (!IsValidTarget(targetable.SelectMany(t => t.TargetTypes)))
|
|
||||||
|
if (!IsValidTarget(targetTypes))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!Warheads.Any(w => w.IsValidAgainst(victim, firedBy)))
|
// PERF: Avoid LINQ.
|
||||||
return false;
|
foreach (var warhead in Warheads)
|
||||||
|
if (warhead.IsValidAgainst(victim, firedBy))
|
||||||
|
return true;
|
||||||
|
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Checks if the weapon is valid against (can target) the frozen actor.</summary>
|
/// <summary>Checks if the weapon is valid against (can target) the frozen actor.</summary>
|
||||||
|
|||||||
@@ -159,13 +159,27 @@ namespace OpenRA
|
|||||||
|
|
||||||
public bool CanTargetActor(Actor a)
|
public bool CanTargetActor(Actor a)
|
||||||
{
|
{
|
||||||
if (HasFogVisibility && fogVisibilities.Any(f => f.IsVisible(a)))
|
// PERF: Avoid LINQ.
|
||||||
return true;
|
if (HasFogVisibility)
|
||||||
|
foreach (var fogVisibility in fogVisibilities)
|
||||||
|
if (fogVisibility.IsVisible(a))
|
||||||
|
return true;
|
||||||
|
|
||||||
return CanViewActor(a);
|
return CanViewActor(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasFogVisibility { get { return fogVisibilities.Any(f => f.HasFogVisibility()); } }
|
public bool HasFogVisibility
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// PERF: Avoid LINQ.
|
||||||
|
foreach (var fogVisibility in fogVisibilities)
|
||||||
|
if (fogVisibility.HasFogVisibility())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#region Scripting interface
|
#region Scripting interface
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ namespace OpenRA.Traits
|
|||||||
|
|
||||||
CenterPosition = self.CenterPosition;
|
CenterPosition = self.CenterPosition;
|
||||||
Bounds = self.Bounds;
|
Bounds = self.Bounds;
|
||||||
TargetTypes = self.TraitsImplementing<ITargetable>().Where(Exts.IsTraitEnabled).SelectMany(t => t.TargetTypes).ToHashSet();
|
TargetTypes = self.GetEnabledTargetTypes().ToHashSet();
|
||||||
|
|
||||||
UpdateVisibility();
|
UpdateVisibility();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ namespace OpenRA.Traits
|
|||||||
|
|
||||||
TargetType type;
|
TargetType type;
|
||||||
Actor actor;
|
Actor actor;
|
||||||
IEnumerable<ITargetable> targetable;
|
|
||||||
FrozenActor frozen;
|
FrozenActor frozen;
|
||||||
WPos pos;
|
WPos pos;
|
||||||
int generation;
|
int generation;
|
||||||
@@ -48,7 +47,6 @@ namespace OpenRA.Traits
|
|||||||
return new Target
|
return new Target
|
||||||
{
|
{
|
||||||
actor = a,
|
actor = a,
|
||||||
targetable = a.TraitsImplementing<ITargetable>(),
|
|
||||||
type = TargetType.Actor,
|
type = TargetType.Actor,
|
||||||
generation = a.Generation,
|
generation = a.Generation,
|
||||||
};
|
};
|
||||||
@@ -83,8 +81,7 @@ namespace OpenRA.Traits
|
|||||||
if (targeter == null || Type == TargetType.Invalid)
|
if (targeter == null || Type == TargetType.Invalid)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var targeted = this.actor;
|
if (actor != null && !actor.IsTargetableBy(targeter))
|
||||||
if (targeted != null && !targetable.Any(t => t.IsTraitEnabled() && t.TargetableBy(targeted, targeter)))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -94,7 +91,25 @@ namespace OpenRA.Traits
|
|||||||
// TODO: either replace based on target type or put in singleton trait
|
// TODO: either replace based on target type or put in singleton trait
|
||||||
public bool RequiresForceFire
|
public bool RequiresForceFire
|
||||||
{
|
{
|
||||||
get { return targetable != null && targetable.Any(Exts.IsTraitEnabled) && targetable.Where(Exts.IsTraitEnabled).All(t => t.RequiresForceFire); }
|
get
|
||||||
|
{
|
||||||
|
if (actor == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// PERF: Avoid LINQ.
|
||||||
|
var isTargetable = false;
|
||||||
|
foreach (var targetable in actor.Targetables)
|
||||||
|
{
|
||||||
|
if (!targetable.IsTraitEnabled())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
isTargetable = true;
|
||||||
|
if (!targetable.RequiresForceFire)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isTargetable;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Representative position - see Positions for the full set of targetable positions.
|
// Representative position - see Positions for the full set of targetable positions.
|
||||||
@@ -126,8 +141,7 @@ namespace OpenRA.Traits
|
|||||||
switch (Type)
|
switch (Type)
|
||||||
{
|
{
|
||||||
case TargetType.Actor:
|
case TargetType.Actor:
|
||||||
var targetable = actor.TraitsImplementing<ITargetable>().Where(Exts.IsTraitEnabled);
|
if (!actor.Targetables.Any(Exts.IsTraitEnabled))
|
||||||
if (!targetable.Any())
|
|
||||||
return new[] { actor.CenterPosition };
|
return new[] { actor.CenterPosition };
|
||||||
|
|
||||||
var targetablePositions = actor.TraitOrDefault<ITargetablePositions>();
|
var targetablePositions = actor.TraitOrDefault<ITargetablePositions>();
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ namespace OpenRA.Mods.Common.AI
|
|||||||
if (!a.Info.HasTraitInfo<AttackBaseInfo>())
|
if (!a.Info.HasTraitInfo<AttackBaseInfo>())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var targetTypes = target.TraitsImplementing<ITargetable>().Where(Exts.IsTraitEnabled).SelectMany(t => t.TargetTypes);
|
var targetTypes = target.GetEnabledTargetTypes();
|
||||||
if (!targetTypes.Any())
|
if (!targetTypes.Any())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|||||||
@@ -125,11 +125,10 @@ namespace OpenRA.Mods.Common.AI
|
|||||||
if (a == null)
|
if (a == null)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
var targetable = a.TraitsImplementing<ITargetable>().Where(Exts.IsTraitEnabled);
|
if (!a.IsTargetableBy(firedBy.PlayerActor))
|
||||||
if (!targetable.Any(t => t.TargetableBy(a, firedBy.PlayerActor)))
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (Types.Overlaps(targetable.SelectMany(t => t.TargetTypes)))
|
if (Types.Overlaps(a.GetEnabledTargetTypes()))
|
||||||
{
|
{
|
||||||
switch (TargetMetric)
|
switch (TargetMetric)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -68,8 +68,8 @@ namespace OpenRA.Mods.Common.Activities
|
|||||||
return NextActivity;
|
return NextActivity;
|
||||||
|
|
||||||
// Drop the target once none of the weapons are effective against it
|
// Drop the target once none of the weapons are effective against it
|
||||||
var armaments = attack.ChooseArmamentsForTarget(Target, forceAttack);
|
var armaments = attack.ChooseArmamentsForTarget(Target, forceAttack).ToList();
|
||||||
if (!armaments.Any())
|
if (armaments.Count == 0)
|
||||||
return NextActivity;
|
return NextActivity;
|
||||||
|
|
||||||
// Update ranges
|
// Update ranges
|
||||||
|
|||||||
@@ -25,12 +25,7 @@ namespace OpenRA.Mods.Common.Activities
|
|||||||
var attack = self.Trait<AttackBase>();
|
var attack = self.Trait<AttackBase>();
|
||||||
targets = self.World.ActorsHavingTrait<Huntable>().Where(
|
targets = self.World.ActorsHavingTrait<Huntable>().Where(
|
||||||
a => self != a && !a.IsDead && a.IsInWorld && a.AppearsHostileTo(self)
|
a => self != a && !a.IsDead && a.IsInWorld && a.AppearsHostileTo(self)
|
||||||
&& IsTargetable(a, self) && attack.HasAnyValidWeapons(Target.FromActor(a)));
|
&& a.IsTargetableBy(self) && attack.HasAnyValidWeapons(Target.FromActor(a)));
|
||||||
}
|
|
||||||
|
|
||||||
bool IsTargetable(Actor self, Actor viewer)
|
|
||||||
{
|
|
||||||
return self.TraitsImplementing<ITargetable>().Any(t => t.IsTraitEnabled() && t.TargetableBy(self, viewer));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Activity Tick(Actor self)
|
public override Activity Tick(Actor self)
|
||||||
|
|||||||
@@ -67,9 +67,9 @@ namespace OpenRA.Mods.Common.Orders
|
|||||||
|
|
||||||
public class TargetTypeOrderTargeter : UnitOrderTargeter
|
public class TargetTypeOrderTargeter : UnitOrderTargeter
|
||||||
{
|
{
|
||||||
readonly string[] targetTypes;
|
readonly HashSet<string> targetTypes;
|
||||||
|
|
||||||
public TargetTypeOrderTargeter(string[] targetTypes, string order, int priority, string cursor, bool targetEnemyUnits, bool targetAllyUnits)
|
public TargetTypeOrderTargeter(HashSet<string> targetTypes, string order, int priority, string cursor, bool targetEnemyUnits, bool targetAllyUnits)
|
||||||
: base(order, priority, cursor, targetEnemyUnits, targetAllyUnits)
|
: base(order, priority, cursor, targetEnemyUnits, targetAllyUnits)
|
||||||
{
|
{
|
||||||
this.targetTypes = targetTypes;
|
this.targetTypes = targetTypes;
|
||||||
@@ -77,7 +77,7 @@ namespace OpenRA.Mods.Common.Orders
|
|||||||
|
|
||||||
public override bool CanTargetActor(Actor self, Actor target, TargetModifiers modifiers, ref string cursor)
|
public override bool CanTargetActor(Actor self, Actor target, TargetModifiers modifiers, ref string cursor)
|
||||||
{
|
{
|
||||||
return target.TraitsImplementing<ITargetable>().Any(t => t.IsTraitEnabled() && t.TargetTypes.Overlaps(targetTypes));
|
return targetTypes.Overlaps(target.GetEnabledTargetTypes());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanTargetFrozenActor(Actor self, FrozenActor target, TargetModifiers modifiers, ref string cursor)
|
public override bool CanTargetFrozenActor(Actor self, FrozenActor target, TargetModifiers modifiers, ref string cursor)
|
||||||
|
|||||||
@@ -189,7 +189,12 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
if (Info.AttackRequiresEnteringCell && !positionable.Value.CanEnterCell(t.Actor.Location, null, false))
|
if (Info.AttackRequiresEnteringCell && !positionable.Value.CanEnterCell(t.Actor.Location, null, false))
|
||||||
return 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()
|
public WDist GetMinimumRange()
|
||||||
@@ -197,9 +202,17 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
if (IsTraitDisabled)
|
if (IsTraitDisabled)
|
||||||
return WDist.Zero;
|
return WDist.Zero;
|
||||||
|
|
||||||
var min = Armaments.Where(a => !a.IsTraitDisabled)
|
// PERF: Avoid LINQ.
|
||||||
.Select(a => a.Weapon.MinRange)
|
var min = WDist.MaxValue;
|
||||||
.Append(WDist.MaxValue).Min();
|
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;
|
return min != WDist.MaxValue ? min : WDist.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,23 +221,32 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
if (IsTraitDisabled)
|
if (IsTraitDisabled)
|
||||||
return WDist.Zero;
|
return WDist.Zero;
|
||||||
|
|
||||||
return Armaments.Where(a => !a.IsTraitDisabled)
|
// PERF: Avoid LINQ.
|
||||||
.Select(a => a.MaxRange())
|
var max = WDist.Zero;
|
||||||
.Append(WDist.Zero).Max();
|
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
|
// 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
|
// If force-fire is not used, and the target requires force-firing or the target is
|
||||||
// terrain or invalid, no armaments can be used
|
// 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>();
|
return Enumerable.Empty<Armament>();
|
||||||
|
|
||||||
// Get target's owner; in case of terrain or invalid target there will be no problems
|
// 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
|
// with owner == null since forceFire will have to be true in this part of the method
|
||||||
// (short-circuiting in the logical expression below)
|
// (short-circuiting in the logical expression below)
|
||||||
var owner = null as Player;
|
Player owner = null;
|
||||||
if (t.Type == TargetType.FrozenActor)
|
if (t.Type == TargetType.FrozenActor)
|
||||||
{
|
{
|
||||||
owner = t.FrozenActor.Owner;
|
owner = t.FrozenActor.Owner;
|
||||||
@@ -241,10 +263,11 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
owner = t.Actor.Owner;
|
owner = t.Actor.Owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Armaments.Where(a => (!a.IsTraitDisabled || !onlyEnabled)
|
return Armaments.Where(a =>
|
||||||
&& a.Weapon.IsValidAgainst(t, self.World, self)
|
!a.IsTraitDisabled
|
||||||
&& (owner == null || (forceAttack ? a.Info.ForceTargetStances : a.Info.TargetStances)
|
&& (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)
|
public void AttackTarget(Target target, bool queued, bool allowMove, bool forceAttack = false)
|
||||||
|
|||||||
@@ -135,24 +135,21 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
--nextScanTime;
|
--nextScanTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Actor ScanForTarget(Actor self, Actor currentTarget, bool allowMove)
|
public Actor ScanForTarget(Actor self, bool allowMove)
|
||||||
{
|
{
|
||||||
if (nextScanTime <= 0)
|
if (nextScanTime <= 0)
|
||||||
{
|
{
|
||||||
|
nextScanTime = self.World.SharedRandom.Next(info.MinimumScanTimeInterval, info.MaximumScanTimeInterval);
|
||||||
var range = info.ScanRadius > 0 ? WDist.FromCells(info.ScanRadius) : attack.GetMaximumRange();
|
var range = info.ScanRadius > 0 ? WDist.FromCells(info.ScanRadius) : attack.GetMaximumRange();
|
||||||
if (self.IsIdle || currentTarget == null || !Target.FromActor(currentTarget).IsInRange(self.CenterPosition, range))
|
return ChooseTarget(self, range, allowMove);
|
||||||
{
|
|
||||||
nextScanTime = self.World.SharedRandom.Next(info.MinimumScanTimeInterval, info.MaximumScanTimeInterval);
|
|
||||||
return ChooseTarget(self, range, allowMove);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return currentTarget;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ScanAndAttack(Actor self, bool allowMove)
|
public void ScanAndAttack(Actor self, bool allowMove)
|
||||||
{
|
{
|
||||||
var targetActor = ScanForTarget(self, null, allowMove);
|
var targetActor = ScanForTarget(self, allowMove);
|
||||||
if (targetActor != null)
|
if (targetActor != null)
|
||||||
Attack(self, targetActor, allowMove);
|
Attack(self, targetActor, allowMove);
|
||||||
}
|
}
|
||||||
@@ -167,43 +164,57 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
|
|
||||||
Actor ChooseTarget(Actor self, WDist range, bool allowMove)
|
Actor ChooseTarget(Actor self, WDist range, bool allowMove)
|
||||||
{
|
{
|
||||||
var inRange = self.World.FindActorsInCircle(self.CenterPosition, range)
|
var actorsByArmament = new Dictionary<Armament, List<Actor>>();
|
||||||
.Where(a =>
|
var actorsInRange = self.World.FindActorsInCircle(self.CenterPosition, range);
|
||||||
!a.TraitsImplementing<IPreventsAutoTarget>().Any(t => t.PreventsAutoTarget(a, self)));
|
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
|
// Armaments are enumerated in attack.Armaments in construct order
|
||||||
// When autotargeting, first choose targets according to the used armament construct order
|
// When autotargeting, first choose targets according to the used armament construct order
|
||||||
// And then according to distance from actor
|
// And then according to distance from actor
|
||||||
// This enables preferential treatment of certain armaments
|
// This enables preferential treatment of certain armaments
|
||||||
// (e.g. tesla trooper's tesla zap should have precedence over tesla charge)
|
// (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)
|
foreach (var arm in attack.Armaments)
|
||||||
{
|
{
|
||||||
Actor actor;
|
List<Actor> actors;
|
||||||
if (actorByArmament.TryGetValue(arm, out actor))
|
if (actorsByArmament.TryGetValue(arm, out actors))
|
||||||
return actor;
|
return actors.ClosestTo(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
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)")]
|
[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))
|
if (info.ValidFactions.Any() && !info.ValidFactions.Contains(collector.Owner.Faction.InternalName))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var targetable = collector.TraitsImplementing<ITargetable>().Where(Exts.IsTraitEnabled);
|
if (!info.ValidTargets.Overlaps(collector.GetEnabledTargetTypes()))
|
||||||
if (!info.ValidTargets.Overlaps(targetable.SelectMany(t => t.TargetTypes)))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var positionable = collector.TraitOrDefault<IPositionable>();
|
var positionable = collector.TraitOrDefault<IPositionable>();
|
||||||
|
|||||||
@@ -56,8 +56,7 @@ namespace OpenRA.Mods.Common.Warheads
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
// A target type is valid if it is in the valid targets list, and not in the invalid targets list.
|
// A target type is valid if it is in the valid targets list, and not in the invalid targets list.
|
||||||
var targetable = victim.TraitsImplementing<ITargetable>().Where(Exts.IsTraitEnabled);
|
if (!IsValidTarget(victim.GetEnabledTargetTypes()))
|
||||||
if (!IsValidTarget(targetable.SelectMany(t => t.TargetTypes)))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace OpenRA.Mods.RA.Traits
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
yield return new TargetTypeOrderTargeter(new[] { "DetonateAttack" }, "DetonateAttack", 5, "attack", true, false) { ForceAttack = false };
|
yield return new TargetTypeOrderTargeter(new HashSet<string> { "DetonateAttack" }, "DetonateAttack", 5, "attack", true, false) { ForceAttack = false };
|
||||||
yield return new DeployOrderTargeter("Detonate", 5);
|
yield return new DeployOrderTargeter("Detonate", 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ namespace OpenRA.Mods.RA.Traits
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
yield return new TargetTypeOrderTargeter(new[] { "Disguise" }, "Disguise", 7, "ability", true, true) { ForceAttack = false };
|
yield return new TargetTypeOrderTargeter(new HashSet<string> { "Disguise" }, "Disguise", 7, "ability", true, true) { ForceAttack = false };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,8 +77,7 @@ namespace OpenRA.Mods.RA.Traits
|
|||||||
targetTypes = frozen.TargetTypes;
|
targetTypes = frozen.TargetTypes;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
targetTypes = order.TargetActor.TraitsImplementing<ITargetable>().Where(Exts.IsTraitEnabled)
|
targetTypes = order.TargetActor.GetEnabledTargetTypes();
|
||||||
.SelectMany(t => t.TargetTypes);
|
|
||||||
|
|
||||||
return Info.Types.Overlaps(targetTypes);
|
return Info.Types.Overlaps(targetTypes);
|
||||||
}
|
}
|
||||||
@@ -96,7 +95,7 @@ namespace OpenRA.Mods.RA.Traits
|
|||||||
|
|
||||||
var target = self.ResolveFrozenActorOrder(order, Color.Red);
|
var target = self.ResolveFrozenActorOrder(order, Color.Red);
|
||||||
if (target.Type != TargetType.Actor
|
if (target.Type != TargetType.Actor
|
||||||
|| !Info.Types.Overlaps(target.Actor.TraitsImplementing<ITargetable>().SelectMany(t => t.TargetTypes)))
|
|| !Info.Types.Overlaps(target.Actor.GetAllTargetTypes()))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!order.Queued)
|
if (!order.Queued)
|
||||||
@@ -124,7 +123,7 @@ namespace OpenRA.Mods.RA.Traits
|
|||||||
if (!info.ValidStances.HasStance(stance))
|
if (!info.ValidStances.HasStance(stance))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return target.TraitsImplementing<ITargetable>().Any(t => t.TargetTypes.Overlaps(info.Types));
|
return info.Types.Overlaps(target.GetAllTargetTypes());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool CanTargetFrozenActor(Actor self, FrozenActor target, TargetModifiers modifiers, ref string cursor)
|
public override bool CanTargetFrozenActor(Actor self, FrozenActor target, TargetModifiers modifiers, ref string cursor)
|
||||||
@@ -134,7 +133,7 @@ namespace OpenRA.Mods.RA.Traits
|
|||||||
if (!info.ValidStances.HasStance(stance))
|
if (!info.ValidStances.HasStance(stance))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return target.Info.TraitInfos<ITargetableInfo>().Any(t => info.Types.Overlaps(t.GetTargetTypes()));
|
return info.Types.Overlaps(target.Info.TraitInfos<ITargetableInfo>().SelectMany(ti => ti.GetTargetTypes()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ namespace OpenRA.Mods.RA.Traits
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
yield return new TargetTypeOrderTargeter(new[] { "DetonateAttack" }, "DetonateAttack", 5, "attack", true, false) { ForceAttack = false };
|
yield return new TargetTypeOrderTargeter(new HashSet<string> { "DetonateAttack" }, "DetonateAttack", 5, "attack", true, false) { ForceAttack = false };
|
||||||
yield return new DeployOrderTargeter("Detonate", 5);
|
yield return new DeployOrderTargeter("Detonate", 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user