diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
index 650ab2e15a..dc05d30577 100644
--- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
+++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
@@ -815,6 +815,7 @@
+
diff --git a/OpenRA.Mods.Common/Traits/AutoTarget.cs b/OpenRA.Mods.Common/Traits/AutoTarget.cs
index 7cd244d94a..e4e4b2d7b4 100644
--- a/OpenRA.Mods.Common/Traits/AutoTarget.cs
+++ b/OpenRA.Mods.Common/Traits/AutoTarget.cs
@@ -102,6 +102,7 @@ namespace OpenRA.Mods.Common.Traits
UnitStance stance;
ConditionManager conditionManager;
+ AutoTargetPriority[] targetPriorities;
int conditionToken = ConditionManager.InvalidConditionToken;
public void SetStance(Actor self, UnitStance value)
@@ -144,6 +145,7 @@ namespace OpenRA.Mods.Common.Traits
void INotifyCreated.Created(Actor self)
{
conditionManager = self.TraitOrDefault();
+ targetPriorities = self.TraitsImplementing().ToArray();
ApplyStanceCondition(self);
}
@@ -265,10 +267,21 @@ namespace OpenRA.Mods.Common.Traits
ab.AttackTarget(target, false, allowMove);
}
- Actor ChooseTarget(Actor self, AttackBase ab, Stance attackStances, WDist range, bool allowMove)
+ Actor ChooseTarget(Actor self, AttackBase ab, Stance attackStances, WDist scanRange, bool allowMove)
{
- var actorsByArmament = new Dictionary>();
- var actorsInRange = self.World.FindActorsInCircle(self.CenterPosition, range);
+ Actor chosenTarget = null;
+ var chosenTargetPriority = int.MinValue;
+ int chosenTargetRange = 0;
+
+ var activePriorities = targetPriorities.Where(Exts.IsTraitEnabled)
+ .Select(at => at.Info)
+ .OrderByDescending(ati => ati.Priority)
+ .ToList();
+
+ if (!activePriorities.Any())
+ return null;
+
+ var actorsInRange = self.World.FindActorsInCircle(self.CenterPosition, scanRange);
foreach (var actor in actorsInRange)
{
// PERF: Most units can only attack enemy units. If this is the case but the target is not an enemy, we
@@ -278,43 +291,53 @@ namespace OpenRA.Mods.Common.Traits
if (attackStances == OpenRA.Traits.Stance.Enemy && !actor.AppearsHostileTo(self))
continue;
- if (PreventsAutoTarget(self, actor) || !self.Owner.CanTargetActor(actor))
+ // Check whether we can auto-target this actor
+ var targetTypes = actor.TraitsImplementing()
+ .Where(Exts.IsTraitEnabled).SelectMany(t => t.TargetTypes)
+ .ToHashSet();
+
+ var target = Target.FromActor(actor);
+ var validPriorities = activePriorities.Where(ati =>
+ {
+ // Already have a higher priority target
+ if (ati.Priority < chosenTargetPriority)
+ return false;
+
+ // Incompatible target types
+ if (!targetTypes.Overlaps(ati.ValidTargets) || targetTypes.Overlaps(ati.InvalidTargets))
+ return false;
+
+ return true;
+ }).ToList();
+
+ if (!validPriorities.Any() || 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);
+ // Make sure that we can actually fire on the actor
var armaments = ab.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)
+ if (!armaments.Any())
continue;
- List actors;
- if (actorsByArmament.TryGetValue(armament, out actors))
- actors.Add(actor);
- else
- actorsByArmament.Add(armament, new List { actor });
+ // Evaluate whether we want to target this actor
+ var targetRange = (target.CenterPosition - self.CenterPosition).Length;
+ foreach (var ati in validPriorities)
+ {
+ if (chosenTarget == null || chosenTargetPriority < ati.Priority
+ || (chosenTargetPriority == ati.Priority && targetRange < chosenTargetRange))
+ {
+ chosenTarget = actor;
+ chosenTargetPriority = ati.Priority;
+ chosenTargetRange = targetRange;
+ }
+ }
}
- // 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)
- foreach (var arm in ab.Armaments)
- {
- List actors;
- if (actorsByArmament.TryGetValue(arm, out actors))
- return actors.ClosestTo(self);
- }
-
- return null;
+ return chosenTarget;
}
bool PreventsAutoTarget(Actor attacker, Actor target)
@@ -327,23 +350,6 @@ namespace OpenRA.Mods.Common.Traits
}
}
- [Desc("Will not get automatically targeted by enemy (like walls)")]
- class AutoTargetIgnoreInfo : ConditionalTraitInfo
- {
- public override object Create(ActorInitializer init) { return new AutoTargetIgnore(this); }
- }
-
- class AutoTargetIgnore : ConditionalTrait, IPreventsAutoTarget
- {
- public AutoTargetIgnore(AutoTargetIgnoreInfo info)
- : base(info) { }
-
- public bool PreventsAutoTarget(Actor self, Actor attacker)
- {
- return !IsTraitDisabled;
- }
- }
-
public class StanceInit : IActorInit
{
[FieldFromYamlKey] readonly UnitStance value = UnitStance.AttackAnything;
diff --git a/OpenRA.Mods.Common/Traits/AutoTargetPriority.cs b/OpenRA.Mods.Common/Traits/AutoTargetPriority.cs
new file mode 100644
index 0000000000..0f4c7e7c21
--- /dev/null
+++ b/OpenRA.Mods.Common/Traits/AutoTargetPriority.cs
@@ -0,0 +1,37 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2017 The OpenRA Developers (see AUTHORS)
+ * This file is part of OpenRA, which is free software. It is made
+ * available to you under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version. For more
+ * information, see COPYING.
+ */
+#endregion
+
+using System.Collections.Generic;
+using OpenRA.Traits;
+
+namespace OpenRA.Mods.Common.Traits
+{
+ [Desc("Specifies the target types and relative priority used by AutoTarget to decide what to target.")]
+ public class AutoTargetPriorityInfo : ConditionalTraitInfo, Requires
+ {
+ [Desc("Target types that can be AutoTargeted.")]
+ public readonly HashSet ValidTargets = new HashSet { "Ground", "Water", "Air" };
+
+ [Desc("Target types that can't be AutoTargeted.", "Overrules ValidTargets.")]
+ public readonly HashSet InvalidTargets = new HashSet();
+
+ [Desc("ValidTargets with larger priorities will be AutoTargeted before lower priorities.")]
+ public readonly int Priority = 1;
+
+ public override object Create(ActorInitializer init) { return new AutoTargetPriority(this); }
+ }
+
+ public class AutoTargetPriority : ConditionalTrait
+ {
+ public AutoTargetPriority(AutoTargetPriorityInfo info)
+ : base(info) { }
+ }
+}
diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs
index 12681dd863..4d9b546d84 100644
--- a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs
+++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs
@@ -710,6 +710,18 @@ namespace OpenRA.Mods.Common.UtilityCommands
}
}
+ // AutoTargetIgnore replaced with AutoTargetPriority and target types
+ if (engineVersion < 20170610)
+ {
+ if (node.Key.StartsWith("AutoTarget", StringComparison.Ordinal) || node.Key.StartsWith("-AutoTarget", StringComparison.Ordinal))
+ {
+ Console.WriteLine("The AutoTarget traits have been reworked to use target types:");
+ Console.WriteLine(" * Actors with AutoTarget must specify one or more AutoTargetPriority traits.");
+ Console.WriteLine(" * The AutoTargetIgnore trait has been removed.");
+ Console.WriteLine(" Append NoAutoTarget to the target types instead.");
+ }
+ }
+
UpgradeActorRules(modData, engineVersion, ref node.Value.Nodes, node, depth + 1);
}