From 716343732f263061b48360b5e5e227f2492ec15a Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 10 Jun 2017 10:05:31 +0000 Subject: [PATCH] Add AutoTargetPriority trait for smarter AutoTarget logic. --- OpenRA.Mods.Common/OpenRA.Mods.Common.csproj | 1 + OpenRA.Mods.Common/Traits/AutoTarget.cs | 96 ++++++++++--------- .../Traits/AutoTargetPriority.cs | 37 +++++++ .../UtilityCommands/UpgradeRules.cs | 12 +++ 4 files changed, 101 insertions(+), 45 deletions(-) create mode 100644 OpenRA.Mods.Common/Traits/AutoTargetPriority.cs 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); }