diff --git a/OpenRA.Mods.Common/Activities/Air/Fly.cs b/OpenRA.Mods.Common/Activities/Air/Fly.cs index c2ca376ded..486d0968cc 100644 --- a/OpenRA.Mods.Common/Activities/Air/Fly.cs +++ b/OpenRA.Mods.Common/Activities/Air/Fly.cs @@ -20,15 +20,27 @@ namespace OpenRA.Mods.Common.Activities public class Fly : Activity { readonly Aircraft aircraft; - readonly Target target; readonly WDist maxRange; readonly WDist minRange; + readonly Color? targetLineColor; + Target target; + Target lastVisibleTarget; + bool useLastVisibleTarget; bool soundPlayed; public Fly(Actor self, Target t, WPos? initialTargetPosition = null, Color? targetLineColor = null) { aircraft = self.Trait(); target = t; + this.targetLineColor = targetLineColor; + + // The target may become hidden between the initial order request and the first tick (e.g. if queued) + // Moving to any position (even if quite stale) is still better than immediately giving up + if ((target.Type == TargetType.Actor && target.Actor.CanBeViewedByPlayer(self.Owner)) + || target.Type == TargetType.FrozenActor || target.Type == TargetType.Terrain) + lastVisibleTarget = Target.FromPos(target.CenterPosition); + else if (initialTargetPosition.HasValue) + lastVisibleTarget = Target.FromPos(initialTargetPosition.Value); } public Fly(Actor self, Target t, WDist minRange, WDist maxRange, @@ -63,14 +75,30 @@ namespace OpenRA.Mods.Common.Activities { // Refuse to take off if it would land immediately again. if (aircraft.ForceLanding) - { Cancel(self); - return NextActivity; - } - if (IsCanceled || !target.IsValidFor(self)) + if (IsCanceled) return NextActivity; + bool targetIsHiddenActor; + target = target.Recalculate(self.Owner, out targetIsHiddenActor); + if (!targetIsHiddenActor && target.Type == TargetType.Actor) + lastVisibleTarget = Target.FromTargetPositions(target); + + var oldUseLastVisibleTarget = useLastVisibleTarget; + useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self); + + // Update target lines if required + if (useLastVisibleTarget != oldUseLastVisibleTarget && targetLineColor.HasValue) + self.SetTargetLine(useLastVisibleTarget ? lastVisibleTarget : target, targetLineColor.Value, false); + + // Target is hidden or dead, and we don't have a fallback position to move towards + if (useLastVisibleTarget && !lastVisibleTarget.IsValidFor(self)) + return NextActivity; + + var pos = self.CenterPosition; + var checkTarget = useLastVisibleTarget ? lastVisibleTarget : target; + if (!soundPlayed && aircraft.Info.TakeoffSounds.Length > 0 && self.IsAtGroundLevel()) { Game.Sound.Play(SoundType.World, aircraft.Info.TakeoffSounds.Random(self.World.SharedRandom), aircraft.CenterPosition); @@ -78,20 +106,20 @@ namespace OpenRA.Mods.Common.Activities } // Inside the target annulus, so we're done - var insideMaxRange = maxRange.Length > 0 && target.IsInRange(aircraft.CenterPosition, maxRange); - var insideMinRange = minRange.Length > 0 && target.IsInRange(aircraft.CenterPosition, minRange); + var insideMaxRange = maxRange.Length > 0 && checkTarget.IsInRange(aircraft.CenterPosition, maxRange); + var insideMinRange = minRange.Length > 0 && checkTarget.IsInRange(aircraft.CenterPosition, minRange); if (insideMaxRange && !insideMinRange) return NextActivity; - var d = target.CenterPosition - self.CenterPosition; + var delta = checkTarget.CenterPosition - self.CenterPosition; // The next move would overshoot, so consider it close enough var move = aircraft.FlyStep(aircraft.Facing); - if (d.HorizontalLengthSquared < move.HorizontalLengthSquared) + if (delta.HorizontalLengthSquared < move.HorizontalLengthSquared) return NextActivity; // Don't turn until we've reached the cruise altitude - var desiredFacing = d.Yaw.Facing; + var desiredFacing = delta.Yaw.Facing; var targetAltitude = aircraft.CenterPosition.Z + aircraft.Info.CruiseAltitude.Length - self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition).Length; if (aircraft.CenterPosition.Z < targetAltitude) desiredFacing = aircraft.Facing; diff --git a/OpenRA.Mods.Common/Activities/Air/FlyFollow.cs b/OpenRA.Mods.Common/Activities/Air/FlyFollow.cs index 7118ecbf09..bdc4b61728 100644 --- a/OpenRA.Mods.Common/Activities/Air/FlyFollow.cs +++ b/OpenRA.Mods.Common/Activities/Air/FlyFollow.cs @@ -23,6 +23,9 @@ namespace OpenRA.Mods.Common.Activities readonly WDist maxRange; readonly Color? targetLineColor; Target target; + Target lastVisibleTarget; + bool useLastVisibleTarget; + bool wasMovingWithinRange; public FlyFollow(Actor self, Target target, WDist minRange, WDist maxRange, WPos? initialTargetPosition, Color? targetLineColor = null) @@ -32,27 +35,63 @@ namespace OpenRA.Mods.Common.Activities this.minRange = minRange; this.maxRange = maxRange; this.targetLineColor = targetLineColor; + + // The target may become hidden between the initial order request and the first tick (e.g. if queued) + // Moving to any position (even if quite stale) is still better than immediately giving up + if ((target.Type == TargetType.Actor && target.Actor.CanBeViewedByPlayer(self.Owner)) + || target.Type == TargetType.FrozenActor || target.Type == TargetType.Terrain) + lastVisibleTarget = Target.FromPos(target.CenterPosition); + else if (initialTargetPosition.HasValue) + lastVisibleTarget = Target.FromPos(initialTargetPosition.Value); } public override Activity Tick(Actor self) { // Refuse to take off if it would land immediately again. if (aircraft.ForceLanding) - { Cancel(self); - return NextActivity; - } - if (IsCanceled || !target.IsValidFor(self)) + if (IsCanceled) return NextActivity; - if (target.IsInRange(self.CenterPosition, maxRange) && !target.IsInRange(self.CenterPosition, minRange)) + bool targetIsHiddenActor; + target = target.Recalculate(self.Owner, out targetIsHiddenActor); + if (!targetIsHiddenActor && target.Type == TargetType.Actor) + lastVisibleTarget = Target.FromTargetPositions(target); + + var oldUseLastVisibleTarget = useLastVisibleTarget; + useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self); + + // If we are ticking again after previously sequencing a MoveWithRange then that move must have completed + // Either we are in range and can see the target, or we've lost track of it and should give up + if (wasMovingWithinRange && targetIsHiddenActor) + return NextActivity; + + wasMovingWithinRange = false; + + // Update target lines if required + if (useLastVisibleTarget != oldUseLastVisibleTarget && targetLineColor.HasValue) + self.SetTargetLine(useLastVisibleTarget ? lastVisibleTarget : target, targetLineColor.Value, false); + + // Target is hidden or dead, and we don't have a fallback position to move towards + if (useLastVisibleTarget && !lastVisibleTarget.IsValidFor(self)) + return NextActivity; + + var pos = self.CenterPosition; + var checkTarget = useLastVisibleTarget ? lastVisibleTarget : target; + + // We've reached the required range - if the target is visible and valid then we wait + // otherwise if it is hidden or dead we give up + if (checkTarget.IsInRange(pos, maxRange) && !checkTarget.IsInRange(pos, minRange)) { Fly.FlyToward(self, aircraft, aircraft.Facing, aircraft.Info.CruiseAltitude); - return this; + return useLastVisibleTarget ? NextActivity : this; } - return ActivityUtils.SequenceActivities(new Fly(self, target, minRange, maxRange, targetLineColor: targetLineColor), this); + wasMovingWithinRange = true; + return ActivityUtils.SequenceActivities( + aircraft.MoveWithinRange(target, minRange, maxRange, checkTarget.CenterPosition, targetLineColor), + this); } } } diff --git a/OpenRA.Mods.Common/Activities/Air/HeliFly.cs b/OpenRA.Mods.Common/Activities/Air/HeliFly.cs index f101c17aa6..ef69fef888 100644 --- a/OpenRA.Mods.Common/Activities/Air/HeliFly.cs +++ b/OpenRA.Mods.Common/Activities/Air/HeliFly.cs @@ -20,15 +20,28 @@ namespace OpenRA.Mods.Common.Activities public class HeliFly : Activity { readonly Aircraft aircraft; - readonly Target target; readonly WDist maxRange; readonly WDist minRange; + readonly Color? targetLineColor; bool soundPlayed; + Target target; + Target lastVisibleTarget; + bool useLastVisibleTarget; + public HeliFly(Actor self, Target t, WPos? initialTargetPosition = null, Color? targetLineColor = null) { aircraft = self.Trait(); target = t; + this.targetLineColor = targetLineColor; + + // The target may become hidden between the initial order request and the first tick (e.g. if queued) + // Moving to any position (even if quite stale) is still better than immediately giving up + if ((target.Type == TargetType.Actor && target.Actor.CanBeViewedByPlayer(self.Owner)) + || target.Type == TargetType.FrozenActor || target.Type == TargetType.Terrain) + lastVisibleTarget = Target.FromPos(target.CenterPosition); + else if (initialTargetPosition.HasValue) + lastVisibleTarget = Target.FromPos(initialTargetPosition.Value); } public HeliFly(Actor self, Target t, WDist minRange, WDist maxRange, @@ -58,12 +71,25 @@ namespace OpenRA.Mods.Common.Activities { // Refuse to take off if it would land immediately again. if (aircraft.ForceLanding) - { Cancel(self); - return NextActivity; - } - if (IsCanceled || !target.IsValidFor(self)) + if (IsCanceled) + return NextActivity; + + bool targetIsHiddenActor; + target = target.Recalculate(self.Owner, out targetIsHiddenActor); + if (!targetIsHiddenActor && target.Type == TargetType.Actor) + lastVisibleTarget = Target.FromTargetPositions(target); + + var oldUseLastVisibleTarget = useLastVisibleTarget; + useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self); + + // Update target lines if required + if (useLastVisibleTarget != oldUseLastVisibleTarget && targetLineColor.HasValue) + self.SetTargetLine(useLastVisibleTarget ? lastVisibleTarget : target, targetLineColor.Value, false); + + // Target is hidden or dead, and we don't have a fallback position to move towards + if (useLastVisibleTarget && !lastVisibleTarget.IsValidFor(self)) return NextActivity; if (!soundPlayed && aircraft.Info.TakeoffSounds.Length > 0 && self.IsAtGroundLevel()) @@ -75,30 +101,33 @@ namespace OpenRA.Mods.Common.Activities if (AdjustAltitude(self, aircraft, aircraft.Info.CruiseAltitude)) return this; - var pos = target.CenterPosition; + var checkTarget = useLastVisibleTarget ? lastVisibleTarget : target; - // Rotate towards the target - var dist = pos - self.CenterPosition; - var desiredFacing = dist.HorizontalLengthSquared != 0 ? dist.Yaw.Facing : aircraft.Facing; + // Update facing + var delta = checkTarget.CenterPosition - aircraft.CenterPosition; + var desiredFacing = delta.HorizontalLengthSquared != 0 ? delta.Yaw.Facing : aircraft.Facing; aircraft.Facing = Util.TickFacing(aircraft.Facing, desiredFacing, aircraft.TurnSpeed); + if (AdjustAltitude(self, aircraft, aircraft.Info.CruiseAltitude)) + return this; + var move = aircraft.FlyStep(desiredFacing); // Inside the minimum range, so reverse - if (minRange.Length > 0 && target.IsInRange(aircraft.CenterPosition, minRange)) + if (minRange.Length > 0 && checkTarget.IsInRange(aircraft.CenterPosition, minRange)) { aircraft.SetPosition(self, aircraft.CenterPosition - move); return this; } // Inside the maximum range, so we're done - if (maxRange.Length > 0 && target.IsInRange(aircraft.CenterPosition, maxRange)) + if (maxRange.Length > 0 && checkTarget.IsInRange(aircraft.CenterPosition, maxRange)) return NextActivity; // The next move would overshoot, so just set the final position - if (dist.HorizontalLengthSquared < move.HorizontalLengthSquared) + if (delta.HorizontalLengthSquared < move.HorizontalLengthSquared) { var targetAltitude = aircraft.CenterPosition.Z + aircraft.Info.CruiseAltitude.Length - self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition).Length; - aircraft.SetPosition(self, pos + new WVec(0, 0, targetAltitude - pos.Z)); + aircraft.SetPosition(self, checkTarget.CenterPosition + new WVec(0, 0, targetAltitude - checkTarget.CenterPosition.Z)); return NextActivity; }