Merge pull request #5617 from pchote/attack-moveadjacent
Improve unit-attack movement
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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<AttackBase>();
|
||||
facing = self.Trait<IFacing>();
|
||||
|
||||
move = allowMovement ? self.TraitOrDefault<IMove>() : null;
|
||||
}
|
||||
|
||||
public override Activity Tick(Actor self)
|
||||
{
|
||||
var attack = self.Trait<AttackBase>();
|
||||
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<Mobile>() && !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<FrozenUnderFog>() && !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<IMove>().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<IFacing>();
|
||||
if (facing.Facing != desiredFacing)
|
||||
return Util.SequenceActivities(new Turn(desiredFacing), this);
|
||||
|
||||
attack.DoAttack(self, Target);
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Plane>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<IMove>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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<Mobile>();
|
||||
pathFinder = self.World.WorldActor.Trait<PathFinder>();
|
||||
domainIndex = self.World.WorldActor.TraitOrDefault<DomainIndex>();
|
||||
domainIndex = self.World.WorldActor.Trait<DomainIndex>();
|
||||
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<CPos> CandidateMovementCells(Actor self)
|
||||
{
|
||||
return Util.AdjacentCells(target);
|
||||
}
|
||||
|
||||
void UpdateInnerPath(Actor self)
|
||||
{
|
||||
var targetCells = Util.AdjacentCells(target);
|
||||
var targetCells = CandidateMovementCells(self);
|
||||
var searchCells = new List<CPos>();
|
||||
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<Target> GetTargets(Actor self)
|
||||
|
||||
63
OpenRA.Mods.RA/Activities/MoveWithinRange.cs
Executable file
63
OpenRA.Mods.RA/Activities/MoveWithinRange.cs
Executable file
@@ -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<CPos> 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)); }
|
||||
|
||||
@@ -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<IMove>())
|
||||
|| (target.Type == TargetType.FrozenActor && target.FrozenActor.Info.Traits.Contains<IMove>());
|
||||
|
||||
// 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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace OpenRA.Mods.RA
|
||||
self.SetTargetLine(target, Color.Yellow);
|
||||
|
||||
var range = WRange.FromCells(target.Actor.Info.Traits.Get<GuardableInfo>().Range);
|
||||
self.QueueActivity(false, new AttackMove.AttackMoveActivity(self, self.Trait<IMove>().MoveFollow(self, target, range)));
|
||||
self.QueueActivity(false, new AttackMove.AttackMoveActivity(self, self.Trait<IMove>().MoveFollow(self, target, WRange.Zero, range)));
|
||||
}
|
||||
|
||||
public string VoicePhraseForOrder(Actor self, Order order)
|
||||
|
||||
@@ -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<List<CPos>> pathFunc) { return new Move(pathFunc); }
|
||||
|
||||
public void OnNotifyBlockingMove(Actor self, Actor blocking)
|
||||
|
||||
@@ -528,6 +528,7 @@
|
||||
<Compile Include="Console\PlayerCommands.cs" />
|
||||
<Compile Include="Render\WithRepairAnimation.cs" />
|
||||
<Compile Include="Render\WithRepairOverlay.cs" />
|
||||
<Compile Include="Activities\MoveWithinRange.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OpenRA.Game\OpenRA.Game.csproj">
|
||||
|
||||
Reference in New Issue
Block a user