Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow turreted units to acquire targets of opportunity while moving #16112

Merged
merged 3 commits into from Feb 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
42 changes: 34 additions & 8 deletions OpenRA.Mods.Common/Traits/Attack/AttackFollow.cs
Expand Up @@ -20,6 +20,9 @@ namespace OpenRA.Mods.Common.Traits
[Desc("Actor will follow units until in range to attack them.")]
public class AttackFollowInfo : AttackBaseInfo
{
[Desc("Automatically acquire and fire on targets of opportunity when not actively attacking.")]
public readonly bool OpportunityFire = true;

public override object Create(ActorInitializer init) { return new AttackFollow(init.Self, this); }
}

Expand All @@ -28,14 +31,18 @@ public class AttackFollow : AttackBase, INotifyOwnerChanged
protected Target requestedTarget;
protected bool requestedForceAttack;
protected int requestedTargetLastTick;
protected Target opportunityTarget;
protected bool opportunityForceAttack;
Mobile mobile;
AutoTarget autoTarget;

public AttackFollow(Actor self, AttackFollowInfo info)
: base(self, info) { }

protected override void Created(Actor self)
{
mobile = self.TraitOrDefault<Mobile>();
autoTarget = self.TraitOrDefault<AutoTarget>();
base.Created(self);
}

Expand All @@ -59,7 +66,7 @@ protected bool CanAimAtTarget(Actor self, Target target, bool forceAttack)
protected override void Tick(Actor self)
{
if (IsTraitDisabled)
requestedTarget = Target.Invalid;
requestedTarget = opportunityTarget = Target.Invalid;

if (requestedTargetLastTick != self.World.WorldTick)
{
Expand All @@ -80,8 +87,26 @@ protected override void Tick(Actor self)
DoAttack(self, requestedTarget);
}
else
{
IsAiming = false;

if (opportunityTarget.Type != TargetType.Invalid)
IsAiming = CanAimAtTarget(self, opportunityTarget, opportunityForceAttack);

if (!IsAiming && ((AttackFollowInfo)Info).OpportunityFire && autoTarget != null &&
!autoTarget.IsTraitDisabled && autoTarget.Stance >= UnitStance.Defend)
{
opportunityTarget = autoTarget.ScanForTarget(self, false);
opportunityForceAttack = false;

if (opportunityTarget.Type != TargetType.Invalid)
IsAiming = CanAimAtTarget(self, opportunityTarget, opportunityForceAttack);
}

if (IsAiming)
DoAttack(self, opportunityTarget);
}

base.Tick(self);
}

Expand All @@ -105,18 +130,13 @@ public override void OnQueueAttackActivity(Actor self, Target target, bool queue

public override void OnStopOrder(Actor self)
{
requestedTarget = Target.Invalid;
requestedTarget = opportunityTarget = Target.Invalid;
base.OnStopOrder(self);
}

public bool HasReachableTarget(bool allowMove)
{
return IsReachableTarget(requestedTarget, allowMove);
}

void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
{
requestedTarget = Target.Invalid;
requestedTarget = opportunityTarget = Target.Invalid;
}

class AttackActivity : Activity
Expand Down Expand Up @@ -157,7 +177,13 @@ public AttackActivity(Actor self, Target target, bool allowMove, bool forceAttac
public override Activity Tick(Actor self)
{
if (IsCanceled)
{
// Cancel the requested target, but keep firing on it while in range
attack.opportunityTarget = attack.requestedTarget;
attack.opportunityForceAttack = attack.requestedForceAttack;
attack.requestedTarget = Target.Invalid;
return NextActivity;
}

// Check that AttackFollow hasn't cancelled the target by modifying attack.Target
// Having both this and AttackFollow modify that field is a horrible hack.
Expand Down
24 changes: 4 additions & 20 deletions OpenRA.Mods.Common/Traits/AutoTarget.cs
Expand Up @@ -112,7 +112,6 @@ IEnumerable<EditorActorOption> IEditorActorOptions.ActorOptions(ActorInfo ai, Wo
public class AutoTarget : ConditionalTrait<AutoTargetInfo>, INotifyIdle, INotifyDamage, ITick, IResolveOrder, ISync, INotifyCreated
{
readonly IEnumerable<AttackBase> activeAttackBases;
readonly AttackFollow[] attackFollows;
[Sync] int nextScanTime = 0;

public UnitStance Stance { get { return stance; } }
Expand Down Expand Up @@ -161,7 +160,6 @@ public AutoTarget(ActorInitializer init, AutoTargetInfo info)
stance = self.Owner.IsBot || !self.Owner.Playable ? info.InitialStanceAI : info.InitialStance;

PredictedStance = stance;
attackFollows = self.TraitsImplementing<AttackFollow>().ToArray();
}

void INotifyCreated.Created(Actor self)
Expand Down Expand Up @@ -215,31 +213,17 @@ void INotifyDamage.Damaged(Actor self, AttackInfo e)

Aggressor = attacker;

bool allowMove;
if (ShouldAttack(out allowMove))
Attack(self, Target.FromActor(Aggressor), allowMove);
var allowMove = Info.AllowMovement && Stance > UnitStance.Defend;
Attack(self, Target.FromActor(Aggressor), allowMove);
}

void INotifyIdle.TickIdle(Actor self)
{
if (IsTraitDisabled || Stance < UnitStance.Defend)
return;

bool allowMove;
if (ShouldAttack(out allowMove))
ScanAndAttack(self, allowMove);
}

bool ShouldAttack(out bool allowMove)
{
allowMove = Info.AllowMovement && Stance > UnitStance.Defend;

// PERF: Avoid LINQ.
foreach (var attackFollow in attackFollows)
if (!attackFollow.IsTraitDisabled && attackFollow.HasReachableTarget(allowMove))
return false;

return true;
var allowMove = Info.AllowMovement && Stance > UnitStance.Defend;
ScanAndAttack(self, allowMove);
}

void ITick.Tick(Actor self)
Expand Down
1 change: 1 addition & 0 deletions mods/ts/rules/gdi-vehicles.yaml
Expand Up @@ -288,6 +288,7 @@ SONIC:
AttackTurreted:
Voice: Attack
PauseOnCondition: empdisable
OpportunityFire: False
Turreted:
TurnSpeed: 5
Offset: -170,0,0
Expand Down