diff --git a/OpenRA.Mods.Common/Activities/Air/FlyAttack.cs b/OpenRA.Mods.Common/Activities/Air/FlyAttack.cs index fb149f727c..91a39aac8b 100644 --- a/OpenRA.Mods.Common/Activities/Air/FlyAttack.cs +++ b/OpenRA.Mods.Common/Activities/Air/FlyAttack.cs @@ -43,7 +43,7 @@ namespace OpenRA.Mods.Common.Activities return NextActivity; } - target = target.Recalculate(self.Owner); + target = target.RecalculateInvalidatingHiddenTargets(self.Owner); if (!target.IsValidFor(self)) return NextActivity; diff --git a/OpenRA.Mods.Common/Activities/Attack.cs b/OpenRA.Mods.Common/Activities/Attack.cs index f8d51f35eb..058dd0332b 100644 --- a/OpenRA.Mods.Common/Activities/Attack.cs +++ b/OpenRA.Mods.Common/Activities/Attack.cs @@ -53,7 +53,7 @@ namespace OpenRA.Mods.Common.Activities protected virtual Target RecalculateTarget(Actor self) { - return target.Recalculate(self.Owner); + return target.RecalculateInvalidatingHiddenTargets(self.Owner); } public override Activity Tick(Actor self) @@ -148,47 +148,4 @@ namespace OpenRA.Mods.Common.Activities return AttackStatus.Attacking; } } - - public static class TargetExts - { - /// Update (Frozen)Actor targets to account for visibility changes or actor replacement - public static Target Recalculate(this Target t, Player viewer) - { - // Check whether the target has transformed into something else - // HACK: This relies on knowing the internal implementation details of Target - if (t.Type == TargetType.Invalid && t.Actor != null && t.Actor.ReplacedByActor != null) - t = Target.FromActor(t.Actor.ReplacedByActor); - - // Bot-controlled units aren't yet capable of understanding visibility changes - if (viewer.IsBot) - return t; - - if (t.Type == TargetType.Actor) - { - // Actor has been hidden under the fog - if (!t.Actor.CanBeViewedByPlayer(viewer)) - { - // Replace with FrozenActor if applicable, otherwise drop the target - var frozen = viewer.FrozenActorLayer.FromID(t.Actor.ActorID); - return frozen != null ? Target.FromFrozenActor(frozen) : Target.Invalid; - } - } - else if (t.Type == TargetType.FrozenActor) - { - // Frozen actor has been revealed - if (!t.FrozenActor.Visible || !t.FrozenActor.IsValid) - { - // Original actor is still alive - if (t.FrozenActor.Actor != null && t.FrozenActor.Actor.CanBeViewedByPlayer(viewer)) - return Target.FromActor(t.FrozenActor.Actor); - - // Original actor was killed while hidden - if (t.Actor == null) - return Target.Invalid; - } - } - - return t; - } - } } diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 89cd1b24cd..e80838673a 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -120,6 +120,7 @@ + diff --git a/OpenRA.Mods.Common/TargetExtensions.cs b/OpenRA.Mods.Common/TargetExtensions.cs new file mode 100644 index 0000000000..745aa7cc60 --- /dev/null +++ b/OpenRA.Mods.Common/TargetExtensions.cs @@ -0,0 +1,79 @@ +#region Copyright & License Information +/* + * Copyright 2007-2019 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using OpenRA.Traits; + +namespace OpenRA.Mods.Common +{ + public static class TargetExtensions + { + /// + /// Update (Frozen)Actor targets to account for visibility changes or actor replacement. + /// If the target actor becomes hidden without a FrozenActor, the target is invalidated. + /// /// + public static Target RecalculateInvalidatingHiddenTargets(this Target t, Player viewer) + { + bool targetIsHiddenActor; + var updated = t.Recalculate(viewer, out targetIsHiddenActor); + return targetIsHiddenActor ? Target.Invalid : updated; + } + + /// + /// Update (Frozen)Actor targets to account for visibility changes or actor replacement. + /// If the target actor becomes hidden without a FrozenActor, the target actor is kept + /// and the actorHidden flag is set to true. + /// + public static Target Recalculate(this Target t, Player viewer, out bool targetIsHiddenActor) + { + targetIsHiddenActor = false; + + // Check whether the target has transformed into something else + // HACK: This relies on knowing the internal implementation details of Target + if (t.Type == TargetType.Invalid && t.Actor != null && t.Actor.ReplacedByActor != null) + t = Target.FromActor(t.Actor.ReplacedByActor); + + // Bot-controlled units aren't yet capable of understanding visibility changes + if (viewer.IsBot) + return t; + + if (t.Type == TargetType.Actor) + { + // Actor has been hidden under the fog + if (!t.Actor.CanBeViewedByPlayer(viewer)) + { + // Replace with FrozenActor if applicable, otherwise return target unmodified + var frozen = viewer.FrozenActorLayer.FromID(t.Actor.ActorID); + if (frozen != null) + return Target.FromFrozenActor(frozen); + + targetIsHiddenActor = true; + return t; + } + } + else if (t.Type == TargetType.FrozenActor) + { + // Frozen actor has been revealed + if (!t.FrozenActor.Visible || !t.FrozenActor.IsValid) + { + // Original actor is still alive + if (t.FrozenActor.Actor != null && t.FrozenActor.Actor.CanBeViewedByPlayer(viewer)) + return Target.FromActor(t.FrozenActor.Actor); + + // Original actor was killed while hidden + if (t.Actor == null) + return Target.Invalid; + } + } + + return t; + } + } +} diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackFollow.cs b/OpenRA.Mods.Common/Traits/Attack/AttackFollow.cs index c5315f49fc..80f8cb79f8 100644 --- a/OpenRA.Mods.Common/Traits/Attack/AttackFollow.cs +++ b/OpenRA.Mods.Common/Traits/Attack/AttackFollow.cs @@ -33,7 +33,9 @@ namespace OpenRA.Mods.Common.Traits protected override void Tick(Actor self) { - Target = Target.Recalculate(self.Owner); + // We can safely ignore target visibility here - the armament will handle this for us. + bool targetIsHiddenActor; + Target = Target.Recalculate(self.Owner, out targetIsHiddenActor); if (IsTraitDisabled) { Target = Target.Invalid; @@ -83,7 +85,12 @@ namespace OpenRA.Mods.Common.Traits public override Activity Tick(Actor self) { - target = target.Recalculate(self.Owner); + // All of the interesting behaviour to move to the last known target position if it becomes hidden + // and to reacquire the target if it is revealed enroute is handled inside MoveWithinRange. + // At this point in the activity chain we are either ticking against the target for the first time + // (and so don't know where it is), or after MoveWithinRange has lost the target and given up. + // We can therefore treat a hidden targets as invalid and give up if we can't currently see it. + target = target.RecalculateInvalidatingHiddenTargets(self.Owner); if (IsCanceled || !target.IsValidFor(self)) return NextActivity; diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackOmni.cs b/OpenRA.Mods.Common/Traits/Attack/AttackOmni.cs index 29d174be0f..4d85975b0b 100644 --- a/OpenRA.Mods.Common/Traits/Attack/AttackOmni.cs +++ b/OpenRA.Mods.Common/Traits/Attack/AttackOmni.cs @@ -46,7 +46,9 @@ namespace OpenRA.Mods.Common.Traits public override Activity Tick(Actor self) { - target = target.Recalculate(self.Owner); + // This activity can't move to reacquire hidden targets, so use the + // Recalculate overload that invalidates hidden targets. + target = target.RecalculateInvalidatingHiddenTargets(self.Owner); if (IsCanceled || !target.IsValidFor(self) || !attack.IsReachableTarget(target, allowMove)) return NextActivity;