diff --git a/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs b/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs index 0f548a3319..82e387893c 100644 --- a/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs +++ b/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs @@ -10,7 +10,6 @@ #endregion using System.Collections.Generic; -using System.Linq; using OpenRA.Activities; using OpenRA.Mods.Common.Traits; using OpenRA.Primitives; @@ -26,7 +25,7 @@ namespace OpenRA.Mods.Common.Activities protected Target Target => useLastVisibleTarget ? lastVisibleTarget : target; Target target; - Target lastVisibleTarget; + protected Target lastVisibleTarget; protected CPos lastVisibleTargetLocation; bool useLastVisibleTarget; @@ -43,7 +42,7 @@ namespace OpenRA.Mods.Common.Activities || target.Type == TargetType.FrozenActor || target.Type == TargetType.Terrain) { lastVisibleTarget = Target.FromPos(target.CenterPosition); - lastVisibleTargetLocation = self.World.Map.CellContaining(target.CenterPosition); + SetVisibleTargetLocation(self, target); } else if (initialTargetPosition.HasValue) { @@ -62,10 +61,9 @@ namespace OpenRA.Mods.Common.Activities return lastVisibleTargetLocation != targetLocation; } - protected virtual IEnumerable CandidateMovementCells(Actor self) + protected virtual void SetVisibleTargetLocation(Actor self, Target target) { - return Util.AdjacentCells(self.World, Target) - .Where(c => Mobile.CanStayInCell(c)); + lastVisibleTargetLocation = self.World.Map.CellContaining(target.CenterPosition); } protected override void OnFirstRun(Actor self) @@ -80,7 +78,7 @@ namespace OpenRA.Mods.Common.Activities if (!targetIsHiddenActor && target.Type == TargetType.Actor) { lastVisibleTarget = Target.FromTargetPositions(target); - lastVisibleTargetLocation = self.World.Map.CellContaining(target.CenterPosition); + SetVisibleTargetLocation(self, target); } // Target is equivalent to checkTarget variable in other activities @@ -91,43 +89,42 @@ namespace OpenRA.Mods.Common.Activities // Target is hidden or dead, and we don't have a fallback position to move towards var noTarget = useLastVisibleTarget && !lastVisibleTarget.IsValidFor(self); - // Cancel the current path if the activity asks to stop, or asks to repath - // The repath happens once the move activity stops in the next cell - var shouldRepath = targetIsValid && ShouldRepath(self, oldTargetLocation); - if (ChildActivity != null && (ShouldStop(self) || shouldRepath || noTarget)) - ChildActivity.Cancel(self); - - // Target has moved, and MoveAdjacentTo is still valid. - if (!IsCanceling && shouldRepath) + // Cancel the current path if the activity asks to stop. + if (ShouldStop(self) || noTarget) + Cancel(self); + else if (!IsCanceling && targetIsValid && ShouldRepath(self, oldTargetLocation)) + { + // Target has moved, but is still valid. + ChildActivity?.Cancel(self); QueueChild(Mobile.MoveTo(check => CalculatePathToTarget(self, check))); + } // The last queued childactivity is guaranteed to be the inner move, so if the childactivity // queue is empty it means we have reached our destination. return TickChild(self); } - readonly List searchCells = new(); - int searchCellsTick = -1; + protected readonly List SearchCells = new(); - List CalculatePathToTarget(Actor self, BlockedByActor check) + protected int searchCellsTick = -1; + + protected virtual List CalculatePathToTarget(Actor self, BlockedByActor check) { - var loc = self.Location; - - // PERF: Assume that CandidateMovementCells doesn't change within a tick to avoid repeated queries - // when Move enumerates different BlockedByActor values + // PERF: Assume that candidate cells don't change within a tick to avoid repeated queries + // when Move enumerates different BlockedByActor values. if (searchCellsTick != self.World.WorldTick) { - searchCells.Clear(); + SearchCells.Clear(); searchCellsTick = self.World.WorldTick; - foreach (var cell in CandidateMovementCells(self)) - if (Mobile.CanEnterCell(cell)) - searchCells.Add(cell); + foreach (var cell in Util.AdjacentCells(self.World, Target)) + if (Mobile.CanStayInCell(cell) && Mobile.CanEnterCell(cell)) + SearchCells.Add(cell); } - if (searchCells.Count == 0) + if (SearchCells.Count == 0) return PathFinder.NoPath; - return Mobile.PathFinder.FindPathToTargetCells(self, loc, searchCells, check); + return Mobile.PathFinder.FindPathToTargetCells(self, self.Location, SearchCells, check); } public override IEnumerable GetTargets(Actor self) diff --git a/OpenRA.Mods.Common/Activities/Move/MoveOnto.cs b/OpenRA.Mods.Common/Activities/Move/MoveOnto.cs new file mode 100644 index 0000000000..ec8bb6d86d --- /dev/null +++ b/OpenRA.Mods.Common/Activities/Move/MoveOnto.cs @@ -0,0 +1,57 @@ +#region Copyright & License Information +/* + * Copyright (c) The OpenRA Developers and Contributors + * 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 System.Collections.Generic; +using OpenRA.Mods.Common.Traits; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Activities +{ + public class MoveOnto : MoveAdjacentTo + { + readonly WVec offset = WVec.Zero; + + public MoveOnto(Actor self, in Target target, WVec? offset = null, WPos? initialTargetPosition = null, Color? targetLineColor = null) + : base(self, target, initialTargetPosition, targetLineColor) + { + if (offset.HasValue) + this.offset = offset.Value; + } + + protected override void SetVisibleTargetLocation(Actor self, Target target) + { + lastVisibleTargetLocation = self.World.Map.CellContaining(Target.CenterPosition + offset); + } + + protected override bool ShouldStop(Actor self) + { + // Stop if the target is dead. + return Target.Type == TargetType.Terrain; + } + + protected override List CalculatePathToTarget(Actor self, BlockedByActor check) + { + // If we are close to the target but can't enter, we wait. + if (!Mobile.CanEnterCell(lastVisibleTargetLocation) && Util.AreAdjacentCells(lastVisibleTargetLocation, self.Location)) + return PathFinder.NoPath; + + // PERF: Don't create a new list every run. + // PERF: Also reuse the already created list in the base class. + if (SearchCells.Count == 0) + SearchCells.Add(lastVisibleTargetLocation); + else if (SearchCells[0] != lastVisibleTargetLocation) + SearchCells[0] = lastVisibleTargetLocation; + + return Mobile.PathFinder.FindPathToTargetCells(self, self.Location, SearchCells, check); + } + } +} diff --git a/OpenRA.Mods.Common/Activities/Move/MoveWithinRange.cs b/OpenRA.Mods.Common/Activities/Move/MoveWithinRange.cs index 991b7915fc..6884e9413a 100644 --- a/OpenRA.Mods.Common/Activities/Move/MoveWithinRange.cs +++ b/OpenRA.Mods.Common/Activities/Move/MoveWithinRange.cs @@ -10,7 +10,7 @@ #endregion using System.Collections.Generic; -using System.Linq; +using OpenRA.Mods.Common.Traits; using OpenRA.Primitives; using OpenRA.Traits; @@ -48,10 +48,23 @@ namespace OpenRA.Mods.Common.Activities || !Mobile.CanInteractWithGroundLayer(self) || !Mobile.CanStayInCell(self.Location)); } - protected override IEnumerable CandidateMovementCells(Actor self) + protected override List CalculatePathToTarget(Actor self, BlockedByActor check) { - return map.FindTilesInAnnulus(lastVisibleTargetLocation, minCells, maxCells) - .Where(c => Mobile.CanStayInCell(c) && AtCorrectRange(map.CenterOfSubCell(c, Mobile.FromSubCell))); + // PERF: Assume that candidate cells don't change within a tick to avoid repeated queries + // when Move enumerates different BlockedByActor values. + if (searchCellsTick != self.World.WorldTick) + { + SearchCells.Clear(); + searchCellsTick = self.World.WorldTick; + foreach (var cell in map.FindTilesInAnnulus(lastVisibleTargetLocation, minCells, maxCells)) + if (Mobile.CanStayInCell(cell) && Mobile.CanEnterCell(cell) && AtCorrectRange(map.CenterOfSubCell(cell, Mobile.FromSubCell))) + SearchCells.Add(cell); + } + + if (SearchCells.Count == 0) + return PathFinder.NoPath; + + return Mobile.PathFinder.FindPathToTargetCells(self, self.Location, SearchCells, check); } bool AtCorrectRange(WPos origin)