diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index 4c7554d89b..f841947000 100755 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -170,7 +170,8 @@ namespace OpenRA.Traits Activity MoveTo(CPos cell, int nearEnough); Activity MoveTo(CPos cell, Actor ignoredActor); Activity MoveWithinRange(Target target, WRange range); - Activity MoveFollow(Actor self, Target target, WRange range); + Activity MoveWithinRange(Target target, WRange minRange, WRange maxRange); + Activity MoveFollow(Actor self, Target target, WRange minRange, WRange maxRange); Activity MoveIntoWorld(Actor self, CPos cell); Activity VisualMove(Actor self, WPos fromPos, WPos toPos); CPos NearestMoveableCell(CPos target); diff --git a/OpenRA.Mods.RA/Activities/Attack.cs b/OpenRA.Mods.RA/Activities/Attack.cs index cf2958cc86..4d20d99dea 100755 --- a/OpenRA.Mods.RA/Activities/Attack.cs +++ b/OpenRA.Mods.RA/Activities/Attack.cs @@ -16,28 +16,27 @@ namespace OpenRA.Mods.RA.Activities /* non-turreted attack */ public class Attack : Activity { - protected Target Target; - WRange Range; - bool AllowMovement; + protected readonly Target Target; + readonly AttackBase attack; + readonly IMove move; + readonly IFacing facing; + readonly WRange minRange; + readonly WRange maxRange; - int nextPathTime; - - const int delayBetweenPathingAttempts = 20; - const int delaySpread = 5; - - public Attack(Target target, WRange range) - : this(target, range, true) {} - - public Attack(Target target, WRange range, bool allowMovement) + public Attack(Actor self, Target target, WRange minRange, WRange maxRange, bool allowMovement) { Target = target; - Range = range; - AllowMovement = allowMovement; + this.minRange = minRange; + this.maxRange = maxRange; + + attack = self.Trait(); + facing = self.Trait(); + + move = allowMovement ? self.TraitOrDefault() : null; } public override Activity Tick(Actor self) { - var attack = self.Trait(); var ret = InnerTick(self, attack); attack.IsAttacking = (ret == this); return ret; @@ -52,27 +51,23 @@ namespace OpenRA.Mods.RA.Activities if (!Target.IsValidFor(self) || type == TargetType.FrozenActor) return NextActivity; - // TODO: This is horrible, and probably wrong. Work out what it is trying to solve, then redo it properly. - if (type == TargetType.Actor && Target.Actor.HasTrait() && !self.Owner.Shroud.IsTargetable(Target.Actor)) + // Drop the target if it moves under the shroud / fog. + // HACK: This would otherwise break targeting frozen actors + // The problem is that Shroud.IsTargetable returns false (as it should) for + // frozen actors, but we do want to explicitly target the underlying actor here. + if (type == TargetType.Actor && !Target.Actor.HasTrait() && !self.Owner.Shroud.IsTargetable(Target.Actor)) return NextActivity; - if (!Target.IsInRange(self.CenterPosition, Range)) - { - if (--nextPathTime > 0) - return this; - - nextPathTime = self.World.SharedRandom.Next(delayBetweenPathingAttempts - delaySpread, - delayBetweenPathingAttempts + delaySpread); - - return (AllowMovement) ? Util.SequenceActivities(self.Trait().MoveWithinRange(Target, Range), this) : NextActivity; - } + // Try to move within range + if (move != null && (!Target.IsInRange(self.CenterPosition, maxRange) || Target.IsInRange(self.CenterPosition, minRange))) + return Util.SequenceActivities(move.MoveWithinRange(Target, minRange, maxRange), this); var desiredFacing = Util.GetFacing(Target.CenterPosition - self.CenterPosition, 0); - var facing = self.Trait(); if (facing.Facing != desiredFacing) return Util.SequenceActivities(new Turn(desiredFacing), this); attack.DoAttack(self, Target); + return this; } } diff --git a/OpenRA.Mods.RA/Activities/FlyFollow.cs b/OpenRA.Mods.RA/Activities/FlyFollow.cs index 2e62d3fdb1..83ea6a0fa1 100644 --- a/OpenRA.Mods.RA/Activities/FlyFollow.cs +++ b/OpenRA.Mods.RA/Activities/FlyFollow.cs @@ -17,13 +17,15 @@ namespace OpenRA.Mods.RA.Activities { Target target; Plane plane; - WRange range; + WRange minRange; + WRange maxRange; - public FlyFollow(Actor self, Target target, WRange range) + public FlyFollow(Actor self, Target target, WRange minRange, WRange maxRange) { this.target = target; plane = self.Trait(); - this.range = range; + this.minRange = minRange; + this.maxRange = maxRange; } public override Activity Tick(Actor self) @@ -31,13 +33,13 @@ namespace OpenRA.Mods.RA.Activities if (IsCanceled || !target.IsValidFor(self)) return NextActivity; - if (target.IsInRange(self.CenterPosition, range)) + if (target.IsInRange(self.CenterPosition, maxRange) && !target.IsInRange(self.CenterPosition, minRange)) { Fly.FlyToward(self, plane, plane.Facing, plane.Info.CruiseAltitude); return this; } - return Util.SequenceActivities(new Fly(self, target, WRange.Zero, range), this); + return Util.SequenceActivities(new Fly(self, target, minRange, maxRange), this); } } } diff --git a/OpenRA.Mods.RA/Activities/Follow.cs b/OpenRA.Mods.RA/Activities/Follow.cs index 3f74bf8621..4cd5b5ce91 100644 --- a/OpenRA.Mods.RA/Activities/Follow.cs +++ b/OpenRA.Mods.RA/Activities/Follow.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2011 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2014 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. For more information, @@ -8,25 +8,25 @@ */ #endregion +using System.Collections.Generic; using OpenRA.Traits; namespace OpenRA.Mods.RA.Activities { public class Follow : Activity { - Target target; - IMove move; - WRange range; - int nextPathTime; + readonly Target target; + readonly WRange minRange; + readonly WRange maxRange; + readonly IMove move; - const int delayBetweenPathingAttempts = 20; - const int delaySpread = 5; - - public Follow(Actor self, Target target, WRange range) + public Follow(Actor self, Target target, WRange minRange, WRange maxRange) { this.target = target; + this.minRange = minRange; + this.maxRange = maxRange; + move = self.Trait(); - this.range = range; } public override Activity Tick(Actor self) @@ -34,13 +34,17 @@ namespace OpenRA.Mods.RA.Activities if (IsCanceled || !target.IsValidFor(self)) return NextActivity; - if (target.IsInRange(self.CenterPosition, range) || --nextPathTime > 0) - return this; + var cachedPosition = target.CenterPosition; + var path = move.MoveWithinRange(target, minRange, maxRange); - nextPathTime = self.World.SharedRandom.Next(delayBetweenPathingAttempts - delaySpread, - delayBetweenPathingAttempts + delaySpread); + // 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 Util.SequenceActivities(wait, path, this); + } - return Util.SequenceActivities(move.MoveWithinRange(target, range), this); + return Util.SequenceActivities(path, this); } } } diff --git a/OpenRA.Mods.RA/Activities/Heal.cs b/OpenRA.Mods.RA/Activities/Heal.cs index 660337a952..b73cc59899 100755 --- a/OpenRA.Mods.RA/Activities/Heal.cs +++ b/OpenRA.Mods.RA/Activities/Heal.cs @@ -12,11 +12,10 @@ using OpenRA.Traits; namespace OpenRA.Mods.RA.Activities { - /* non-turreted attack */ public class Heal : Attack { - public Heal(Target target, WRange range, bool allowMovement) - : base(target, range, allowMovement) { } + public Heal(Actor self, Target target, WRange minRange, WRange maxRange, bool allowMovement) + : base(self, target, minRange, maxRange, allowMovement) { } protected override Activity InnerTick(Actor self, AttackBase attack) { diff --git a/OpenRA.Mods.RA/Activities/MoveAdjacentTo.cs b/OpenRA.Mods.RA/Activities/MoveAdjacentTo.cs index 6fb50cc2d4..a2569415dc 100755 --- a/OpenRA.Mods.RA/Activities/MoveAdjacentTo.cs +++ b/OpenRA.Mods.RA/Activities/MoveAdjacentTo.cs @@ -17,14 +17,14 @@ namespace OpenRA.Mods.RA.Activities { public class MoveAdjacentTo : Activity { - readonly Target target; + protected readonly Target target; readonly Mobile mobile; readonly PathFinder pathFinder; readonly DomainIndex domainIndex; readonly uint movementClass; + protected CPos targetPosition; Activity inner; - CPos targetPosition; bool repath; public MoveAdjacentTo(Actor self, Target target) @@ -33,12 +33,25 @@ namespace OpenRA.Mods.RA.Activities mobile = self.Trait(); pathFinder = self.World.WorldActor.Trait(); - domainIndex = self.World.WorldActor.TraitOrDefault(); + domainIndex = self.World.WorldActor.Trait(); movementClass = (uint)mobile.Info.GetMovementClass(self.World.TileSet); + if (target.IsValidFor(self)) + targetPosition = target.CenterPosition.ToCPos(); + repath = true; } + protected virtual bool ShouldStop(Actor self, CPos oldTargetPosition) + { + return false; + } + + protected virtual bool ShouldRepath(Actor self, CPos oldTargetPosition) + { + return targetPosition != oldTargetPosition; + } + public override Activity Tick(Actor self) { var targetIsValid = target.IsValidFor(self); @@ -59,15 +72,17 @@ namespace OpenRA.Mods.RA.Activities if (targetIsValid) { // Check if the target has moved - var oldPosition = targetPosition; + var oldTargetPosition = targetPosition; targetPosition = target.CenterPosition.ToCPos(); - if (!repath && targetPosition != oldPosition) + + var shroudStop = ShouldStop(self, oldTargetPosition); + if (shroudStop || (!repath && ShouldRepath(self, oldTargetPosition))) { // Finish moving into the next cell and then repath. if (inner != null) inner.Cancel(self); - repath = true; + repath = !shroudStop; } } else @@ -84,34 +99,30 @@ namespace OpenRA.Mods.RA.Activities return this; } + protected virtual IEnumerable CandidateMovementCells(Actor self) + { + return Util.AdjacentCells(target); + } + void UpdateInnerPath(Actor self) { - var targetCells = Util.AdjacentCells(target); + var targetCells = CandidateMovementCells(self); var searchCells = new List(); var loc = self.Location; foreach (var cell in targetCells) - if (mobile.CanEnterCell(cell) && (domainIndex == null || domainIndex.IsPassable(loc, cell, movementClass))) + if (domainIndex.IsPassable(loc, cell, movementClass) && mobile.CanEnterCell(cell)) searchCells.Add(cell); - if (searchCells.Any()) - { - var ps1 = new PathSearch(self.World, mobile.Info, self) - { - checkForBlocked = true, - heuristic = location => 0, - inReverse = true - }; + if (!searchCells.Any()) + return; - foreach (var cell in searchCells) - ps1.AddInitialCell(cell); + var path = pathFinder.FindBidiPath( + PathSearch.FromPoints(self.World, mobile.Info, self, searchCells, loc, true), + PathSearch.FromPoint(self.World, mobile.Info, self, loc, targetPosition, true).InReverse() + ); - ps1.heuristic = PathSearch.DefaultEstimator(mobile.toCell); - var ps2 = PathSearch.FromPoint(self.World, mobile.Info, self, mobile.toCell, targetPosition, true); - var ret = pathFinder.FindBidiPath(ps1, ps2); - - inner = mobile.MoveTo(() => ret); - } + inner = mobile.MoveTo(() => path); } public override IEnumerable GetTargets(Actor self) diff --git a/OpenRA.Mods.RA/Activities/MoveWithinRange.cs b/OpenRA.Mods.RA/Activities/MoveWithinRange.cs new file mode 100755 index 0000000000..20eb9b4ce6 --- /dev/null +++ b/OpenRA.Mods.RA/Activities/MoveWithinRange.cs @@ -0,0 +1,63 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 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. For more information, + * see COPYING. + */ +#endregion + +using System.Collections.Generic; +using System.Linq; +using OpenRA.Mods.RA.Move; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA.Activities +{ + public class MoveWithinRange : MoveAdjacentTo + { + readonly WRange maxRange; + readonly WRange minRange; + + public MoveWithinRange(Actor self, Target target, WRange minRange, WRange maxRange) + : base(self, target) + { + this.minRange = minRange; + this.maxRange = maxRange; + } + + protected override bool ShouldStop(Actor self, CPos oldTargetPosition) + { + // We are now in range. Don't move any further! + // HACK: This works around the pathfinder not returning the shortest path + var cp = self.CenterPosition; + return target.IsInRange(cp, maxRange) && !target.IsInRange(cp, minRange); + } + + protected override bool ShouldRepath(Actor self, CPos oldTargetPosition) + { + var cp = self.CenterPosition; + return targetPosition != oldTargetPosition && (!target.IsInRange(cp, maxRange) || target.IsInRange(cp, minRange)); + } + + protected override IEnumerable CandidateMovementCells(Actor self) + { + var maxCells = (maxRange.Range + 1023) / 1024; + var outerCells = self.World.Map.FindTilesInCircle(targetPosition, maxCells); + + var minCells = minRange.Range / 1024; + var innerCells = self.World.Map.FindTilesInCircle(targetPosition, minCells); + + var outerSq = maxRange.Range * maxRange.Range; + var innerSq = minRange.Range * minRange.Range; + var center = target.CenterPosition; + + return outerCells.Except(innerCells).Where(c => + { + var dxSq = (c.CenterPosition - center).HorizontalLengthSquared; + return dxSq >= innerSq && dxSq <= outerSq; + }); + } + } +} diff --git a/OpenRA.Mods.RA/Air/Helicopter.cs b/OpenRA.Mods.RA/Air/Helicopter.cs index 4d0a446b27..7dc9e46de0 100755 --- a/OpenRA.Mods.RA/Air/Helicopter.cs +++ b/OpenRA.Mods.RA/Air/Helicopter.cs @@ -165,7 +165,7 @@ namespace OpenRA.Mods.RA.Air public Activity MoveTo(CPos cell, Actor ignoredActor) { return new HeliFly(self, Target.FromCell(cell)); } public Activity MoveWithinRange(Target target, WRange range) { return new HeliFly(self, target, WRange.Zero, range); } public Activity MoveWithinRange(Target target, WRange minRange, WRange maxRange) { return new HeliFly(self, target, minRange, maxRange); } - public Activity MoveFollow(Actor self, Target target, WRange range) { return new Follow(self, target, range); } + public Activity MoveFollow(Actor self, Target target, WRange minRange, WRange maxRange) { return new Follow(self, target, minRange, maxRange); } public CPos NearestMoveableCell(CPos cell) { return cell; } public Activity MoveIntoWorld(Actor self, CPos cell) diff --git a/OpenRA.Mods.RA/Air/Plane.cs b/OpenRA.Mods.RA/Air/Plane.cs index 652df7166b..40f81c5b43 100755 --- a/OpenRA.Mods.RA/Air/Plane.cs +++ b/OpenRA.Mods.RA/Air/Plane.cs @@ -105,7 +105,7 @@ namespace OpenRA.Mods.RA.Air public Activity MoveTo(CPos cell, Actor ignoredActor) { return Util.SequenceActivities(new Fly(self, Target.FromCell(cell)), new FlyCircle()); } public Activity MoveWithinRange(Target target, WRange range) { return Util.SequenceActivities(new Fly(self, target, WRange.Zero, range), new FlyCircle()); } public Activity MoveWithinRange(Target target, WRange minRange, WRange maxRange) { return Util.SequenceActivities(new Fly(self, target, minRange, maxRange), new FlyCircle()); } - public Activity MoveFollow(Actor self, Target target, WRange range) { return new FlyFollow(self, target, range); } + public Activity MoveFollow(Actor self, Target target, WRange minRange, WRange maxRange) { return new FlyFollow(self, target, minRange, maxRange); } public CPos NearestMoveableCell(CPos cell) { return cell; } public Activity MoveIntoWorld(Actor self, CPos cell) { return new Fly(self, Target.FromCell(cell)); } diff --git a/OpenRA.Mods.RA/Attack/AttackFollow.cs b/OpenRA.Mods.RA/Attack/AttackFollow.cs index 2eb3bb3d10..cd7bbfaaeb 100644 --- a/OpenRA.Mods.RA/Attack/AttackFollow.cs +++ b/OpenRA.Mods.RA/Attack/AttackFollow.cs @@ -80,17 +80,20 @@ namespace OpenRA.Mods.RA if (self.IsDisabled()) return this; - const int RangeTolerance = 1; /* how far inside our maximum range we should try to sit */ var weapon = attack.ChooseArmamentForTarget(target); - if (weapon != null) { - var range = WRange.FromCells(Math.Max(0, weapon.Weapon.Range.Range / 1024 - RangeTolerance)); + var targetIsMobile = (target.Type == TargetType.Actor && target.Actor.HasTrait()) + || (target.Type == TargetType.FrozenActor && target.FrozenActor.Info.Traits.Contains()); + + // Try and sit at least one cell closer than the max range to give some leeway if the target starts moving. + var maxRange = targetIsMobile ? new WRange(Math.Max(weapon.Weapon.MinRange.Range, weapon.Weapon.Range.Range - 1024)) + : weapon.Weapon.Range; attack.Target = target; if (move != null) - return Util.SequenceActivities(move.MoveFollow(self, target, range), this); + return Util.SequenceActivities(move.MoveFollow(self, target, weapon.Weapon.MinRange, maxRange), this); } return NextActivity; diff --git a/OpenRA.Mods.RA/Attack/AttackFrontal.cs b/OpenRA.Mods.RA/Attack/AttackFrontal.cs index c46a2c8fae..ab0c1dd02a 100644 --- a/OpenRA.Mods.RA/Attack/AttackFrontal.cs +++ b/OpenRA.Mods.RA/Attack/AttackFrontal.cs @@ -51,7 +51,7 @@ namespace OpenRA.Mods.RA if (a == null) return null; - return new Activities.Attack(newTarget, a.Weapon.Range, allowMove); + return new Activities.Attack(self, newTarget, a.Weapon.MinRange, a.Weapon.Range, allowMove); } } } diff --git a/OpenRA.Mods.RA/Attack/AttackMedic.cs b/OpenRA.Mods.RA/Attack/AttackMedic.cs index 4065582a42..1bf2b524e6 100644 --- a/OpenRA.Mods.RA/Attack/AttackMedic.cs +++ b/OpenRA.Mods.RA/Attack/AttackMedic.cs @@ -31,7 +31,7 @@ namespace OpenRA.Mods.RA if (a == null) return null; - return new Activities.Heal(newTarget, a.Weapon.Range, allowMove); + return new Activities.Heal(self, newTarget, a.Weapon.MinRange, a.Weapon.Range, allowMove); } } } diff --git a/OpenRA.Mods.RA/Guard.cs b/OpenRA.Mods.RA/Guard.cs index 0e9e1b62f2..5472a61956 100644 --- a/OpenRA.Mods.RA/Guard.cs +++ b/OpenRA.Mods.RA/Guard.cs @@ -35,7 +35,7 @@ namespace OpenRA.Mods.RA self.SetTargetLine(target, Color.Yellow); var range = WRange.FromCells(target.Actor.Info.Traits.Get().Range); - self.QueueActivity(false, new AttackMove.AttackMoveActivity(self, self.Trait().MoveFollow(self, target, range))); + self.QueueActivity(false, new AttackMove.AttackMoveActivity(self, self.Trait().MoveFollow(self, target, WRange.Zero, range))); } public string VoicePhraseForOrder(Actor self, Order order) diff --git a/OpenRA.Mods.RA/Move/Mobile.cs b/OpenRA.Mods.RA/Move/Mobile.cs index b698758ec0..6ede32679a 100755 --- a/OpenRA.Mods.RA/Move/Mobile.cs +++ b/OpenRA.Mods.RA/Move/Mobile.cs @@ -591,8 +591,9 @@ namespace OpenRA.Mods.RA.Move public Activity ScriptedMove(CPos cell) { return new Move(cell); } public Activity MoveTo(CPos cell, int nearEnough) { return new Move(cell, nearEnough); } public Activity MoveTo(CPos cell, Actor ignoredActor) { return new Move(cell, ignoredActor); } - public Activity MoveWithinRange(Target target, WRange range) { return new Move(target, range); } - public Activity MoveFollow(Actor self, Target target, WRange range) { return new Follow(self, target, range); } + public Activity MoveWithinRange(Target target, WRange range) { return new MoveWithinRange(self, target, WRange.Zero, range); } + public Activity MoveWithinRange(Target target, WRange minRange, WRange maxRange) { return new MoveWithinRange(self, target, minRange, maxRange); } + public Activity MoveFollow(Actor self, Target target, WRange minRange, WRange maxRange) { return new Follow(self, target, minRange, maxRange); } public Activity MoveTo(Func> pathFunc) { return new Move(pathFunc); } public void OnNotifyBlockingMove(Actor self, Actor blocking) diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index 7161cb0c93..bc0080da54 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -528,6 +528,7 @@ +