diff --git a/OpenRA.Mods.Cnc/Traits/Buildings/ProductionAirdrop.cs b/OpenRA.Mods.Cnc/Traits/Buildings/ProductionAirdrop.cs index 801796198b..ca697103d4 100644 --- a/OpenRA.Mods.Cnc/Traits/Buildings/ProductionAirdrop.cs +++ b/OpenRA.Mods.Cnc/Traits/Buildings/ProductionAirdrop.cs @@ -72,8 +72,8 @@ namespace OpenRA.Mods.Cnc.Traits new FacingInit(64) }); - actor.QueueActivity(new Fly(actor, Target.FromPos(self.CenterPosition + new WVec(landDistance, 0, 0)))); - actor.QueueActivity(new Land(actor, Target.FromActor(self))); + var exitCell = self.Location + exit.ExitCell; + actor.QueueActivity(new Land(actor, Target.FromActor(self), WDist.Zero, WVec.Zero, 64, clearCells: new CPos[1] { exitCell })); actor.QueueActivity(new CallFunc(() => { if (!self.IsInWorld || self.IsDead) diff --git a/OpenRA.Mods.Common/Activities/Air/Land.cs b/OpenRA.Mods.Common/Activities/Air/Land.cs index bcff084153..8ee26a2841 100644 --- a/OpenRA.Mods.Common/Activities/Air/Land.cs +++ b/OpenRA.Mods.Common/Activities/Air/Land.cs @@ -9,6 +9,8 @@ */ #endregion +using System; +using System.Collections.Generic; using OpenRA.Activities; using OpenRA.Mods.Common.Traits; using OpenRA.Traits; @@ -17,25 +19,57 @@ namespace OpenRA.Mods.Common.Activities { public class Land : Activity { - readonly Target target; readonly Aircraft aircraft; readonly WVec offset; + readonly int desiredFacing; + readonly bool assignTargetOnFirstRun; + readonly CPos[] clearCells; + readonly WDist landRange; + Target target; + WPos targetPosition; + CPos landingCell; 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; - aircraft = self.Trait(); - this.offset = offset; + assignTargetOnFirstRun = true; } - public Land(Actor self, Target t) - : this(self, t, WVec.Zero) { } + public Land(Actor self, Target target, int facing = -1) + : this(self, target, new WDist(-1), WVec.Zero, facing) { } - public Land(Actor self) - : this(self, Target.FromPos(Aircraft.GroundPosition(self)), WVec.Zero) { } + public Land(Actor self, Target target, WDist landRange, int facing = -1) + : 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(); + 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) { @@ -48,73 +82,177 @@ namespace OpenRA.Mods.Common.Activities if (IsCanceling || target.Type == TargetType.Invalid) { - // We must return the actor to a sensible height before continuing. - // If the aircraft lands when idle and is idle, continue landing, - // otherwise climb back to CruiseAltitude. - // TODO: Remove this after fixing all activities to work properly with arbitrary starting altitudes. - var continueLanding = aircraft.Info.LandWhenIdle && self.CurrentActivity.IsCanceling && self.CurrentActivity.NextActivity == null; - if (!continueLanding) + if (landingInitiated) { - var dat = self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition); - if (dat > aircraft.LandAltitude && dat < aircraft.Info.CruiseAltitude) + // We must return the actor to a sensible height before continuing. + // If the aircraft lands when idle and is idle, continue landing, + // otherwise climb back to CruiseAltitude. + // TODO: Remove this after fixing all activities to work properly with arbitrary starting altitudes. + var continueLanding = aircraft.Info.LandWhenIdle && self.CurrentActivity.IsCanceling && self.CurrentActivity.NextActivity == null; + if (!continueLanding) { - QueueChild(self, new TakeOff(self), true); - return this; - } + var dat = self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition); + if (dat > aircraft.LandAltitude && dat < aircraft.Info.CruiseAltitude) + { + QueueChild(self, new TakeOff(self), true); + return this; + } - aircraft.RemoveInfluence(); - return NextActivity; + aircraft.RemoveInfluence(); + 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) { - var landingCell = !aircraft.Info.VTOL ? self.World.Map.CellContaining(target.CenterPosition + offset) : self.Location; - if (!aircraft.CanLand(landingCell, target.Actor)) + var blockingCells = clearCells.Append(landingCell); + + if (!aircraft.CanLand(blockingCells, target.Actor)) { // 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); - self.NotifyBlocker(landingCell); + self.NotifyBlocker(blockingCells); + finishedApproach = false; return this; } + if (aircraft.Info.LandingSounds.Length > 0) + Game.Sound.Play(SoundType.World, aircraft.Info.LandingSounds, self.World, aircraft.CenterPosition); + aircraft.AddInfluence(landingCell); aircraft.EnteringCell(self); landingInitiated = true; } - var altitude = self.World.Map.DistanceAboveTerrain(self.CenterPosition); - 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 + // Final descent. if (aircraft.Info.VTOL) { + var landAltitude = self.World.Map.DistanceAboveTerrain(targetPosition) + aircraft.LandAltitude; if (Fly.VerticalTakeOffOrLandTick(self, aircraft, aircraft.Facing, landAltitude)) return this; return NextActivity; } - var d = (target.CenterPosition + offset) - self.CenterPosition; + var d = targetPosition - pos; // The next move would overshoot, so just set the final position var move = aircraft.FlyStep(aircraft.Facing); if (d.HorizontalLengthSquared < move.HorizontalLengthSquared) { var landingAltVec = new WVec(WDist.Zero, WDist.Zero, aircraft.LandAltitude); - aircraft.SetPosition(self, target.CenterPosition + offset + landingAltVec); + aircraft.SetPosition(self, targetPosition + landingAltVec); 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); return this; diff --git a/OpenRA.Mods.Common/Activities/Air/ReturnToBase.cs b/OpenRA.Mods.Common/Activities/Air/ReturnToBase.cs index d42f0adb47..3db7924221 100644 --- a/OpenRA.Mods.Common/Activities/Air/ReturnToBase.cs +++ b/OpenRA.Mods.Common/Activities/Air/ReturnToBase.cs @@ -26,10 +26,9 @@ namespace OpenRA.Mods.Common.Activities readonly Rearmable rearmable; readonly bool alwaysLand; readonly bool abortOnResupply; - bool isCalculated; bool resupplied; Actor dest; - WPos w1, w2, w3; + int facing = -1; public ReturnToBase(Actor self, bool abortOnResupply, Actor dest = null, bool alwaysLand = true) { @@ -55,59 +54,6 @@ namespace OpenRA.Mods.Common.Activities .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) { if (alwaysLand) @@ -148,10 +94,7 @@ namespace OpenRA.Mods.Common.Activities return NextActivity; if (dest == null || dest.IsDead || !Reservable.IsAvailableFor(dest, self)) - dest = ReturnToBase.ChooseResupplier(self, true); - - if (!isCalculated) - Calculate(self); + dest = ChooseResupplier(self, true); if (dest == null) { @@ -175,62 +118,35 @@ namespace OpenRA.Mods.Common.Activities return this; } - else - { - QueueChild(self, - new Fly(self, Target.FromActor(nearestResupplier), WDist.Zero, aircraft.Info.WaitDistanceFromResupplyBase, targetLineColor: Color.Green), + + 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); - return this; - } + QueueChild(self, new FlyCircle(self, aircraft.Info.NumberOfTicksToVerifyAvailableAirport), true); + 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. - Cancel(self); - 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); + // Prevent an infinite loop in case we'd return to the activity that called ReturnToBase in the first place. Go idle instead. + Cancel(self); + return NextActivity; } 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); - - 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 Land(self, Target.FromActor(dest), offset, facing), 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; } } diff --git a/OpenRA.Mods.Common/Scripting/Global/ReinforcementsGlobal.cs b/OpenRA.Mods.Common/Scripting/Global/ReinforcementsGlobal.cs index bca8dadb00..7a023d3a1c 100644 --- a/OpenRA.Mods.Common/Scripting/Global/ReinforcementsGlobal.cs +++ b/OpenRA.Mods.Common/Scripting/Global/ReinforcementsGlobal.cs @@ -177,19 +177,7 @@ namespace OpenRA.Mods.Common.Scripting } } - if (aircraft.Info.VTOL) - { - 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 Land(transport, Target.FromCell(transport.World, destination), facing: aircraft.Info.InitialFacing)); transport.QueueActivity(new Wait(15)); } diff --git a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs index 9cbc1a34ad..4c4341250d 100644 --- a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs +++ b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs @@ -96,6 +96,9 @@ namespace OpenRA.Mods.Common.Traits [Desc("Altitude at which the aircraft considers itself landed.")] 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.")] public readonly WAngle MaximumPitch = WAngle.FromDegrees(10); @@ -200,7 +203,7 @@ namespace OpenRA.Mods.Common.Traits public Actor ReservedActor { get; private set; } public bool MayYieldReservation { get; private set; } public bool ForceLanding { get; private set; } - CPos? landingCell; + IEnumerable landingCells = Enumerable.Empty(); bool requireForceMove; public WDist LandAltitude { get; private set; } @@ -336,12 +339,7 @@ namespace OpenRA.Mods.Common.Traits && !((self.CurrentActivity is Land) || self.CurrentActivity is Turn)) { self.CancelActivity(); - - if (Info.VTOL && Info.TurnToLand) - self.QueueActivity(new Turn(self, Info.InitialFacing)); - self.QueueActivity(new Land(self)); - ForceLanding = true; } @@ -532,12 +530,7 @@ namespace OpenRA.Mods.Common.Traits public Pair[] OccupiedCells() { if (!self.IsAtGroundLevel()) - { - if (landingCell.HasValue) - return new[] { Pair.New(landingCell.Value, SubCell.FullCell) }; - - return NoCells; - } + return landingCells.Select(c => Pair.New(c, SubCell.FullCell)).ToArray(); return new[] { Pair.New(TopLeft, SubCell.FullCell) }; } @@ -553,29 +546,65 @@ namespace OpenRA.Mods.Common.Traits 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 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)) return false; foreach (var otherActor in self.World.ActorMap.GetActorsAt(cell)) - if (IsBlockedBy(self, otherActor, ignoreActor)) + if (IsBlockedBy(self, otherActor, dockingActor, blockedByMobile)) return false; - foreach (var otherActor in self.World.ActorMap.GetActorsAt(cell)) - if (AircraftCanEnter(otherActor)) - return true; + // Terrain type is ignored when docking with an actor + if (dockingActor != null) + return true; var type = self.World.Map.GetTerrainInfo(cell).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. if (otherActor == self || otherActor == ignoreActor) 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() != null && otherActor.CurrentActivity == null) + return false; + // PERF: Only perform ITemporaryBlocker trait look-up if mod/map rules contain any actors that are temporary blockers if (self.World.RulesContainTemporaryBlocker) { @@ -638,12 +667,7 @@ namespace OpenRA.Mods.Common.Traits } if (!atLandAltitude && Info.LandWhenIdle) - { - if (Info.VTOL && Info.TurnToLand) - self.QueueActivity(new Turn(self, Info.InitialFacing)); - self.QueueActivity(new Land(self)); - } else if (!Info.CanHover && !atLandAltitude) self.QueueActivity(new FlyCircle(self, -1, Info.IdleTurnSpeed > -1 ? Info.IdleTurnSpeed : TurnSpeed)); 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); } + public void AddInfluence(IEnumerable landingCells) + { + this.landingCells = landingCells; + if (self.IsInWorld) + self.World.ActorMap.AddInfluence(self, this); + } + public void AddInfluence(CPos landingCell) { - this.landingCell = landingCell; + landingCells = new List { landingCell }; if (self.IsInWorld) self.World.ActorMap.AddInfluence(self, this); } @@ -753,7 +784,7 @@ namespace OpenRA.Mods.Common.Traits if (self.IsInWorld) self.World.ActorMap.RemoveInfluence(self, this); - landingCell = null; + landingCells = Enumerable.Empty(); } #endregion diff --git a/mods/cnc/rules/aircraft.yaml b/mods/cnc/rules/aircraft.yaml index 0386222aa6..fcf75557bc 100644 --- a/mods/cnc/rules/aircraft.yaml +++ b/mods/cnc/rules/aircraft.yaml @@ -183,7 +183,6 @@ C17: Speed: 326 Repulsable: False MaximumPitch: 36 - LandableTerrainTypes: Clear, Rough, Road, Beach HiddenUnderFog: AlwaysVisibleStances: None Type: CenterPosition diff --git a/mods/d2k/rules/aircraft.yaml b/mods/d2k/rules/aircraft.yaml index 8b0f03f9ea..4d9505664b 100644 --- a/mods/d2k/rules/aircraft.yaml +++ b/mods/d2k/rules/aircraft.yaml @@ -82,7 +82,6 @@ frigate: Repulsable: False MaximumPitch: 20 CruiseAltitude: 2048 - LandableTerrainTypes: Rock, Concrete -AppearsOnRadar: Cargo: MaxWeight: 20