diff --git a/OpenRA.Mods.Common/Activities/Air/Fly.cs b/OpenRA.Mods.Common/Activities/Air/Fly.cs index 4831d06c84..acde3bace1 100644 --- a/OpenRA.Mods.Common/Activities/Air/Fly.cs +++ b/OpenRA.Mods.Common/Activities/Air/Fly.cs @@ -114,7 +114,8 @@ namespace OpenRA.Mods.Common.Activities // TODO: It would be better to not take off at all, but we lack the plumbing to detect current airborne/landed state. // If the aircraft lands when idle and is idle, we let the default idle handler manage this. // TODO: Remove this after fixing all activities to work properly with arbitrary starting altitudes. - var skipHeightAdjustment = aircraft.Info.LandWhenIdle && self.CurrentActivity.IsCanceling && self.CurrentActivity.NextActivity == null; + var landWhenIdle = aircraft.Info.IdleBehavior == IdleBehaviorType.Land; + var skipHeightAdjustment = landWhenIdle && self.CurrentActivity.IsCanceling && self.CurrentActivity.NextActivity == null; if (aircraft.Info.CanHover && !skipHeightAdjustment && dat != aircraft.Info.CruiseAltitude) { if (dat <= aircraft.LandAltitude) diff --git a/OpenRA.Mods.Common/Activities/Air/Land.cs b/OpenRA.Mods.Common/Activities/Air/Land.cs index 49b6ebc0e3..d70e0cc6a2 100644 --- a/OpenRA.Mods.Common/Activities/Air/Land.cs +++ b/OpenRA.Mods.Common/Activities/Air/Land.cs @@ -81,7 +81,8 @@ namespace OpenRA.Mods.Common.Activities // 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; + var shouldLand = aircraft.Info.IdleBehavior == IdleBehaviorType.Land; + var continueLanding = shouldLand && self.CurrentActivity.IsCanceling && self.CurrentActivity.NextActivity == null; if (!continueLanding) { var dat = self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition); diff --git a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs index 53234fe04f..422df5e97d 100644 --- a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs +++ b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs @@ -21,9 +21,21 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { + public enum IdleBehaviorType + { + None, + Land, + ReturnToBase, + LeaveMap, + } + public class AircraftInfo : ITraitInfo, IPositionableInfo, IFacingInfo, IMoveInfo, ICruiseAltitudeInfo, IActorPreviewInitInfo, IEditorActorOptions, IObservesVariablesInfo { + [Desc("Behavior when aircraft becomes idle. Options are Land, ReturnToBase, LeaveMap, and None.", + "'Land' will behave like 'None' (hover or circle) if a suitable landing site is not available.")] + public readonly IdleBehaviorType IdleBehavior = IdleBehaviorType.None; + public readonly WDist CruiseAltitude = new WDist(1280); [Desc("Whether the aircraft can be repulsed.")] @@ -78,9 +90,6 @@ namespace OpenRA.Mods.Common.Traits [Desc("Does the actor land and take off vertically?")] public readonly bool VTOL = false; - [Desc("Will this actor try to land after it has no more commands?")] - public readonly bool LandWhenIdle = true; - [Desc("Does this VTOL actor need to turn before landing (on terrain)?")] public readonly bool TurnToLand = false; @@ -365,10 +374,9 @@ namespace OpenRA.Mods.Common.Traits { ForceLanding = false; - if (!Info.LandWhenIdle) + if (Info.IdleBehavior != IdleBehaviorType.Land) { self.CancelActivity(); - self.QueueActivity(new TakeOff(self)); } } @@ -684,15 +692,31 @@ namespace OpenRA.Mods.Common.Traits } } - var isCircler = !Info.CanHover; - if (!atLandAltitude && Info.LandWhenIdle && Info.LandableTerrainTypes.Count > 0) - self.QueueActivity(new Land(self)); - else if (isCircler && !atLandAltitude) - self.QueueActivity(new FlyCircle(self, -1, Info.IdleTurnSpeed > -1 ? Info.IdleTurnSpeed : TurnSpeed)); - else if (atLandAltitude && !CanLand(self.Location) && ReservedActor == null) - self.QueueActivity(new TakeOff(self)); - else if (!atLandAltitude && altitude != Info.CruiseAltitude && !Info.LandWhenIdle) - self.QueueActivity(new TakeOff(self)); + if (Info.IdleBehavior == IdleBehaviorType.LeaveMap) + { + self.QueueActivity(new FlyOffMap(self)); + self.QueueActivity(new RemoveSelf()); + } + else if (Info.IdleBehavior == IdleBehaviorType.ReturnToBase && GetActorBelow() == null) + self.QueueActivity(new ReturnToBase(self, null, !Info.TakeOffOnResupply)); + else + { + if (atLandAltitude) + { + if (!CanLand(self.Location) && ReservedActor == null) + self.QueueActivity(new TakeOff(self)); + + // All remaining idle behaviors rely on not being atLandAltitude, so unconditionally return + return; + } + + if (Info.IdleBehavior != IdleBehaviorType.Land && altitude != Info.CruiseAltitude) + self.QueueActivity(new TakeOff(self)); + else if (Info.IdleBehavior == IdleBehaviorType.Land && Info.LandableTerrainTypes.Count > 0) + self.QueueActivity(new Land(self)); + else if (!Info.CanHover) + self.QueueActivity(new FlyCircle(self, -1, Info.IdleTurnSpeed > -1 ? Info.IdleTurnSpeed : TurnSpeed)); + } } #region Implement IPositionable diff --git a/OpenRA.Mods.Common/Traits/Air/FlyAwayOnIdle.cs b/OpenRA.Mods.Common/Traits/Air/FlyAwayOnIdle.cs deleted file mode 100644 index 394f78f1ea..0000000000 --- a/OpenRA.Mods.Common/Traits/Air/FlyAwayOnIdle.cs +++ /dev/null @@ -1,28 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2019 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, either version 3 of - * the License, or (at your option) any later version. For more - * information, see COPYING. - */ -#endregion - -using OpenRA.Mods.Common.Activities; -using OpenRA.Traits; - -namespace OpenRA.Mods.Common.Traits -{ - [Desc("Leave the map when idle.")] - class FlyAwayOnIdleInfo : TraitInfo { } - - class FlyAwayOnIdle : INotifyIdle - { - void INotifyIdle.TickIdle(Actor self) - { - self.QueueActivity(new FlyOffMap(self)); - self.QueueActivity(new RemoveSelf()); - } - } -} diff --git a/OpenRA.Mods.Common/Traits/Air/ReturnOnIdle.cs b/OpenRA.Mods.Common/Traits/Air/ReturnOnIdle.cs deleted file mode 100644 index 2800f4db70..0000000000 --- a/OpenRA.Mods.Common/Traits/Air/ReturnOnIdle.cs +++ /dev/null @@ -1,68 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2019 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, either version 3 of - * the License, or (at your option) any later version. For more - * information, see COPYING. - */ -#endregion - -using System.Linq; -using OpenRA.Mods.Common.Activities; -using OpenRA.Traits; - -namespace OpenRA.Mods.Common.Traits -{ - [Desc("Return to a player owned RearmActor. If none available, head back to base and circle over it.")] - public class ReturnOnIdleInfo : ITraitInfo, Requires - { - public object Create(ActorInitializer init) { return new ReturnOnIdle(init.Self, this); } - } - - public class ReturnOnIdle : INotifyIdle - { - readonly AircraftInfo aircraftInfo; - - public ReturnOnIdle(Actor self, ReturnOnIdleInfo info) - { - aircraftInfo = self.Info.TraitInfo(); - } - - void INotifyIdle.TickIdle(Actor self) - { - // We're on the ground, let's stay there. - if (self.World.Map.DistanceAboveTerrain(self.CenterPosition).Length < aircraftInfo.MinAirborneAltitude) - return; - - var resupplier = ReturnToBase.ChooseResupplier(self, true); - if (resupplier != null) - self.QueueActivity(new ReturnToBase(self, resupplier)); - else - { - // nowhere to land, pick something friendly and circle over it. - - // I'd prefer something we own - var someBuilding = self.World.ActorsHavingTrait() - .FirstOrDefault(a => a.Owner == self.Owner); - - // failing that, something unlikely to shoot at us - if (someBuilding == null) - someBuilding = self.World.ActorsHavingTrait() - .FirstOrDefault(a => self.Owner.Stances[a.Owner] == Stance.Ally); - - if (someBuilding == null) - { - // ... going down the garden to eat worms ... - self.QueueActivity(new FlyOffMap(self)); - self.QueueActivity(new RemoveSelf()); - return; - } - - self.QueueActivity(new Fly(self, Target.FromActor(someBuilding))); - self.QueueActivity(new FlyCircle(self)); - } - } - } -} diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20190314/AddAircraftIdleBehavior.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20190314/AddAircraftIdleBehavior.cs new file mode 100644 index 0000000000..edbf665548 --- /dev/null +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20190314/AddAircraftIdleBehavior.cs @@ -0,0 +1,83 @@ +#region Copyright & License Information +/* + * Copyright 2007-2019 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, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace OpenRA.Mods.Common.UpdateRules.Rules +{ + public class AddAircraftIdleBehavior : UpdateRule + { + public override string Name { get { return "Several aircraft traits and fields were replaced by Aircraft.IdleBehavior"; } } + public override string Description + { + get + { + return "ReturnOnIdle and FlyAwayOnIdle traits as well as LandWhenIdle boolean\n" + + "were replaced by Aircraft.IdleBehavior."; + } + } + + readonly List> returnOnIdles = new List>(); + + public override IEnumerable AfterUpdate(ModData modData) + { + var message = "ReturnOnIdle trait has been removed from the places listed below.\n" + + "Since this trait has been dysfunctional for a long time,\n" + + "IdleBehavior: ReturnToBase is NOT being set automatically.\n" + + "If you want your aircraft to return when idle, manually set it on the following definitions:\n" + + UpdateUtils.FormatMessageList(returnOnIdles.Select(n => n.Item1 + " (" + n.Item2 + ")")); + + if (returnOnIdles.Any()) + yield return message; + + returnOnIdles.Clear(); + } + + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + { + var aircraft = actorNode.LastChildMatching("Aircraft"); + var returnOnIdle = actorNode.LastChildMatching("ReturnOnIdle"); + var flyAwayOnIdle = actorNode.LastChildMatching("FlyAwayOnIdle"); + + if (aircraft != null) + { + var landWhenIdle = false; + var landWhenIdleNode = aircraft.LastChildMatching("LandWhenIdle"); + if (landWhenIdleNode != null) + { + landWhenIdle = landWhenIdleNode.NodeValue(); + aircraft.RemoveNode(landWhenIdleNode); + } + + // FlyAwayOnIdle should have had higher priority than LandWhenIdle even if both were 'true'. + // ReturnOnIdle has been broken for so long that it's safer to ignore it here and only inform + // the modder of the places it's been removed from, so they can change the IdleBehavior manually if desired. + if (flyAwayOnIdle != null && !flyAwayOnIdle.IsRemoval()) + aircraft.AddNode(new MiniYamlNode("IdleBehavior", "LeaveMap")); + else if (landWhenIdle) + aircraft.AddNode(new MiniYamlNode("IdleBehavior", "Land")); + } + + if (flyAwayOnIdle != null) + actorNode.RemoveNode(flyAwayOnIdle); + + if (returnOnIdle != null) + { + returnOnIdles.Add(Tuple.Create(actorNode.Key, actorNode.Location.Filename)); + actorNode.RemoveNode(returnOnIdle); + } + + yield break; + } + } +} diff --git a/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs b/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs index cf182a7092..bd4135638a 100644 --- a/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs +++ b/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs @@ -131,6 +131,7 @@ namespace OpenRA.Mods.Common.UpdateRules new AddAirAttackTypes(), new RenameCarryallDelays(), new AddCanSlide(), + new AddAircraftIdleBehavior(), }) }; diff --git a/mods/cnc/rules/aircraft.yaml b/mods/cnc/rules/aircraft.yaml index 66e48c42b5..4d171c5780 100644 --- a/mods/cnc/rules/aircraft.yaml +++ b/mods/cnc/rules/aircraft.yaml @@ -12,7 +12,6 @@ TRAN: Queue: Aircraft.GDI, Aircraft.Nod Description: Fast Infantry Transport Helicopter.\n Unarmed Aircraft: - LandWhenIdle: false TurnSpeed: 5 Speed: 150 AltitudeVelocity: 0c100 diff --git a/mods/cnc/rules/defaults.yaml b/mods/cnc/rules/defaults.yaml index 42ab742be6..9cbb2ad101 100644 --- a/mods/cnc/rules/defaults.yaml +++ b/mods/cnc/rules/defaults.yaml @@ -322,7 +322,6 @@ Repairable: RepairActors: hpad Aircraft: - LandWhenIdle: false AirborneCondition: airborne CruisingCondition: cruising CanHover: True @@ -659,10 +658,10 @@ Offset: 43, 128, 0 ZOffset: -129 WithFacingSpriteBody: - FlyAwayOnIdle: RejectsOrders: Aircraft: CruiseAltitude: 2560 + IdleBehavior: LeaveMap MapEditorData: Categories: Aircraft diff --git a/mods/d2k/rules/aircraft.yaml b/mods/d2k/rules/aircraft.yaml index 09075d8d0f..53d9d25c74 100644 --- a/mods/d2k/rules/aircraft.yaml +++ b/mods/d2k/rules/aircraft.yaml @@ -16,7 +16,6 @@ carryall.reinforce: TurnSpeed: 4 LandableTerrainTypes: Sand, Rock, Transition, Spice, SpiceSand, Dune, Concrete Repulsable: False - LandWhenIdle: False AirborneCondition: airborne CanSlide: True VTOL: true @@ -77,6 +76,7 @@ frigate: Tooltip: Name: Frigate Aircraft: + IdleBehavior: LeaveMap Speed: 189 TurnSpeed: 1 Repulsable: False @@ -89,7 +89,6 @@ frigate: Cargo: MaxWeight: 20 PipCount: 10 - FlyAwayOnIdle: RejectsOrders: ornithopter: diff --git a/mods/ra/rules/aircraft.yaml b/mods/ra/rules/aircraft.yaml index e0f73800c0..2dc07a0f10 100644 --- a/mods/ra/rules/aircraft.yaml +++ b/mods/ra/rules/aircraft.yaml @@ -124,7 +124,6 @@ MIG: AmmoPool: Ammo: 8 AmmoCondition: ammo - ReturnOnIdle: Selectable: Bounds: 36,28,0,2 DecorationBounds: 40,29,0,1 @@ -196,7 +195,6 @@ YAK: PipCount: 6 ReloadDelay: 11 AmmoCondition: ammo - ReturnOnIdle: SelectionDecorations: WithMuzzleOverlay: Contrail: @@ -237,7 +235,6 @@ TRAN: Range: 6c0 Type: GroundPosition Aircraft: - LandWhenIdle: false TurnSpeed: 5 Speed: 128 AltitudeVelocity: 0c58 @@ -305,7 +302,6 @@ HELI: PersistentTargeting: false AttackType: Hover Aircraft: - LandWhenIdle: false TurnSpeed: 4 Speed: 149 AutoTarget: @@ -374,7 +370,6 @@ HIND: PersistentTargeting: false AttackType: Hover Aircraft: - LandWhenIdle: false TurnSpeed: 4 Speed: 112 AutoTarget: @@ -475,7 +470,6 @@ MH60: PersistentTargeting: false AttackType: Hover Aircraft: - LandWhenIdle: false TurnSpeed: 4 Speed: 112 AutoTarget: diff --git a/mods/ts/rules/aircraft.yaml b/mods/ts/rules/aircraft.yaml index b39c3d3eb2..435da3a7bb 100644 --- a/mods/ts/rules/aircraft.yaml +++ b/mods/ts/rules/aircraft.yaml @@ -6,7 +6,7 @@ DPOD: Tooltip: Name: Drop Pod Aircraft: - LandWhenIdle: true + IdleBehavior: Land TurnSpeed: 5 Speed: 149 InitialFacing: 0 @@ -40,7 +40,7 @@ DSHP: UpdatesPlayerStatistics: AddToArmyValue: true Aircraft: - LandWhenIdle: true + IdleBehavior: Land TurnSpeed: 5 Speed: 168 InitialFacing: 0 @@ -147,7 +147,6 @@ ORCAB: LandingSounds: orcadwn1.aud CanHover: false CanSlide: false - ReturnOnIdle: Health: HP: 26000 Armor: @@ -193,7 +192,6 @@ ORCATRAN: Prerequisites: ~disabled RenderSprites: Aircraft: - LandWhenIdle: false TurnSpeed: 5 Speed: 84 InitialFacing: 0 @@ -285,7 +283,6 @@ SCRIN: LandingSounds: dropdwn1.aud CanHover: false CanSlide: false - ReturnOnIdle: Health: HP: 28000 Armor: diff --git a/mods/ts/rules/defaults.yaml b/mods/ts/rules/defaults.yaml index 243a4cb5ae..5d337deabe 100644 --- a/mods/ts/rules/defaults.yaml +++ b/mods/ts/rules/defaults.yaml @@ -875,7 +875,6 @@ CruisingCondition: cruising CruiseAltitude: 4c704 AltitudeVelocity: 96 - LandWhenIdle: false Voice: Move IdealSeparation: 853 MaximumPitch: 120