Add AutoTargetPriority trait for smarter AutoTarget logic.

This commit is contained in:
Paul Chote
2017-06-10 10:05:31 +00:00
committed by atlimit8
parent ab8bc53ed8
commit 716343732f
4 changed files with 101 additions and 45 deletions

View File

@@ -815,6 +815,7 @@
<Compile Include="Widgets\Logic\Ingame\CommandBarLogic.cs" />
<Compile Include="Widgets\Logic\Ingame\StanceSelectorLogic.cs" />
<Compile Include="Widgets\Logic\ButtonTooltipWithDescHighlightLogic.cs" />
<Compile Include="Traits\AutoTargetPriority.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="AfterBuild">

View File

@@ -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<ConditionManager>();
targetPriorities = self.TraitsImplementing<AutoTargetPriority>().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<Armament, List<Actor>>();
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<ITargetable>()
.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<Actor> actors;
if (actorsByArmament.TryGetValue(armament, out actors))
actors.Add(actor);
else
actorsByArmament.Add(armament, new List<Actor> { 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<Actor> 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<AutoTargetIgnoreInfo>, IPreventsAutoTarget
{
public AutoTargetIgnore(AutoTargetIgnoreInfo info)
: base(info) { }
public bool PreventsAutoTarget(Actor self, Actor attacker)
{
return !IsTraitDisabled;
}
}
public class StanceInit : IActorInit<UnitStance>
{
[FieldFromYamlKey] readonly UnitStance value = UnitStance.AttackAnything;

View File

@@ -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<AutoTargetInfo>
{
[Desc("Target types that can be AutoTargeted.")]
public readonly HashSet<string> ValidTargets = new HashSet<string> { "Ground", "Water", "Air" };
[Desc("Target types that can't be AutoTargeted.", "Overrules ValidTargets.")]
public readonly HashSet<string> InvalidTargets = new HashSet<string>();
[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<AutoTargetPriorityInfo>
{
public AutoTargetPriority(AutoTargetPriorityInfo info)
: base(info) { }
}
}

View File

@@ -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);
}