Merge pull request #5617 from pchote/attack-moveadjacent

Improve unit-attack movement
This commit is contained in:
Matthias Mailänder
2014-06-17 10:03:49 +02:00
15 changed files with 167 additions and 87 deletions

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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)
{

View File

@@ -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)

View 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;
});
}
}
}

View File

@@ -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)

View File

@@ -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)); }

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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">