Overhaul Land activity:
- Landing on an actor is no longer blocked by the underlying terrain - Land in a nearby cell if the requested location is blocked - Internally manages the fixed-wing landing sequence - ProductionAirdrop transport waits until the exit is free before landing
This commit is contained in:
@@ -72,8 +72,8 @@ namespace OpenRA.Mods.Cnc.Traits
|
|||||||
new FacingInit(64)
|
new FacingInit(64)
|
||||||
});
|
});
|
||||||
|
|
||||||
actor.QueueActivity(new Fly(actor, Target.FromPos(self.CenterPosition + new WVec(landDistance, 0, 0))));
|
var exitCell = self.Location + exit.ExitCell;
|
||||||
actor.QueueActivity(new Land(actor, Target.FromActor(self)));
|
actor.QueueActivity(new Land(actor, Target.FromActor(self), WDist.Zero, WVec.Zero, 64, clearCells: new CPos[1] { exitCell }));
|
||||||
actor.QueueActivity(new CallFunc(() =>
|
actor.QueueActivity(new CallFunc(() =>
|
||||||
{
|
{
|
||||||
if (!self.IsInWorld || self.IsDead)
|
if (!self.IsInWorld || self.IsDead)
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using OpenRA.Activities;
|
using OpenRA.Activities;
|
||||||
using OpenRA.Mods.Common.Traits;
|
using OpenRA.Mods.Common.Traits;
|
||||||
using OpenRA.Traits;
|
using OpenRA.Traits;
|
||||||
@@ -17,25 +19,57 @@ namespace OpenRA.Mods.Common.Activities
|
|||||||
{
|
{
|
||||||
public class Land : Activity
|
public class Land : Activity
|
||||||
{
|
{
|
||||||
readonly Target target;
|
|
||||||
readonly Aircraft aircraft;
|
readonly Aircraft aircraft;
|
||||||
readonly WVec offset;
|
readonly WVec offset;
|
||||||
|
readonly int desiredFacing;
|
||||||
|
readonly bool assignTargetOnFirstRun;
|
||||||
|
readonly CPos[] clearCells;
|
||||||
|
readonly WDist landRange;
|
||||||
|
|
||||||
|
Target target;
|
||||||
|
WPos targetPosition;
|
||||||
|
CPos landingCell;
|
||||||
bool landingInitiated;
|
bool landingInitiated;
|
||||||
bool soundPlayed;
|
bool finishedApproach;
|
||||||
|
|
||||||
public Land(Actor self, Target t, WVec offset)
|
public Land(Actor self, int facing = -1)
|
||||||
|
: this(self, Target.Invalid, new WDist(-1), WVec.Zero, facing, null)
|
||||||
{
|
{
|
||||||
target = t;
|
assignTargetOnFirstRun = true;
|
||||||
aircraft = self.Trait<Aircraft>();
|
|
||||||
this.offset = offset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Land(Actor self, Target t)
|
public Land(Actor self, Target target, int facing = -1)
|
||||||
: this(self, t, WVec.Zero) { }
|
: this(self, target, new WDist(-1), WVec.Zero, facing) { }
|
||||||
|
|
||||||
public Land(Actor self)
|
public Land(Actor self, Target target, WDist landRange, int facing = -1)
|
||||||
: this(self, Target.FromPos(Aircraft.GroundPosition(self)), WVec.Zero) { }
|
: this(self, target, landRange, WVec.Zero, facing) { }
|
||||||
|
|
||||||
|
public Land(Actor self, Target target, WVec offset, int facing = -1)
|
||||||
|
: this(self, target, WDist.Zero, offset, facing) { }
|
||||||
|
|
||||||
|
public Land(Actor self, Target target, WDist landRange, WVec offset, int facing = -1, CPos[] clearCells = null)
|
||||||
|
{
|
||||||
|
aircraft = self.Trait<Aircraft>();
|
||||||
|
this.target = target;
|
||||||
|
this.offset = offset;
|
||||||
|
this.clearCells = clearCells ?? new CPos[0];
|
||||||
|
this.landRange = landRange.Length >= 0 ? landRange : aircraft.Info.LandRange;
|
||||||
|
|
||||||
|
// NOTE: desiredFacing = -1 means we should not prefer any particular facing and instead just
|
||||||
|
// use whatever facing gives us the most direct path to the landing site.
|
||||||
|
if (facing == -1 && aircraft.Info.TurnToLand)
|
||||||
|
desiredFacing = aircraft.Info.InitialFacing;
|
||||||
|
else
|
||||||
|
desiredFacing = facing;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFirstRun(Actor self)
|
||||||
|
{
|
||||||
|
// When no target is provided we should land in the most direct manner possible.
|
||||||
|
// TODO: For fixed-wing aircraft self.Location is not necessarily the most direct landing site.
|
||||||
|
if (assignTargetOnFirstRun)
|
||||||
|
target = Target.FromCell(self.World, self.Location);
|
||||||
|
}
|
||||||
|
|
||||||
public override Activity Tick(Actor self)
|
public override Activity Tick(Actor self)
|
||||||
{
|
{
|
||||||
@@ -47,6 +81,8 @@ namespace OpenRA.Mods.Common.Activities
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (IsCanceling || target.Type == TargetType.Invalid)
|
if (IsCanceling || target.Type == TargetType.Invalid)
|
||||||
|
{
|
||||||
|
if (landingInitiated)
|
||||||
{
|
{
|
||||||
// We must return the actor to a sensible height before continuing.
|
// We must return the actor to a sensible height before continuing.
|
||||||
// If the aircraft lands when idle and is idle, continue landing,
|
// If the aircraft lands when idle and is idle, continue landing,
|
||||||
@@ -66,55 +102,157 @@ namespace OpenRA.Mods.Common.Activities
|
|||||||
return NextActivity;
|
return NextActivity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
return NextActivity;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pos = aircraft.CenterPosition;
|
||||||
|
|
||||||
|
// Reevaluate target position in case the target has moved.
|
||||||
|
targetPosition = target.CenterPosition + offset;
|
||||||
|
landingCell = self.World.Map.CellContaining(targetPosition);
|
||||||
|
|
||||||
|
// We are already at the landing location.
|
||||||
|
if ((targetPosition - pos).LengthSquared == 0)
|
||||||
|
return NextActivity;
|
||||||
|
|
||||||
|
// Look for free landing cell
|
||||||
|
if (target.Type == TargetType.Terrain && !landingInitiated)
|
||||||
|
{
|
||||||
|
var targetLocation = aircraft.FindLandingLocation(landingCell, landRange);
|
||||||
|
if (!targetLocation.HasValue)
|
||||||
|
{
|
||||||
|
// Maintain holding pattern.
|
||||||
|
if (aircraft.Info.CanHover)
|
||||||
|
QueueChild(self, new Wait(25), true);
|
||||||
|
else
|
||||||
|
QueueChild(self, new FlyCircle(self, 25), true);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
target = Target.FromCell(self.World, targetLocation.Value);
|
||||||
|
targetPosition = target.CenterPosition + offset;
|
||||||
|
landingCell = self.World.Map.CellContaining(targetPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move towards landing location
|
||||||
|
if (aircraft.Info.VTOL && (pos - targetPosition).HorizontalLengthSquared != 0)
|
||||||
|
{
|
||||||
|
QueueChild(self, new Fly(self, Target.FromPos(targetPosition)), true);
|
||||||
|
|
||||||
|
if (desiredFacing != -1)
|
||||||
|
QueueChild(self, new Turn(self, desiredFacing));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!aircraft.Info.VTOL && !finishedApproach)
|
||||||
|
{
|
||||||
|
// Calculate approach trajectory
|
||||||
|
var altitude = aircraft.Info.CruiseAltitude.Length;
|
||||||
|
|
||||||
|
// Distance required for descent.
|
||||||
|
var landDistance = altitude * 1024 / aircraft.Info.MaximumPitch.Tan();
|
||||||
|
|
||||||
|
// Approach landing from the opposite direction of the desired facing
|
||||||
|
// TODO: Calculate sensible trajectory without preferred facing.
|
||||||
|
var rotation = WRot.Zero;
|
||||||
|
if (desiredFacing != -1)
|
||||||
|
rotation = WRot.FromFacing(desiredFacing);
|
||||||
|
|
||||||
|
var approachStart = targetPosition + new WVec(0, landDistance, altitude).Rotate(rotation);
|
||||||
|
|
||||||
|
// Add 10% to the turning radius to ensure we have enough room
|
||||||
|
var speed = aircraft.MovementSpeed * 32 / 35;
|
||||||
|
var turnRadius = Fly.CalculateTurnRadius(speed, aircraft.Info.TurnSpeed);
|
||||||
|
|
||||||
|
// Find the center of the turning circles for clockwise and counterclockwise turns
|
||||||
|
var angle = WAngle.FromFacing(aircraft.Facing);
|
||||||
|
var fwd = -new WVec(angle.Sin(), angle.Cos(), 0);
|
||||||
|
|
||||||
|
// Work out whether we should turn clockwise or counter-clockwise for approach
|
||||||
|
var side = new WVec(-fwd.Y, fwd.X, fwd.Z);
|
||||||
|
var approachDelta = self.CenterPosition - approachStart;
|
||||||
|
var sideTowardBase = new[] { side, -side }
|
||||||
|
.MinBy(a => WVec.Dot(a, approachDelta));
|
||||||
|
|
||||||
|
// Calculate the tangent line that joins the turning circles at the current and approach positions
|
||||||
|
var cp = self.CenterPosition + turnRadius * sideTowardBase / 1024;
|
||||||
|
var posCenter = new WPos(cp.X, cp.Y, altitude);
|
||||||
|
var approachCenter = approachStart + new WVec(0, turnRadius * Math.Sign(self.CenterPosition.Y - approachStart.Y), 0);
|
||||||
|
var tangentDirection = approachCenter - posCenter;
|
||||||
|
var tangentLength = tangentDirection.Length;
|
||||||
|
var tangentOffset = WVec.Zero;
|
||||||
|
if (tangentLength != 0)
|
||||||
|
tangentOffset = new WVec(-tangentDirection.Y, tangentDirection.X, 0) * turnRadius / tangentLength;
|
||||||
|
|
||||||
|
// TODO: correctly handle CCW <-> CW turns
|
||||||
|
if (tangentOffset.X > 0)
|
||||||
|
tangentOffset = -tangentOffset;
|
||||||
|
|
||||||
|
var w1 = posCenter + tangentOffset;
|
||||||
|
var w2 = approachCenter + tangentOffset;
|
||||||
|
var w3 = approachStart;
|
||||||
|
|
||||||
|
turnRadius = Fly.CalculateTurnRadius(aircraft.Info.Speed, aircraft.Info.TurnSpeed);
|
||||||
|
|
||||||
|
// Move along approach trajectory.
|
||||||
|
QueueChild(self, new Fly(self, Target.FromPos(w1), WDist.Zero, new WDist(turnRadius * 3)), true);
|
||||||
|
QueueChild(self, new Fly(self, Target.FromPos(w2)), true);
|
||||||
|
|
||||||
|
// Fix a problem when the airplane is sent to land near the landing cell
|
||||||
|
QueueChild(self, new Fly(self, Target.FromPos(w3), WDist.Zero, new WDist(turnRadius / 2)), true);
|
||||||
|
finishedApproach = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
if (!landingInitiated)
|
if (!landingInitiated)
|
||||||
{
|
{
|
||||||
var landingCell = !aircraft.Info.VTOL ? self.World.Map.CellContaining(target.CenterPosition + offset) : self.Location;
|
var blockingCells = clearCells.Append(landingCell);
|
||||||
if (!aircraft.CanLand(landingCell, target.Actor))
|
|
||||||
|
if (!aircraft.CanLand(blockingCells, target.Actor))
|
||||||
{
|
{
|
||||||
// Maintain holding pattern.
|
// Maintain holding pattern.
|
||||||
if (!aircraft.Info.CanHover)
|
if (aircraft.Info.CanHover)
|
||||||
|
QueueChild(self, new Wait(25), true);
|
||||||
|
else
|
||||||
QueueChild(self, new FlyCircle(self, 25), true);
|
QueueChild(self, new FlyCircle(self, 25), true);
|
||||||
|
|
||||||
self.NotifyBlocker(landingCell);
|
self.NotifyBlocker(blockingCells);
|
||||||
|
finishedApproach = false;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (aircraft.Info.LandingSounds.Length > 0)
|
||||||
|
Game.Sound.Play(SoundType.World, aircraft.Info.LandingSounds, self.World, aircraft.CenterPosition);
|
||||||
|
|
||||||
aircraft.AddInfluence(landingCell);
|
aircraft.AddInfluence(landingCell);
|
||||||
aircraft.EnteringCell(self);
|
aircraft.EnteringCell(self);
|
||||||
landingInitiated = true;
|
landingInitiated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var altitude = self.World.Map.DistanceAboveTerrain(self.CenterPosition);
|
// Final descent.
|
||||||
var landAltitude = self.World.Map.DistanceAboveTerrain(target.CenterPosition + offset) + aircraft.LandAltitude;
|
|
||||||
|
|
||||||
if (!soundPlayed && aircraft.Info.LandingSounds.Length > 0 && altitude != landAltitude)
|
|
||||||
{
|
|
||||||
Game.Sound.Play(SoundType.World, aircraft.Info.LandingSounds, self.World, aircraft.CenterPosition);
|
|
||||||
soundPlayed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For VTOLs we assume we've already arrived at the target location and just need to move downward
|
|
||||||
if (aircraft.Info.VTOL)
|
if (aircraft.Info.VTOL)
|
||||||
{
|
{
|
||||||
|
var landAltitude = self.World.Map.DistanceAboveTerrain(targetPosition) + aircraft.LandAltitude;
|
||||||
if (Fly.VerticalTakeOffOrLandTick(self, aircraft, aircraft.Facing, landAltitude))
|
if (Fly.VerticalTakeOffOrLandTick(self, aircraft, aircraft.Facing, landAltitude))
|
||||||
return this;
|
return this;
|
||||||
|
|
||||||
return NextActivity;
|
return NextActivity;
|
||||||
}
|
}
|
||||||
|
|
||||||
var d = (target.CenterPosition + offset) - self.CenterPosition;
|
var d = targetPosition - pos;
|
||||||
|
|
||||||
// The next move would overshoot, so just set the final position
|
// The next move would overshoot, so just set the final position
|
||||||
var move = aircraft.FlyStep(aircraft.Facing);
|
var move = aircraft.FlyStep(aircraft.Facing);
|
||||||
if (d.HorizontalLengthSquared < move.HorizontalLengthSquared)
|
if (d.HorizontalLengthSquared < move.HorizontalLengthSquared)
|
||||||
{
|
{
|
||||||
var landingAltVec = new WVec(WDist.Zero, WDist.Zero, aircraft.LandAltitude);
|
var landingAltVec = new WVec(WDist.Zero, WDist.Zero, aircraft.LandAltitude);
|
||||||
aircraft.SetPosition(self, target.CenterPosition + offset + landingAltVec);
|
aircraft.SetPosition(self, targetPosition + landingAltVec);
|
||||||
return NextActivity;
|
return NextActivity;
|
||||||
}
|
}
|
||||||
|
|
||||||
var landingAlt = self.World.Map.DistanceAboveTerrain(target.CenterPosition + offset) + aircraft.LandAltitude;
|
var landingAlt = self.World.Map.DistanceAboveTerrain(targetPosition) + aircraft.LandAltitude;
|
||||||
Fly.FlyTick(self, aircraft, d.Yaw.Facing, landingAlt);
|
Fly.FlyTick(self, aircraft, d.Yaw.Facing, landingAlt);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@@ -26,10 +26,9 @@ namespace OpenRA.Mods.Common.Activities
|
|||||||
readonly Rearmable rearmable;
|
readonly Rearmable rearmable;
|
||||||
readonly bool alwaysLand;
|
readonly bool alwaysLand;
|
||||||
readonly bool abortOnResupply;
|
readonly bool abortOnResupply;
|
||||||
bool isCalculated;
|
|
||||||
bool resupplied;
|
bool resupplied;
|
||||||
Actor dest;
|
Actor dest;
|
||||||
WPos w1, w2, w3;
|
int facing = -1;
|
||||||
|
|
||||||
public ReturnToBase(Actor self, bool abortOnResupply, Actor dest = null, bool alwaysLand = true)
|
public ReturnToBase(Actor self, bool abortOnResupply, Actor dest = null, bool alwaysLand = true)
|
||||||
{
|
{
|
||||||
@@ -55,59 +54,6 @@ namespace OpenRA.Mods.Common.Activities
|
|||||||
.ClosestTo(self);
|
.ClosestTo(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculates non-CanHover/non-VTOL approach vector and waypoints
|
|
||||||
void Calculate(Actor self)
|
|
||||||
{
|
|
||||||
if (dest == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var exit = dest.FirstExitOrDefault(null);
|
|
||||||
var offset = exit != null ? exit.Info.SpawnOffset : WVec.Zero;
|
|
||||||
|
|
||||||
var landPos = dest.CenterPosition + offset;
|
|
||||||
var altitude = aircraft.Info.CruiseAltitude.Length;
|
|
||||||
|
|
||||||
// Distance required for descent.
|
|
||||||
var landDistance = altitude * 1024 / aircraft.Info.MaximumPitch.Tan();
|
|
||||||
|
|
||||||
// Land towards the east
|
|
||||||
var approachStart = landPos + new WVec(-landDistance, 0, altitude);
|
|
||||||
|
|
||||||
// Add 10% to the turning radius to ensure we have enough room
|
|
||||||
var speed = aircraft.MovementSpeed * 32 / 35;
|
|
||||||
var turnRadius = Fly.CalculateTurnRadius(speed, aircraft.Info.TurnSpeed);
|
|
||||||
|
|
||||||
// Find the center of the turning circles for clockwise and counterclockwise turns
|
|
||||||
var angle = WAngle.FromFacing(aircraft.Facing);
|
|
||||||
var fwd = -new WVec(angle.Sin(), angle.Cos(), 0);
|
|
||||||
|
|
||||||
// Work out whether we should turn clockwise or counter-clockwise for approach
|
|
||||||
var side = new WVec(-fwd.Y, fwd.X, fwd.Z);
|
|
||||||
var approachDelta = self.CenterPosition - approachStart;
|
|
||||||
var sideTowardBase = new[] { side, -side }
|
|
||||||
.MinBy(a => WVec.Dot(a, approachDelta));
|
|
||||||
|
|
||||||
// Calculate the tangent line that joins the turning circles at the current and approach positions
|
|
||||||
var cp = self.CenterPosition + turnRadius * sideTowardBase / 1024;
|
|
||||||
var posCenter = new WPos(cp.X, cp.Y, altitude);
|
|
||||||
var approachCenter = approachStart + new WVec(0, turnRadius * Math.Sign(self.CenterPosition.Y - approachStart.Y), 0);
|
|
||||||
var tangentDirection = approachCenter - posCenter;
|
|
||||||
var tangentLength = tangentDirection.Length;
|
|
||||||
var tangentOffset = WVec.Zero;
|
|
||||||
if (tangentLength != 0)
|
|
||||||
tangentOffset = new WVec(-tangentDirection.Y, tangentDirection.X, 0) * turnRadius / tangentLength;
|
|
||||||
|
|
||||||
// TODO: correctly handle CCW <-> CW turns
|
|
||||||
if (tangentOffset.X > 0)
|
|
||||||
tangentOffset = -tangentOffset;
|
|
||||||
|
|
||||||
w1 = posCenter + tangentOffset;
|
|
||||||
w2 = approachCenter + tangentOffset;
|
|
||||||
w3 = approachStart;
|
|
||||||
|
|
||||||
isCalculated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShouldLandAtBuilding(Actor self, Actor dest)
|
bool ShouldLandAtBuilding(Actor self, Actor dest)
|
||||||
{
|
{
|
||||||
if (alwaysLand)
|
if (alwaysLand)
|
||||||
@@ -148,10 +94,7 @@ namespace OpenRA.Mods.Common.Activities
|
|||||||
return NextActivity;
|
return NextActivity;
|
||||||
|
|
||||||
if (dest == null || dest.IsDead || !Reservable.IsAvailableFor(dest, self))
|
if (dest == null || dest.IsDead || !Reservable.IsAvailableFor(dest, self))
|
||||||
dest = ReturnToBase.ChooseResupplier(self, true);
|
dest = ChooseResupplier(self, true);
|
||||||
|
|
||||||
if (!isCalculated)
|
|
||||||
Calculate(self);
|
|
||||||
|
|
||||||
if (dest == null)
|
if (dest == null)
|
||||||
{
|
{
|
||||||
@@ -175,62 +118,35 @@ namespace OpenRA.Mods.Common.Activities
|
|||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
QueueChild(self,
|
|
||||||
new Fly(self, Target.FromActor(nearestResupplier), WDist.Zero, aircraft.Info.WaitDistanceFromResupplyBase, targetLineColor: Color.Green),
|
|
||||||
true);
|
|
||||||
|
|
||||||
|
QueueChild(self, new Fly(self, Target.FromActor(nearestResupplier), WDist.Zero, aircraft.Info.WaitDistanceFromResupplyBase, targetLineColor: Color.Green),
|
||||||
|
true);
|
||||||
QueueChild(self, new FlyCircle(self, aircraft.Info.NumberOfTicksToVerifyAvailableAirport), true);
|
QueueChild(self, new FlyCircle(self, aircraft.Info.NumberOfTicksToVerifyAvailableAirport), true);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (nearestResupplier == null && aircraft.Info.VTOL && aircraft.Info.LandWhenIdle)
|
|
||||||
{
|
|
||||||
// Using Queue instead of QueueChild here is intentional, as we want VTOLs with LandWhenIdle to land and stay there in this situation
|
|
||||||
Cancel(self);
|
|
||||||
if (aircraft.Info.TurnToLand)
|
|
||||||
Queue(self, new Turn(self, aircraft.Info.InitialFacing));
|
|
||||||
|
|
||||||
Queue(self, new Land(self));
|
|
||||||
return NextActivity;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Prevent an infinite loop in case we'd return to the activity that called ReturnToBase in the first place. Go idle instead.
|
// Prevent an infinite loop in case we'd return to the activity that called ReturnToBase in the first place. Go idle instead.
|
||||||
Cancel(self);
|
Cancel(self);
|
||||||
return NextActivity;
|
return NextActivity;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var exit = dest.FirstExitOrDefault(null);
|
|
||||||
var offset = exit != null ? exit.Info.SpawnOffset : WVec.Zero;
|
|
||||||
|
|
||||||
if (aircraft.Info.VTOL || aircraft.Info.CanHover)
|
|
||||||
QueueChild(self, new Fly(self, Target.FromPos(dest.CenterPosition + offset)), true);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var turnRadius = Fly.CalculateTurnRadius(aircraft.Info.Speed, aircraft.Info.TurnSpeed);
|
|
||||||
|
|
||||||
QueueChild(self, new Fly(self, Target.FromPos(w1), WDist.Zero, new WDist(turnRadius * 3)), true);
|
|
||||||
QueueChild(self, new Fly(self, Target.FromPos(w2)), true);
|
|
||||||
|
|
||||||
// Fix a problem when the airplane is sent to resupply near the airport
|
|
||||||
QueueChild(self, new Fly(self, Target.FromPos(w3), WDist.Zero, new WDist(turnRadius / 2)), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ShouldLandAtBuilding(self, dest))
|
if (ShouldLandAtBuilding(self, dest))
|
||||||
{
|
{
|
||||||
|
var exit = dest.FirstExitOrDefault(null);
|
||||||
|
var offset = exit != null ? exit.Info.SpawnOffset : WVec.Zero;
|
||||||
|
if (aircraft.Info.TurnToDock)
|
||||||
|
facing = aircraft.Info.InitialFacing;
|
||||||
|
if (!aircraft.Info.VTOL)
|
||||||
|
facing = 192;
|
||||||
|
|
||||||
aircraft.MakeReservation(dest);
|
aircraft.MakeReservation(dest);
|
||||||
|
QueueChild(self, new Land(self, Target.FromActor(dest), offset, facing), true);
|
||||||
if (aircraft.Info.VTOL && aircraft.Info.TurnToDock)
|
|
||||||
QueueChild(self, new Turn(self, aircraft.Info.InitialFacing), true);
|
|
||||||
|
|
||||||
QueueChild(self, new Land(self, Target.FromActor(dest), offset), true);
|
|
||||||
QueueChild(self, new Resupply(self, dest, WDist.Zero), true);
|
QueueChild(self, new Resupply(self, dest, WDist.Zero), true);
|
||||||
resupplied = true;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
QueueChild(self, new Fly(self, Target.FromActor(dest)), true);
|
||||||
|
|
||||||
|
resupplied = true;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,19 +177,7 @@ namespace OpenRA.Mods.Common.Scripting
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aircraft.Info.VTOL)
|
transport.QueueActivity(new Land(transport, Target.FromCell(transport.World, destination), facing: aircraft.Info.InitialFacing));
|
||||||
{
|
|
||||||
if (destination != entryPath.Last())
|
|
||||||
Move(transport, destination);
|
|
||||||
|
|
||||||
transport.QueueActivity(new Turn(transport, aircraft.Info.InitialFacing));
|
|
||||||
transport.QueueActivity(new Land(transport));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
transport.QueueActivity(new Land(transport, Target.FromCell(transport.World, destination)));
|
|
||||||
}
|
|
||||||
|
|
||||||
transport.QueueActivity(new Wait(15));
|
transport.QueueActivity(new Wait(15));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,9 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
[Desc("Altitude at which the aircraft considers itself landed.")]
|
[Desc("Altitude at which the aircraft considers itself landed.")]
|
||||||
public readonly WDist LandAltitude = WDist.Zero;
|
public readonly WDist LandAltitude = WDist.Zero;
|
||||||
|
|
||||||
|
[Desc("Range to search for an alternative landing location if the ordered cell is blocked.")]
|
||||||
|
public readonly WDist LandRange = WDist.FromCells(5);
|
||||||
|
|
||||||
[Desc("How fast this actor ascends or descends during horizontal movement.")]
|
[Desc("How fast this actor ascends or descends during horizontal movement.")]
|
||||||
public readonly WAngle MaximumPitch = WAngle.FromDegrees(10);
|
public readonly WAngle MaximumPitch = WAngle.FromDegrees(10);
|
||||||
|
|
||||||
@@ -200,7 +203,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
public Actor ReservedActor { get; private set; }
|
public Actor ReservedActor { get; private set; }
|
||||||
public bool MayYieldReservation { get; private set; }
|
public bool MayYieldReservation { get; private set; }
|
||||||
public bool ForceLanding { get; private set; }
|
public bool ForceLanding { get; private set; }
|
||||||
CPos? landingCell;
|
IEnumerable<CPos> landingCells = Enumerable.Empty<CPos>();
|
||||||
bool requireForceMove;
|
bool requireForceMove;
|
||||||
|
|
||||||
public WDist LandAltitude { get; private set; }
|
public WDist LandAltitude { get; private set; }
|
||||||
@@ -336,12 +339,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
&& !((self.CurrentActivity is Land) || self.CurrentActivity is Turn))
|
&& !((self.CurrentActivity is Land) || self.CurrentActivity is Turn))
|
||||||
{
|
{
|
||||||
self.CancelActivity();
|
self.CancelActivity();
|
||||||
|
|
||||||
if (Info.VTOL && Info.TurnToLand)
|
|
||||||
self.QueueActivity(new Turn(self, Info.InitialFacing));
|
|
||||||
|
|
||||||
self.QueueActivity(new Land(self));
|
self.QueueActivity(new Land(self));
|
||||||
|
|
||||||
ForceLanding = true;
|
ForceLanding = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,12 +530,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
public Pair<CPos, SubCell>[] OccupiedCells()
|
public Pair<CPos, SubCell>[] OccupiedCells()
|
||||||
{
|
{
|
||||||
if (!self.IsAtGroundLevel())
|
if (!self.IsAtGroundLevel())
|
||||||
{
|
return landingCells.Select(c => Pair.New(c, SubCell.FullCell)).ToArray();
|
||||||
if (landingCell.HasValue)
|
|
||||||
return new[] { Pair.New(landingCell.Value, SubCell.FullCell) };
|
|
||||||
|
|
||||||
return NoCells;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new[] { Pair.New(TopLeft, SubCell.FullCell) };
|
return new[] { Pair.New(TopLeft, SubCell.FullCell) };
|
||||||
}
|
}
|
||||||
@@ -553,29 +546,65 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
return speed * dir / 1024;
|
return speed * dir / 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanLand(CPos cell, Actor ignoreActor = null)
|
public CPos? FindLandingLocation(CPos targetCell, WDist maxSearchDistance)
|
||||||
|
{
|
||||||
|
// The easy case
|
||||||
|
if (CanLand(targetCell, blockedByMobile: false))
|
||||||
|
return targetCell;
|
||||||
|
|
||||||
|
var cellRange = (maxSearchDistance.Length + 1023) / 1024;
|
||||||
|
var centerPosition = self.World.Map.CenterOfCell(targetCell);
|
||||||
|
foreach (var c in self.World.Map.FindTilesInCircle(targetCell, cellRange))
|
||||||
|
{
|
||||||
|
if (!CanLand(c, blockedByMobile: false))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var delta = self.World.Map.CenterOfCell(c) - centerPosition;
|
||||||
|
if (delta.LengthSquared < maxSearchDistance.LengthSquared)
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanLand(IEnumerable<CPos> cells, Actor dockingActor = null, bool blockedByMobile = true)
|
||||||
|
{
|
||||||
|
foreach (var c in cells)
|
||||||
|
if (!CanLand(c, dockingActor, blockedByMobile))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanLand(CPos cell, Actor dockingActor = null, bool blockedByMobile = true)
|
||||||
{
|
{
|
||||||
if (!self.World.Map.Contains(cell))
|
if (!self.World.Map.Contains(cell))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
foreach (var otherActor in self.World.ActorMap.GetActorsAt(cell))
|
foreach (var otherActor in self.World.ActorMap.GetActorsAt(cell))
|
||||||
if (IsBlockedBy(self, otherActor, ignoreActor))
|
if (IsBlockedBy(self, otherActor, dockingActor, blockedByMobile))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
foreach (var otherActor in self.World.ActorMap.GetActorsAt(cell))
|
// Terrain type is ignored when docking with an actor
|
||||||
if (AircraftCanEnter(otherActor))
|
if (dockingActor != null)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
var type = self.World.Map.GetTerrainInfo(cell).Type;
|
var type = self.World.Map.GetTerrainInfo(cell).Type;
|
||||||
return Info.LandableTerrainTypes.Contains(type);
|
return Info.LandableTerrainTypes.Contains(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsBlockedBy(Actor self, Actor otherActor, Actor ignoreActor)
|
bool IsBlockedBy(Actor self, Actor otherActor, Actor ignoreActor, bool blockedByMobile = true)
|
||||||
{
|
{
|
||||||
// We are not blocked by the actor we are ignoring.
|
// We are not blocked by the actor we are ignoring.
|
||||||
if (otherActor == self || otherActor == ignoreActor)
|
if (otherActor == self || otherActor == ignoreActor)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// We are not blocked by actors we can nudge out of the way
|
||||||
|
// TODO: Generalize blocker checks and handling here and in Locomotor
|
||||||
|
if (!blockedByMobile && self.Owner.Stances[otherActor.Owner] == Stance.Ally &&
|
||||||
|
otherActor.TraitOrDefault<Mobile>() != null && otherActor.CurrentActivity == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
// PERF: Only perform ITemporaryBlocker trait look-up if mod/map rules contain any actors that are temporary blockers
|
// PERF: Only perform ITemporaryBlocker trait look-up if mod/map rules contain any actors that are temporary blockers
|
||||||
if (self.World.RulesContainTemporaryBlocker)
|
if (self.World.RulesContainTemporaryBlocker)
|
||||||
{
|
{
|
||||||
@@ -638,12 +667,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!atLandAltitude && Info.LandWhenIdle)
|
if (!atLandAltitude && Info.LandWhenIdle)
|
||||||
{
|
|
||||||
if (Info.VTOL && Info.TurnToLand)
|
|
||||||
self.QueueActivity(new Turn(self, Info.InitialFacing));
|
|
||||||
|
|
||||||
self.QueueActivity(new Land(self));
|
self.QueueActivity(new Land(self));
|
||||||
}
|
|
||||||
else if (!Info.CanHover && !atLandAltitude)
|
else if (!Info.CanHover && !atLandAltitude)
|
||||||
self.QueueActivity(new FlyCircle(self, -1, Info.IdleTurnSpeed > -1 ? Info.IdleTurnSpeed : TurnSpeed));
|
self.QueueActivity(new FlyCircle(self, -1, Info.IdleTurnSpeed > -1 ? Info.IdleTurnSpeed : TurnSpeed));
|
||||||
else if (atLandAltitude && !CanLand(self.Location) && ReservedActor == null)
|
else if (atLandAltitude && !CanLand(self.Location) && ReservedActor == null)
|
||||||
@@ -741,9 +765,16 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
notifyCrushed.Trait.WarnCrush(notifyCrushed.Actor, self, Info.Crushes);
|
notifyCrushed.Trait.WarnCrush(notifyCrushed.Actor, self, Info.Crushes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddInfluence(IEnumerable<CPos> landingCells)
|
||||||
|
{
|
||||||
|
this.landingCells = landingCells;
|
||||||
|
if (self.IsInWorld)
|
||||||
|
self.World.ActorMap.AddInfluence(self, this);
|
||||||
|
}
|
||||||
|
|
||||||
public void AddInfluence(CPos landingCell)
|
public void AddInfluence(CPos landingCell)
|
||||||
{
|
{
|
||||||
this.landingCell = landingCell;
|
landingCells = new List<CPos> { landingCell };
|
||||||
if (self.IsInWorld)
|
if (self.IsInWorld)
|
||||||
self.World.ActorMap.AddInfluence(self, this);
|
self.World.ActorMap.AddInfluence(self, this);
|
||||||
}
|
}
|
||||||
@@ -753,7 +784,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
if (self.IsInWorld)
|
if (self.IsInWorld)
|
||||||
self.World.ActorMap.RemoveInfluence(self, this);
|
self.World.ActorMap.RemoveInfluence(self, this);
|
||||||
|
|
||||||
landingCell = null;
|
landingCells = Enumerable.Empty<CPos>();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -183,7 +183,6 @@ C17:
|
|||||||
Speed: 326
|
Speed: 326
|
||||||
Repulsable: False
|
Repulsable: False
|
||||||
MaximumPitch: 36
|
MaximumPitch: 36
|
||||||
LandableTerrainTypes: Clear, Rough, Road, Beach
|
|
||||||
HiddenUnderFog:
|
HiddenUnderFog:
|
||||||
AlwaysVisibleStances: None
|
AlwaysVisibleStances: None
|
||||||
Type: CenterPosition
|
Type: CenterPosition
|
||||||
|
|||||||
@@ -82,7 +82,6 @@ frigate:
|
|||||||
Repulsable: False
|
Repulsable: False
|
||||||
MaximumPitch: 20
|
MaximumPitch: 20
|
||||||
CruiseAltitude: 2048
|
CruiseAltitude: 2048
|
||||||
LandableTerrainTypes: Rock, Concrete
|
|
||||||
-AppearsOnRadar:
|
-AppearsOnRadar:
|
||||||
Cargo:
|
Cargo:
|
||||||
MaxWeight: 20
|
MaxWeight: 20
|
||||||
|
|||||||
Reference in New Issue
Block a user