diff --git a/OpenRA.Mods.Common/Activities/Move/Follow.cs b/OpenRA.Mods.Common/Activities/Move/Follow.cs index b8e740c64b..ff333879a8 100644 --- a/OpenRA.Mods.Common/Activities/Move/Follow.cs +++ b/OpenRA.Mods.Common/Activities/Move/Follow.cs @@ -18,11 +18,14 @@ namespace OpenRA.Mods.Common.Activities { public class Follow : Activity { - readonly Target target; readonly WDist minRange; readonly WDist maxRange; readonly IMove move; readonly Color? targetLineColor; + Target target; + Target lastVisibleTarget; + bool useLastVisibleTarget; + bool wasMovingWithinRange; public Follow(Actor self, Target target, WDist minRange, WDist maxRange, WPos? initialTargetPosition, Color? targetLineColor = null) @@ -31,26 +34,58 @@ namespace OpenRA.Mods.Common.Activities this.minRange = minRange; this.maxRange = maxRange; this.targetLineColor = targetLineColor; - move = self.Trait(); + + // 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) { - if (IsCanceled || !target.IsValidFor(self)) + if (IsCanceled) return NextActivity; - var cachedPosition = target.CenterPosition; - var path = move.MoveWithinRange(target, minRange, maxRange, targetLineColor: targetLineColor); + bool targetIsHiddenActor; + target = target.Recalculate(self.Owner, out targetIsHiddenActor); + if (!targetIsHiddenActor && target.Type == TargetType.Actor) + lastVisibleTarget = Target.FromTargetPositions(target); - // We are already in range, so wait until the target moves before doing anything - if (target.IsInRange(self.CenterPosition, maxRange) && !target.IsInRange(self.CenterPosition, minRange)) - { - var wait = new WaitFor(() => !target.IsValidFor(self) || target.CenterPosition != cachedPosition); - return ActivityUtils.SequenceActivities(wait, path, this); - } + var oldUseLastVisibleTarget = useLastVisibleTarget; + useLastVisibleTarget = targetIsHiddenActor || !target.IsValidFor(self); - return ActivityUtils.SequenceActivities(path, this); + // 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)) + return useLastVisibleTarget ? NextActivity : this; + + // Move into range + wasMovingWithinRange = true; + return ActivityUtils.SequenceActivities( + move.MoveWithinRange(target, minRange, maxRange, checkTarget.CenterPosition, targetLineColor), + this); } } }