diff --git a/OpenRA.Mods.Cnc/Activities/LeapAttack.cs b/OpenRA.Mods.Cnc/Activities/LeapAttack.cs index a2f2ea7aaf..761f413acf 100644 --- a/OpenRA.Mods.Cnc/Activities/LeapAttack.cs +++ b/OpenRA.Mods.Cnc/Activities/LeapAttack.cs @@ -20,25 +20,29 @@ using OpenRA.Traits; namespace OpenRA.Mods.Cnc.Activities { - public class LeapAttack : Activity + public class LeapAttack : Activity, IActivityNotifyStanceChanged { readonly AttackLeapInfo info; readonly AttackLeap attack; readonly Mobile mobile; readonly bool allowMovement; + readonly bool forceAttack; Target target; Target lastVisibleTarget; bool useLastVisibleTarget; WDist lastVisibleMinRange; WDist lastVisibleMaxRange; + BitSet lastVisibleTargetTypes; + Player lastVisibleOwner; - public LeapAttack(Actor self, Target target, bool allowMovement, AttackLeap attack, AttackLeapInfo info) + public LeapAttack(Actor self, Target target, bool allowMovement, bool forceAttack, AttackLeap attack, AttackLeapInfo info) { this.target = target; this.info = info; this.attack = attack; this.allowMovement = allowMovement; + this.forceAttack = forceAttack; mobile = self.Trait(); // The target may become hidden between the initial order request and the first tick (e.g. if queued) @@ -49,6 +53,17 @@ namespace OpenRA.Mods.Cnc.Activities lastVisibleTarget = Target.FromPos(target.CenterPosition); lastVisibleMinRange = attack.GetMinimumRangeVersusTarget(target); lastVisibleMaxRange = attack.GetMaximumRangeVersusTarget(target); + + if (target.Type == TargetType.Actor) + { + lastVisibleOwner = target.Actor.Owner; + lastVisibleTargetTypes = target.Actor.GetEnabledTargetTypes(); + } + else if (target.Type == TargetType.FrozenActor) + { + lastVisibleOwner = target.FrozenActor.Owner; + lastVisibleTargetTypes = target.FrozenActor.TargetTypes; + } } } @@ -76,6 +91,8 @@ namespace OpenRA.Mods.Cnc.Activities lastVisibleTarget = Target.FromTargetPositions(target); lastVisibleMinRange = attack.GetMinimumRangeVersusTarget(target); lastVisibleMaxRange = attack.GetMaximumRangeVersusTarget(target); + lastVisibleOwner = target.Actor.Owner; + lastVisibleTargetTypes = target.Actor.GetEnabledTargetTypes(); } var oldUseLastVisibleTarget = useLastVisibleTarget; @@ -141,5 +158,15 @@ namespace OpenRA.Mods.Cnc.Activities { attack.IsAiming = false; } + + void IActivityNotifyStanceChanged.StanceChanged(Actor self, AutoTarget autoTarget, UnitStance oldStance, UnitStance newStance) + { + // Cancel non-forced targets when switching to a more restrictive stance if they are no longer valid for auto-targeting + if (newStance > oldStance || forceAttack) + return; + + if (!autoTarget.HasValidTargetPriority(self, lastVisibleOwner, lastVisibleTargetTypes)) + target = Target.Invalid; + } } } diff --git a/OpenRA.Mods.Cnc/Traits/Attack/AttackLeap.cs b/OpenRA.Mods.Cnc/Traits/Attack/AttackLeap.cs index 2b2896b949..d253103533 100644 --- a/OpenRA.Mods.Cnc/Traits/Attack/AttackLeap.cs +++ b/OpenRA.Mods.Cnc/Traits/Attack/AttackLeap.cs @@ -73,7 +73,7 @@ namespace OpenRA.Mods.Cnc.Traits public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack) { - return new LeapAttack(self, newTarget, allowMove, this, info); + return new LeapAttack(self, newTarget, allowMove, forceAttack, this, info); } } } diff --git a/OpenRA.Mods.Cnc/Traits/Attack/AttackTesla.cs b/OpenRA.Mods.Cnc/Traits/Attack/AttackTesla.cs index d269c79ab6..cebdf0d6ac 100644 --- a/OpenRA.Mods.Cnc/Traits/Attack/AttackTesla.cs +++ b/OpenRA.Mods.Cnc/Traits/Attack/AttackTesla.cs @@ -75,18 +75,20 @@ namespace OpenRA.Mods.Cnc.Traits public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack) { - return new ChargeAttack(this, newTarget); + return new ChargeAttack(this, newTarget, forceAttack); } - class ChargeAttack : Activity + class ChargeAttack : Activity, IActivityNotifyStanceChanged { readonly AttackTesla attack; readonly Target target; + readonly bool forceAttack; - public ChargeAttack(AttackTesla attack, Target target) + public ChargeAttack(AttackTesla attack, Target target, bool forceAttack) { this.attack = attack; this.target = target; + this.forceAttack = forceAttack; } public override Activity Tick(Actor self) @@ -114,6 +116,26 @@ namespace OpenRA.Mods.Cnc.Traits QueueChild(self, new ChargeFire(attack, target)); return this; } + + void IActivityNotifyStanceChanged.StanceChanged(Actor self, AutoTarget autoTarget, UnitStance oldStance, UnitStance newStance) + { + // Cancel non-forced targets when switching to a more restrictive stance if they are no longer valid for auto-targeting + if (newStance > oldStance || forceAttack) + return; + + if (target.Type == TargetType.Actor) + { + var a = target.Actor; + if (!autoTarget.HasValidTargetPriority(self, a.Owner, a.GetEnabledTargetTypes())) + Cancel(self, true); + } + else if (target.Type == TargetType.FrozenActor) + { + var fa = target.FrozenActor; + if (!autoTarget.HasValidTargetPriority(self, fa.Owner, fa.TargetTypes)) + Cancel(self, true); + } + } } class ChargeFire : Activity diff --git a/OpenRA.Mods.Common/Activities/Air/FlyAttack.cs b/OpenRA.Mods.Common/Activities/Air/FlyAttack.cs index 59432b8f74..b3bcd89735 100644 --- a/OpenRA.Mods.Common/Activities/Air/FlyAttack.cs +++ b/OpenRA.Mods.Common/Activities/Air/FlyAttack.cs @@ -17,22 +17,26 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Activities { - public class FlyAttack : Activity + public class FlyAttack : Activity, IActivityNotifyStanceChanged { readonly Aircraft aircraft; readonly AttackAircraft attackAircraft; readonly Rearmable rearmable; + readonly bool forceAttack; readonly int ticksUntilTurn; Target target; Target lastVisibleTarget; WDist lastVisibleMaximumRange; + BitSet lastVisibleTargetTypes; + Player lastVisibleOwner; bool useLastVisibleTarget; bool hasTicked; - public FlyAttack(Actor self, Target target) + public FlyAttack(Actor self, Target target, bool forceAttack) { this.target = target; + this.forceAttack = forceAttack; aircraft = self.Trait(); attackAircraft = self.Trait(); rearmable = self.TraitOrDefault(); @@ -45,6 +49,17 @@ namespace OpenRA.Mods.Common.Activities { lastVisibleTarget = Target.FromPos(target.CenterPosition); lastVisibleMaximumRange = attackAircraft.GetMaximumRangeVersusTarget(target); + + if (target.Type == TargetType.Actor) + { + lastVisibleOwner = target.Actor.Owner; + lastVisibleTargetTypes = target.Actor.GetEnabledTargetTypes(); + } + else if (target.Type == TargetType.FrozenActor) + { + lastVisibleOwner = target.FrozenActor.Owner; + lastVisibleTargetTypes = target.FrozenActor.TargetTypes; + } } } @@ -92,6 +107,8 @@ namespace OpenRA.Mods.Common.Activities { lastVisibleTarget = Target.FromTargetPositions(target); lastVisibleMaximumRange = attackAircraft.GetMaximumRangeVersusTarget(target); + lastVisibleOwner = target.Actor.Owner; + lastVisibleTargetTypes = target.Actor.GetEnabledTargetTypes(); } var oldUseLastVisibleTarget = useLastVisibleTarget; @@ -144,5 +161,15 @@ namespace OpenRA.Mods.Common.Activities return this; } + + void IActivityNotifyStanceChanged.StanceChanged(Actor self, AutoTarget autoTarget, UnitStance oldStance, UnitStance newStance) + { + // Cancel non-forced targets when switching to a more restrictive stance if they are no longer valid for auto-targeting + if (newStance > oldStance || forceAttack) + return; + + if (!autoTarget.HasValidTargetPriority(self, lastVisibleOwner, lastVisibleTargetTypes)) + attackAircraft.RequestedTarget = Target.Invalid; + } } } diff --git a/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs b/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs index c4f941f967..2371828af2 100644 --- a/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs +++ b/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs @@ -17,21 +17,25 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Activities { - public class HeliAttack : Activity + public class HeliAttack : Activity, IActivityNotifyStanceChanged { readonly Aircraft aircraft; readonly AttackAircraft attackAircraft; readonly Rearmable rearmable; + readonly bool forceAttack; Target target; Target lastVisibleTarget; WDist lastVisibleMaximumRange; + BitSet lastVisibleTargetTypes; + Player lastVisibleOwner; bool useLastVisibleTarget; bool hasTicked; - public HeliAttack(Actor self, Target target) + public HeliAttack(Actor self, Target target, bool forceAttack) { this.target = target; + this.forceAttack = forceAttack; aircraft = self.Trait(); attackAircraft = self.Trait(); rearmable = self.TraitOrDefault(); @@ -43,6 +47,17 @@ namespace OpenRA.Mods.Common.Activities { lastVisibleTarget = Target.FromPos(target.CenterPosition); lastVisibleMaximumRange = attackAircraft.GetMaximumRangeVersusTarget(target); + + if (target.Type == TargetType.Actor) + { + lastVisibleOwner = target.Actor.Owner; + lastVisibleTargetTypes = target.Actor.GetEnabledTargetTypes(); + } + else if (target.Type == TargetType.FrozenActor) + { + lastVisibleOwner = target.FrozenActor.Owner; + lastVisibleTargetTypes = target.FrozenActor.TargetTypes; + } } } @@ -90,6 +105,8 @@ namespace OpenRA.Mods.Common.Activities { lastVisibleTarget = Target.FromTargetPositions(target); lastVisibleMaximumRange = attackAircraft.GetMaximumRangeVersusTarget(target); + lastVisibleOwner = target.Actor.Owner; + lastVisibleTargetTypes = target.Actor.GetEnabledTargetTypes(); } var oldUseLastVisibleTarget = useLastVisibleTarget; @@ -156,5 +173,15 @@ namespace OpenRA.Mods.Common.Activities return this; } + + void IActivityNotifyStanceChanged.StanceChanged(Actor self, AutoTarget autoTarget, UnitStance oldStance, UnitStance newStance) + { + // Cancel non-forced targets when switching to a more restrictive stance if they are no longer valid for auto-targeting + if (newStance > oldStance || forceAttack) + return; + + if (!autoTarget.HasValidTargetPriority(self, lastVisibleOwner, lastVisibleTargetTypes)) + attackAircraft.RequestedTarget = Target.Invalid; + } } } diff --git a/OpenRA.Mods.Common/Activities/Attack.cs b/OpenRA.Mods.Common/Activities/Attack.cs index f934b4ee86..de2a3f5182 100644 --- a/OpenRA.Mods.Common/Activities/Attack.cs +++ b/OpenRA.Mods.Common/Activities/Attack.cs @@ -20,7 +20,7 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Activities { /* non-turreted attack */ - public class Attack : Activity + public class Attack : Activity, IActivityNotifyStanceChanged { [Flags] protected enum AttackStatus { UnableToAttack, NeedsToTurn, NeedsToMove, Attacking } @@ -35,6 +35,8 @@ namespace OpenRA.Mods.Common.Activities protected Target target; Target lastVisibleTarget; WDist lastVisibleMaximumRange; + BitSet lastVisibleTargetTypes; + Player lastVisibleOwner; bool useLastVisibleTarget; bool wasMovingWithinRange; @@ -62,6 +64,17 @@ namespace OpenRA.Mods.Common.Activities lastVisibleTarget = Target.FromPos(target.CenterPosition); lastVisibleMaximumRange = attackTraits.Where(x => !x.IsTraitDisabled) .Min(x => x.GetMaximumRangeVersusTarget(target)); + + if (target.Type == TargetType.Actor) + { + lastVisibleOwner = target.Actor.Owner; + lastVisibleTargetTypes = target.Actor.GetEnabledTargetTypes(); + } + else if (target.Type == TargetType.FrozenActor) + { + lastVisibleOwner = target.FrozenActor.Owner; + lastVisibleTargetTypes = target.FrozenActor.TargetTypes; + } } } @@ -88,6 +101,9 @@ namespace OpenRA.Mods.Common.Activities lastVisibleTarget = Target.FromTargetPositions(target); lastVisibleMaximumRange = attackTraits.Where(x => !x.IsTraitDisabled) .Min(x => x.GetMaximumRangeVersusTarget(target)); + + lastVisibleOwner = target.Actor.Owner; + lastVisibleTargetTypes = target.Actor.GetEnabledTargetTypes(); } var oldUseLastVisibleTarget = useLastVisibleTarget; @@ -211,5 +227,15 @@ namespace OpenRA.Mods.Common.Activities foreach (var a in armaments) a.CheckFire(self, facing, target); } + + void IActivityNotifyStanceChanged.StanceChanged(Actor self, AutoTarget autoTarget, UnitStance oldStance, UnitStance newStance) + { + // Cancel non-forced targets when switching to a more restrictive stance if they are no longer valid for auto-targeting + if (newStance > oldStance || forceAttack) + return; + + if (!autoTarget.HasValidTargetPriority(self, lastVisibleOwner, lastVisibleTargetTypes)) + target = Target.Invalid; + } } } diff --git a/OpenRA.Mods.Common/Traits/Air/AttackAircraft.cs b/OpenRA.Mods.Common/Traits/Air/AttackAircraft.cs index a6df9da8f8..f8527dc9f7 100644 --- a/OpenRA.Mods.Common/Traits/Air/AttackAircraft.cs +++ b/OpenRA.Mods.Common/Traits/Air/AttackAircraft.cs @@ -38,9 +38,9 @@ namespace OpenRA.Mods.Common.Traits public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack) { if (aircraftInfo.CanHover) - return new HeliAttack(self, newTarget); + return new HeliAttack(self, newTarget, forceAttack); - return new FlyAttack(self, newTarget); + return new FlyAttack(self, newTarget, forceAttack); } protected override bool CanAttack(Actor self, Target target) diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackFollow.cs b/OpenRA.Mods.Common/Traits/Attack/AttackFollow.cs index 3604ad2b79..ad218d9b44 100644 --- a/OpenRA.Mods.Common/Traits/Attack/AttackFollow.cs +++ b/OpenRA.Mods.Common/Traits/Attack/AttackFollow.cs @@ -29,7 +29,7 @@ namespace OpenRA.Mods.Common.Traits public override object Create(ActorInitializer init) { return new AttackFollow(init.Self, this); } } - public class AttackFollow : AttackBase, INotifyOwnerChanged, IDisableAutoTarget + public class AttackFollow : AttackBase, INotifyOwnerChanged, IDisableAutoTarget, INotifyStanceChanged { public new readonly AttackFollowInfo Info; public Target RequestedTarget; @@ -161,7 +161,27 @@ namespace OpenRA.Mods.Common.Traits (OpportunityTargetIsPersistentTarget && OpportunityTarget.Type != TargetType.Invalid); } - class AttackActivity : Activity + void INotifyStanceChanged.StanceChanged(Actor self, AutoTarget autoTarget, UnitStance oldStance, UnitStance newStance) + { + // Cancel opportunity targets when switching to a more restrictive stance if they are no longer valid for auto-targeting + if (newStance > oldStance || OpportunityForceAttack) + return; + + if (OpportunityTarget.Type == TargetType.Actor) + { + var a = OpportunityTarget.Actor; + if (!autoTarget.HasValidTargetPriority(self, a.Owner, a.GetEnabledTargetTypes())) + OpportunityTarget = Target.Invalid; + } + else if (OpportunityTarget.Type == TargetType.FrozenActor) + { + var fa = OpportunityTarget.FrozenActor; + if (!autoTarget.HasValidTargetPriority(self, fa.Owner, fa.TargetTypes)) + OpportunityTarget = Target.Invalid; + } + } + + class AttackActivity : Activity, IActivityNotifyStanceChanged { readonly AttackFollow attack; readonly RevealsShroud[] revealsShroud; @@ -173,6 +193,8 @@ namespace OpenRA.Mods.Common.Traits bool useLastVisibleTarget; WDist lastVisibleMaximumRange; WDist lastVisibleMinimumRange; + BitSet lastVisibleTargetTypes; + Player lastVisibleOwner; bool wasMovingWithinRange; bool hasTicked; @@ -193,6 +215,17 @@ namespace OpenRA.Mods.Common.Traits lastVisibleTarget = Target.FromPos(target.CenterPosition); lastVisibleMaximumRange = attack.GetMaximumRangeVersusTarget(target); lastVisibleMinimumRange = attack.GetMinimumRangeVersusTarget(target); + + if (target.Type == TargetType.Actor) + { + lastVisibleOwner = target.Actor.Owner; + lastVisibleTargetTypes = target.Actor.GetEnabledTargetTypes(); + } + else if (target.Type == TargetType.FrozenActor) + { + lastVisibleOwner = target.FrozenActor.Owner; + lastVisibleTargetTypes = target.FrozenActor.TargetTypes; + } } } @@ -238,6 +271,8 @@ namespace OpenRA.Mods.Common.Traits lastVisibleTarget = Target.FromTargetPositions(target); lastVisibleMaximumRange = attack.GetMaximumRangeVersusTarget(target); lastVisibleMinimumRange = attack.GetMinimumRange(); + lastVisibleOwner = target.Actor.Owner; + lastVisibleTargetTypes = target.Actor.GetEnabledTargetTypes(); // Try and sit at least one cell away from the min or max ranges to give some leeway if the target starts moving. if (move != null && target.Actor.Info.HasTraitInfo()) @@ -312,6 +347,16 @@ namespace OpenRA.Mods.Common.Traits QueueChild(self, move.MoveWithinRange(target, minRange, maxRange, checkTarget.CenterPosition, Color.Red), true); return this; } + + void IActivityNotifyStanceChanged.StanceChanged(Actor self, AutoTarget autoTarget, UnitStance oldStance, UnitStance newStance) + { + // Cancel non-forced targets when switching to a more restrictive stance if they are no longer valid for auto-targeting + if (newStance > oldStance || forceAttack) + return; + + if (!autoTarget.HasValidTargetPriority(self, lastVisibleOwner, lastVisibleTargetTypes)) + attack.RequestedTarget = Target.Invalid; + } } } } diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackOmni.cs b/OpenRA.Mods.Common/Traits/Attack/AttackOmni.cs index 238cfca405..236e22648a 100644 --- a/OpenRA.Mods.Common/Traits/Attack/AttackOmni.cs +++ b/OpenRA.Mods.Common/Traits/Attack/AttackOmni.cs @@ -27,21 +27,23 @@ namespace OpenRA.Mods.Common.Traits public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack) { - return new SetTarget(this, newTarget, allowMove); + return new SetTarget(this, newTarget, allowMove, forceAttack); } // Some 3rd-party mods rely on this being public - public class SetTarget : Activity + public class SetTarget : Activity, IActivityNotifyStanceChanged { readonly AttackOmni attack; readonly bool allowMove; + readonly bool forceAttack; Target target; - public SetTarget(AttackOmni attack, Target target, bool allowMove) + public SetTarget(AttackOmni attack, Target target, bool allowMove, bool forceAttack) { this.target = target; this.attack = attack; this.allowMove = allowMove; + this.forceAttack = forceAttack; } public override Activity Tick(Actor self) @@ -55,6 +57,26 @@ namespace OpenRA.Mods.Common.Traits attack.DoAttack(self, target); return this; } + + void IActivityNotifyStanceChanged.StanceChanged(Actor self, AutoTarget autoTarget, UnitStance oldStance, UnitStance newStance) + { + // Cancel non-forced targets when switching to a more restrictive stance if they are no longer valid for auto-targeting + if (newStance > oldStance || forceAttack) + return; + + if (target.Type == TargetType.Actor) + { + var a = target.Actor; + if (!autoTarget.HasValidTargetPriority(self, a.Owner, a.GetEnabledTargetTypes())) + target = Target.Invalid; + } + else if (target.Type == TargetType.FrozenActor) + { + var fa = target.FrozenActor; + if (!autoTarget.HasValidTargetPriority(self, fa.Owner, fa.TargetTypes)) + target = Target.Invalid; + } + } } } } diff --git a/OpenRA.Mods.Common/Traits/AutoTarget.cs b/OpenRA.Mods.Common/Traits/AutoTarget.cs index 0d3a07aa71..0373724b0c 100644 --- a/OpenRA.Mods.Common/Traits/AutoTarget.cs +++ b/OpenRA.Mods.Common/Traits/AutoTarget.cs @@ -313,6 +313,25 @@ namespace OpenRA.Mods.Common.Traits ab.AttackTarget(target, false, allowMove); } + public bool HasValidTargetPriority(Actor self, Player owner, BitSet targetTypes) + { + if (Stance <= UnitStance.ReturnFire) + return false; + + return activeTargetPriorities.Any(ati => + { + // Incompatible stances + if (!ati.ValidStances.HasStance(self.Owner.Stances[owner])) + return false; + + // Incompatible target types + if (!ati.ValidTargets.Overlaps(targetTypes) || ati.InvalidTargets.Overlaps(targetTypes)) + return false; + + return true; + }); + } + Target ChooseTarget(Actor self, AttackBase ab, Stance attackStances, WDist scanRange, bool allowMove, bool allowTurn) { var chosenTarget = Target.Invalid;