Overhaul Carryall behaviour, adding support for manual control.

This commit is contained in:
Paul Chote
2016-07-31 02:01:29 +01:00
parent d08cc10abb
commit 1ae86f34f8
20 changed files with 842 additions and 402 deletions

View File

@@ -18,7 +18,7 @@ namespace OpenRA.Mods.Common.Activities
{
int remainingTicks;
public FlyCircleTimed(int ticks, Actor self) : base(self)
public FlyCircleTimed(Actor self, int ticks) : base(self)
{
remainingTicks = ticks;
}

View File

@@ -17,12 +17,18 @@ namespace OpenRA.Mods.Common.Activities
public class HeliLand : Activity
{
readonly Aircraft helicopter;
bool requireSpace;
readonly WDist landAltitude;
readonly bool requireSpace;
bool playedSound;
public HeliLand(Actor self, bool requireSpace)
: this(self, requireSpace, self.Info.TraitInfo<AircraftInfo>().LandAltitude) { }
public HeliLand(Actor self, bool requireSpace, WDist landAltitude)
{
this.requireSpace = requireSpace;
this.landAltitude = landAltitude;
helicopter = self.Trait<Aircraft>();
}
@@ -40,7 +46,7 @@ namespace OpenRA.Mods.Common.Activities
playedSound = true;
}
if (HeliFly.AdjustAltitude(self, helicopter, helicopter.Info.LandAltitude))
if (HeliFly.AdjustAltitude(self, helicopter, landAltitude))
return this;
return NextActivity;

View File

@@ -109,7 +109,7 @@ namespace OpenRA.Mods.Common.Activities
if (nearestAfld != null)
return ActivityUtils.SequenceActivities(
new Fly(self, Target.FromActor(nearestAfld), WDist.Zero, plane.Info.WaitDistanceFromResupplyBase),
new FlyCircleTimed(plane.Info.NumberOfTicksToVerifyAvailableAirport, self),
new FlyCircleTimed(self, plane.Info.NumberOfTicksToVerifyAvailableAirport),
this);
else
return NextActivity;

View File

@@ -65,9 +65,8 @@ namespace OpenRA.Mods.Common.Activities
if (self.Location != proc.Location + iao.DeliveryOffset)
{
var notify = self.TraitsImplementing<INotifyHarvesterAction>();
var next = new DeliverResources(self);
foreach (var n in notify)
n.MovingToRefinery(self, proc.Location + iao.DeliveryOffset, next);
n.MovingToRefinery(self, proc.Location + iao.DeliveryOffset, this);
return ActivityUtils.SequenceActivities(movement.MoveTo(proc.Location + iao.DeliveryOffset, 0), this);
}

View File

@@ -9,6 +9,7 @@
*/
#endregion
using System.Linq;
using OpenRA.Activities;
using OpenRA.Mods.Common.Traits;
using OpenRA.Traits;
@@ -18,86 +19,145 @@ namespace OpenRA.Mods.Common.Activities
public class DeliverUnit : Activity
{
readonly Actor self;
readonly Actor cargo;
readonly IMove movement;
readonly Carryable carryable;
readonly Carryall carryall;
readonly Aircraft aircraft;
readonly IPositionable positionable;
readonly IFacing cargoFacing;
readonly IFacing selfFacing;
readonly BodyOrientation body;
readonly IFacing carryableFacing;
readonly IFacing carryallFacing;
readonly CPos destination;
enum State { Transport, Land, Release }
enum State { Transport, Land, Wait, Release, TakeOff, Aborted }
State state;
Activity innerActivity;
public DeliverUnit(Actor self)
public DeliverUnit(Actor self, CPos destination)
{
carryall = self.Trait<Carryall>();
this.self = self;
cargo = carryall.Carrying;
movement = self.Trait<IMove>();
carryable = cargo.Trait<Carryable>();
aircraft = self.Trait<Aircraft>();
positionable = cargo.Trait<IPositionable>();
cargoFacing = cargo.Trait<IFacing>();
selfFacing = self.Trait<IFacing>();
this.destination = destination;
carryallFacing = self.Trait<IFacing>();
carryall = self.Trait<Carryall>();
body = self.Trait<BodyOrientation>();
carryable = carryall.Carryable.Trait<Carryable>();
positionable = carryall.Carryable.Trait<IPositionable>();
carryableFacing = carryall.Carryable.Trait<IFacing>();
state = State.Transport;
}
// Find a suitable location to drop our carryable
CPos GetLocationToDrop(CPos requestedPosition)
CPos? FindDropLocation(CPos targetCell, WDist maxSearchDistance)
{
if (positionable.CanEnterCell(requestedPosition))
return requestedPosition;
// The easy case
if (positionable.CanEnterCell(targetCell))
return targetCell;
var candidateCells = Util.AdjacentCells(self.World, Target.FromCell(self.World, requestedPosition));
// TODO: This will behave badly if there is no suitable drop point nearby
do
var cellRange = (maxSearchDistance.Length + 1023) / 1024;
var centerPosition = self.World.Map.CenterOfCell(targetCell);
foreach (var c in self.World.Map.FindTilesInCircle(targetCell, cellRange))
{
foreach (var c in candidateCells)
if (positionable.CanEnterCell(c))
return c;
if (!positionable.CanEnterCell(c))
continue;
// Expanding dropable cells search area
// TODO: This also includes all of the cells we have just checked
candidateCells = Util.ExpandFootprint(candidateCells, true);
} while (true);
var delta = self.World.Map.CenterOfCell(c) - centerPosition;
if (delta.LengthSquared < maxSearchDistance.LengthSquared)
return c;
}
return null;
}
// Check if we can drop the unit at our current location.
bool CanDropHere()
{
return positionable.CanEnterCell(self.Location);
var localOffset = carryall.CarryableOffset.Rotate(body.QuantizeOrientation(self, self.Orientation));
var targetCell = self.World.Map.CellContaining(self.CenterPosition + body.LocalToWorld(localOffset));
return positionable.CanEnterCell(targetCell);
}
public override Activity Tick(Actor self)
{
if (cargo.IsDead || !carryall.IsBusy)
if (innerActivity != null)
{
carryall.UnreserveCarryable();
return NextActivity;
innerActivity = ActivityUtils.RunActivity(self, innerActivity);
return this;
}
if (IsCanceled)
return NextActivity;
if ((carryall.State == Carryall.CarryallState.Idle || carryall.Carryable.IsDead) && state != State.TakeOff)
state = State.Aborted;
switch (state)
{
case State.Transport:
var targetl = GetLocationToDrop(carryable.Destination);
{
var targetLocation = FindDropLocation(destination, carryall.Info.DropRange);
// Can't land, so wait at the target until something changes
if (!targetLocation.HasValue)
{
innerActivity = ActivityUtils.SequenceActivities(
new HeliFly(self, Target.FromCell(self.World, destination)),
new Wait(25));
return this;
}
var targetPosition = self.World.Map.CenterOfCell(targetLocation.Value);
var localOffset = carryall.CarryableOffset.Rotate(body.QuantizeOrientation(self, self.Orientation));
var carryablePosition = self.CenterPosition + body.LocalToWorld(localOffset);
if ((carryablePosition - targetPosition).HorizontalLengthSquared != 0)
{
// For non-zero offsets the drop position depends on the carryall facing
// We therefore need to predict/correct for the facing *at the drop point*
if (carryall.CarryableOffset.HorizontalLengthSquared != 0)
{
var facing = (targetPosition - self.CenterPosition).Yaw.Facing;
localOffset = carryall.CarryableOffset.Rotate(body.QuantizeOrientation(self, WRot.FromFacing(facing)));
innerActivity = ActivityUtils.SequenceActivities(
new HeliFly(self, Target.FromPos(targetPosition - body.LocalToWorld(localOffset))),
new Turn(self, facing));
return this;
}
innerActivity = new HeliFly(self, Target.FromPos(targetPosition));
return this;
}
state = State.Land;
return ActivityUtils.SequenceActivities(movement.MoveTo(targetl, 0), this);
return this;
}
case State.Land:
{
if (!CanDropHere())
{
state = State.Transport;
return this;
}
if (HeliFly.AdjustAltitude(self, aircraft, aircraft.Info.LandAltitude))
// Make sure that the carried actor is on the ground before releasing it
var localOffset = carryall.CarryableOffset.Rotate(body.QuantizeOrientation(self, self.Orientation));
var carryablePosition = self.CenterPosition + body.LocalToWorld(localOffset);
if (self.World.Map.DistanceAboveTerrain(carryablePosition) != WDist.Zero)
{
innerActivity = new HeliLand(self, false, -new WDist(carryall.CarryableOffset.Z));
return this;
}
state = carryall.Info.UnloadingDelay > 0 ? State.Wait : State.Release;
return this;
}
case State.Wait:
state = State.Release;
return ActivityUtils.SequenceActivities(new Wait(15), this);
innerActivity = new Wait(carryall.Info.UnloadingDelay, false);
return this;
case State.Release:
if (!CanDropHere())
@@ -107,7 +167,15 @@ namespace OpenRA.Mods.Common.Activities
}
Release();
return NextActivity;
state = State.TakeOff;
return this;
case State.TakeOff:
return ActivityUtils.SequenceActivities(new HeliFly(self, Target.FromPos(self.CenterPosition)), NextActivity);
case State.Aborted:
carryall.UnreserveCarryable(self);
break;
}
return NextActivity;
@@ -115,24 +183,29 @@ namespace OpenRA.Mods.Common.Activities
void Release()
{
positionable.SetPosition(cargo, self.Location, SubCell.FullCell);
cargoFacing.Facing = selfFacing.Facing;
var localOffset = carryall.CarryableOffset.Rotate(body.QuantizeOrientation(self, self.Orientation));
var targetPosition = self.CenterPosition + body.LocalToWorld(localOffset);
var targetLocation = self.World.Map.CellContaining(targetPosition);
positionable.SetPosition(carryall.Carryable, targetLocation, SubCell.FullCell);
carryableFacing.Facing = carryallFacing.Facing;
// Put back into world
self.World.AddFrameEndTask(w =>
{
cargo.World.Add(cargo);
carryall.UnreserveCarryable();
var cargo = carryall.Carryable;
w.Add(cargo);
carryall.DetachCarryable(self);
carryable.UnReserve(cargo);
carryable.Detached(cargo);
});
// Unlock carryable
carryall.CarryableReleased();
carryable.Dropped();
}
public override void Cancel(Actor self)
{
// TODO: Drop the unit at the nearest available cell
if (innerActivity != null)
innerActivity.Cancel(self);
base.Cancel(self);
}
}
}

View File

@@ -73,19 +73,19 @@ namespace OpenRA.Mods.Common.Activities
self.QueueActivity(mobile.MoveTo(moveTo, 1));
self.SetTargetLine(Target.FromCell(self.World, moveTo), Color.Gray, false);
// TODO: The harvest-deliver-return sequence is a horrible mess of duplicated code and edge-cases
var notify = self.TraitsImplementing<INotifyHarvesterAction>();
foreach (var n in notify)
n.MovingToResources(self, moveTo, this);
var randFrames = self.World.SharedRandom.Next(100, 175);
return ActivityUtils.SequenceActivities(NextActivity, new Wait(randFrames), this);
}
else
{
var next = this;
// Attempt to claim a resource as ours
if (territory != null)
{
if (!territory.ClaimResource(self, closestHarvestablePosition.Value))
return ActivityUtils.SequenceActivities(new Wait(25), next);
}
if (territory != null && !territory.ClaimResource(self, closestHarvestablePosition.Value))
return ActivityUtils.SequenceActivities(new Wait(25), this);
// If not given a direct order, assume ordered to the first resource location we find:
if (!harv.LastOrderLocation.HasValue)
@@ -93,12 +93,12 @@ namespace OpenRA.Mods.Common.Activities
self.SetTargetLine(Target.FromCell(self.World, closestHarvestablePosition.Value), Color.Red, false);
// TODO: The harvest-deliver-return sequence is a horrible mess of duplicated code and edge-cases
var notify = self.TraitsImplementing<INotifyHarvesterAction>();
foreach (var n in notify)
n.MovingToResources(self, closestHarvestablePosition.Value, next);
n.MovingToResources(self, closestHarvestablePosition.Value, this);
return ActivityUtils.SequenceActivities(mobile.MoveTo(closestHarvestablePosition.Value, 1), new HarvestResource(self), next);
return ActivityUtils.SequenceActivities(mobile.MoveTo(closestHarvestablePosition.Value, 1), new HarvestResource(self), this);
}
}

View File

@@ -19,87 +19,150 @@ namespace OpenRA.Mods.Common.Activities
{
readonly Actor cargo;
readonly IMove movement;
readonly Carryable carryable;
readonly Carryall carryall;
readonly Aircraft aircraft;
readonly IFacing cargoFacing;
readonly IFacing selfFacing;
enum State { Intercept, LockCarryable, MoveToCarryable, Turn, Pickup, TakeOff }
readonly Carryall carryall;
readonly IFacing carryallFacing;
readonly Carryable carryable;
readonly IFacing carryableFacing;
readonly BodyOrientation carryableBody;
readonly int delay;
enum State { Intercept, LockCarryable, MoveToCarryable, Turn, Land, Wait, Pickup, Aborted }
State state;
Activity innerActivity;
public PickupUnit(Actor self, Actor cargo)
public PickupUnit(Actor self, Actor cargo, int delay)
{
this.cargo = cargo;
this.delay = delay;
carryable = cargo.Trait<Carryable>();
cargoFacing = cargo.Trait<IFacing>();
carryableFacing = cargo.Trait<IFacing>();
carryableBody = cargo.Trait<BodyOrientation>();
movement = self.Trait<IMove>();
carryall = self.Trait<Carryall>();
aircraft = self.Trait<Aircraft>();
selfFacing = self.Trait<IFacing>();
carryallFacing = self.Trait<IFacing>();
state = State.Intercept;
}
public override Activity Tick(Actor self)
{
if (cargo.IsDead || !carryall.IsBusy)
if (innerActivity != null)
{
carryall.UnreserveCarryable();
innerActivity = ActivityUtils.RunActivity(self, innerActivity);
return this;
}
if (cargo != carryall.Carryable)
return NextActivity;
if (cargo.IsDead || IsCanceled)
{
carryall.UnreserveCarryable(self);
return NextActivity;
}
if (carryall.State == Carryall.CarryallState.Idle)
return NextActivity;
switch (state)
{
case State.Intercept:
innerActivity = movement.MoveWithinRange(Target.FromActor(cargo), WDist.FromCells(4));
state = State.LockCarryable;
return ActivityUtils.SequenceActivities(movement.MoveWithinRange(Target.FromActor(cargo), WDist.FromCells(4)), this);
return this;
case State.LockCarryable:
// Last check
if (carryable.StandbyForPickup(self))
state = State.MoveToCarryable;
if (!carryable.LockForPickup(cargo, self))
state = State.Aborted;
return this;
case State.MoveToCarryable:
{
// Line up with the attachment point
var localOffset = carryall.OffsetForCarryable(self, cargo).Rotate(carryableBody.QuantizeOrientation(self, cargo.Orientation));
var targetPosition = cargo.CenterPosition - carryableBody.LocalToWorld(localOffset);
if ((self.CenterPosition - targetPosition).HorizontalLengthSquared != 0)
{
// Run the first tick of the move activity immediately to avoid a one-frame pause
innerActivity = ActivityUtils.RunActivity(self, new HeliFly(self, Target.FromPos(targetPosition)));
return this;
}
state = State.Turn;
return this;
}
case State.Turn:
if (carryallFacing.Facing != carryableFacing.Facing)
{
innerActivity = new Turn(self, carryableFacing.Facing);
return this;
}
state = State.Land;
return this;
case State.Land:
{
var localOffset = carryall.OffsetForCarryable(self, cargo).Rotate(carryableBody.QuantizeOrientation(self, cargo.Orientation));
var targetPosition = cargo.CenterPosition - carryableBody.LocalToWorld(localOffset);
if ((self.CenterPosition - targetPosition).HorizontalLengthSquared != 0 || carryallFacing.Facing != carryableFacing.Facing)
{
state = State.MoveToCarryable;
return this;
}
// We got cancelled
carryall.UnreserveCarryable();
return NextActivity;
case State.MoveToCarryable: // We arrived, move on top
if (self.Location == cargo.Location)
if (targetPosition.Z != self.CenterPosition.Z)
{
state = State.Turn;
innerActivity = new HeliLand(self, false, self.World.Map.DistanceAboveTerrain(targetPosition));
return this;
}
return ActivityUtils.SequenceActivities(movement.MoveTo(cargo.Location, 0), this);
state = delay > 0 ? State.Wait : State.Pickup;
return this;
}
case State.Turn: // Align facing and Land
if (selfFacing.Facing != cargoFacing.Facing)
return ActivityUtils.SequenceActivities(new Turn(self, cargoFacing.Facing), this);
case State.Wait:
state = State.Pickup;
return ActivityUtils.SequenceActivities(new HeliLand(self, false), new Wait(10), this);
innerActivity = new Wait(delay, false);
return this;
case State.Pickup:
// Remove our carryable from world
self.World.AddFrameEndTask(w => cargo.World.Remove(cargo));
carryall.AttachCarryable(cargo);
state = State.TakeOff;
return this;
case State.TakeOff:
if (HeliFly.AdjustAltitude(self, aircraft, aircraft.Info.CruiseAltitude))
return this;
Attach(self);
return NextActivity;
case State.Aborted:
// We got cancelled
carryall.UnreserveCarryable(self);
break;
}
return NextActivity;
}
void Attach(Actor self)
{
self.World.AddFrameEndTask(w =>
{
cargo.World.Remove(cargo);
carryable.Attached(cargo);
carryall.AttachCarryable(self, cargo);
});
}
public override void Cancel(Actor self)
{
// TODO: Drop the unit at the nearest available cell
if (innerActivity != null)
innerActivity.Cancel(self);
base.Cancel(self);
}
}
}

View File

@@ -43,9 +43,6 @@ namespace OpenRA.Mods.Common.Activities
public override void Cancel(Actor self)
{
if (transportable != null)
transportable.WantsTransport = false;
if (inner != null)
inner.Cancel(self);
}