diff --git a/OpenRA.Mods.Common/AI/HackyAI.cs b/OpenRA.Mods.Common/AI/HackyAI.cs index b09760a04d..505169850e 100644 --- a/OpenRA.Mods.Common/AI/HackyAI.cs +++ b/OpenRA.Mods.Common/AI/HackyAI.cs @@ -664,7 +664,7 @@ namespace OpenRA.Mods.Common.AI if (protectSq == null) protectSq = RegisterNewSquad(SquadType.Protection, attacker); - if (!protectSq.TargetIsValid) + if (!protectSq.IsTargetValid) protectSq.TargetActor = attacker; if (!protectSq.IsValid) diff --git a/OpenRA.Mods.Common/AI/Squad.cs b/OpenRA.Mods.Common/AI/Squad.cs index 0193671d3a..fb1a460bbd 100644 --- a/OpenRA.Mods.Common/AI/Squad.cs +++ b/OpenRA.Mods.Common/AI/Squad.cs @@ -72,11 +72,16 @@ namespace OpenRA.Mods.Common.AI set { Target = Target.FromActor(value); } } - public bool TargetIsValid + public bool IsTargetValid { get { return Target.IsValidFor(Units.FirstOrDefault()) && !Target.Actor.HasTrait(); } } + public bool IsTargetVisible + { + get { return Bot.Player.PlayerActor.Owner.CanTargetActor(TargetActor); } + } + public WPos CenterPosition { get { return Units.Select(u => u.CenterPosition).Average(); } } } } diff --git a/OpenRA.Mods.Common/AI/States/AirStates.cs b/OpenRA.Mods.Common/AI/States/AirStates.cs index 095ee9f7f7..880ea20253 100644 --- a/OpenRA.Mods.Common/AI/States/AirStates.cs +++ b/OpenRA.Mods.Common/AI/States/AirStates.cs @@ -185,7 +185,7 @@ namespace OpenRA.Mods.Common.AI if (!owner.IsValid) return; - if (!owner.TargetIsValid) + if (!owner.IsTargetValid) { var a = owner.Units.Random(owner.Random); var closestEnemy = owner.Bot.FindClosestEnemy(a.CenterPosition); diff --git a/OpenRA.Mods.Common/AI/States/GroundStates.cs b/OpenRA.Mods.Common/AI/States/GroundStates.cs index 853a911da4..4d59540678 100644 --- a/OpenRA.Mods.Common/AI/States/GroundStates.cs +++ b/OpenRA.Mods.Common/AI/States/GroundStates.cs @@ -30,7 +30,7 @@ namespace OpenRA.Mods.Common.AI if (!owner.IsValid) return; - if (!owner.TargetIsValid) + if (!owner.IsTargetValid) { var t = owner.Bot.FindClosestEnemy(owner.Units.FirstOrDefault().CenterPosition); if (t == null) return; @@ -68,7 +68,7 @@ namespace OpenRA.Mods.Common.AI if (!owner.IsValid) return; - if (!owner.TargetIsValid) + if (!owner.IsTargetValid) { var closestEnemy = owner.Bot.FindClosestEnemy(owner.Units.Random(owner.Random).CenterPosition); if (closestEnemy != null) @@ -127,7 +127,7 @@ namespace OpenRA.Mods.Common.AI if (!owner.IsValid) return; - if (!owner.TargetIsValid) + if (!owner.IsTargetValid) { var closestEnemy = owner.Bot.FindClosestEnemy(owner.Units.Random(owner.Random).CenterPosition); if (closestEnemy != null) diff --git a/OpenRA.Mods.Common/AI/States/ProtectionStates.cs b/OpenRA.Mods.Common/AI/States/ProtectionStates.cs index 1a405e89d8..a6ebb0771c 100644 --- a/OpenRA.Mods.Common/AI/States/ProtectionStates.cs +++ b/OpenRA.Mods.Common/AI/States/ProtectionStates.cs @@ -19,6 +19,9 @@ namespace OpenRA.Mods.Common.AI class UnitsForProtectionAttackState : GroundStateBase, IState { + public const int BackoffTicks = 4; + internal int Backoff = BackoffTicks; + public void Activate(Squad owner) { } public void Tick(Squad owner) @@ -26,7 +29,7 @@ namespace OpenRA.Mods.Common.AI if (!owner.IsValid) return; - if (!owner.TargetIsValid) + if (!owner.IsTargetValid) { owner.TargetActor = owner.Bot.FindClosestEnemy(owner.CenterPosition, WRange.FromCells(8)); @@ -37,8 +40,22 @@ namespace OpenRA.Mods.Common.AI } } - foreach (var a in owner.Units) - owner.World.IssueOrder(new Order("AttackMove", a, false) { TargetLocation = owner.TargetActor.Location }); + if (!owner.IsTargetVisible) + { + if (Backoff < 0) + { + owner.FuzzyStateMachine.ChangeState(owner, new UnitsForProtectionFleeState(), true); + Backoff = BackoffTicks; + return; + } + + Backoff--; + } + else + { + foreach (var a in owner.Units) + owner.World.IssueOrder(new Order("AttackMove", a, false) { TargetLocation = owner.TargetActor.Location }); + } } public void Deactivate(Squad owner) { } diff --git a/OpenRA.Mods.Common/Activities/Air/FlyAttack.cs b/OpenRA.Mods.Common/Activities/Air/FlyAttack.cs index a071b0406c..50aa09ffa6 100644 --- a/OpenRA.Mods.Common/Activities/Air/FlyAttack.cs +++ b/OpenRA.Mods.Common/Activities/Air/FlyAttack.cs @@ -22,6 +22,7 @@ namespace OpenRA.Mods.Common.Activities readonly Target target; readonly AttackPlane attackPlane; readonly AmmoPool[] ammoPools; + Activity inner; int ticksUntilTurn; diff --git a/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs b/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs index e9155ecf9f..cf54845c5f 100644 --- a/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs +++ b/OpenRA.Mods.Common/Activities/Air/HeliAttack.cs @@ -8,8 +8,8 @@ */ #endregion -using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using OpenRA.Activities; using OpenRA.Mods.Common.Traits; @@ -19,17 +19,35 @@ namespace OpenRA.Mods.Common.Activities { public class HeliAttack : Activity { - readonly Target target; readonly Helicopter helicopter; readonly AttackHeli attackHeli; readonly AmmoPool[] ammoPools; + readonly bool attackOnlyVisibleTargets; - public HeliAttack(Actor self, Target target) + Target target; + bool canHideUnderFog; + protected Target Target { - this.target = target; + get + { + return target; + } + + private set + { + target = value; + if (target.Type == TargetType.Actor) + canHideUnderFog = target.Actor.HasTrait(); + } + } + + public HeliAttack(Actor self, Target target, bool attackOnlyVisibleTargets = true) + { + Target = target; helicopter = self.Trait(); attackHeli = self.Trait(); ammoPools = self.TraitsImplementing().ToArray(); + this.attackOnlyVisibleTargets = attackOnlyVisibleTargets; } public override Activity Tick(Actor self) @@ -37,6 +55,16 @@ namespace OpenRA.Mods.Common.Activities if (IsCanceled || !target.IsValidFor(self)) return NextActivity; + if (attackOnlyVisibleTargets && target.Type == TargetType.Actor && canHideUnderFog + && !self.Owner.CanTargetActor(target.Actor)) + { + var newTarget = Target.FromCell(self.World, self.World.Map.CellContaining(target.CenterPosition)); + + self.CancelActivity(); + self.SetTargetLine(newTarget, Color.Green); + return Util.SequenceActivities(new HeliFly(self, newTarget)); + } + // If all ammo pools are depleted and none reload automatically, return to helipad to reload and then move to next activity // TODO: This should check whether there is ammo left that is actually suitable for the target if (ammoPools.All(x => !x.Info.SelfReloads && !x.HasAmmo())) diff --git a/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs b/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs index a1cbc0b5c1..db9624cc5e 100644 --- a/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs +++ b/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using OpenRA.Activities; using OpenRA.Mods.Common.Pathfinder; @@ -27,7 +28,23 @@ namespace OpenRA.Mods.Common.Activities readonly DomainIndex domainIndex; readonly uint movementClass; - protected Target Target { get; private set; } + Target target; + bool canHideUnderFog; + protected Target Target + { + get + { + return target; + } + + private set + { + target = value; + if (target.Type == TargetType.Actor) + canHideUnderFog = target.Actor.HasTrait(); + } + } + protected CPos targetPosition; Activity inner; bool repath; @@ -66,6 +83,17 @@ namespace OpenRA.Mods.Common.Activities { var targetIsValid = Target.IsValidFor(self); + // Target moved under the fog. Move to its last known position. + if (Target.Type == TargetType.Actor && canHideUnderFog + && !self.Owner.CanTargetActor(Target.Actor)) + { + if (inner != null) + inner.Cancel(self); + + self.SetTargetLine(Target.FromCell(self.World, targetPosition), Color.Green); + return Util.RunActivity(self, new AttackMoveActivity(self, mobile.MoveTo(targetPosition, 0))); + } + // Inner move order has completed. if (inner == null) {