diff --git a/OpenRA.Mods.Common/Activities/DeliverUnit.cs b/OpenRA.Mods.Common/Activities/DeliverUnit.cs index bcfa8a310c..7325098974 100644 --- a/OpenRA.Mods.Common/Activities/DeliverUnit.cs +++ b/OpenRA.Mods.Common/Activities/DeliverUnit.cs @@ -45,7 +45,7 @@ namespace OpenRA.Mods.Common.Activities destination = Target.FromCell(self.World, self.Location); QueueChild(self, new Land(self, destination, deliverRange), true); - QueueChild(self, new Wait(carryall.Info.UnloadingDelay, false), true); + QueueChild(self, new Wait(carryall.Info.BeforeUnloadDelay, false), true); QueueChild(self, new ReleaseUnit(self)); QueueChild(self, new TakeOff(self)); } diff --git a/OpenRA.Mods.Common/Activities/EnterTransport.cs b/OpenRA.Mods.Common/Activities/EnterTransport.cs index 6f803d2fa3..c8a65e42ac 100644 --- a/OpenRA.Mods.Common/Activities/EnterTransport.cs +++ b/OpenRA.Mods.Common/Activities/EnterTransport.cs @@ -24,6 +24,7 @@ namespace OpenRA.Mods.Common.Activities Actor enterActor; Cargo enterCargo; + Aircraft enterAircraft; public EnterTransport(Actor self, Target target) : base(self, target, Color.Green) @@ -35,6 +36,7 @@ namespace OpenRA.Mods.Common.Activities { enterActor = targetActor; enterCargo = targetActor.TraitOrDefault(); + enterAircraft = targetActor.TraitOrDefault(); // Make sure we can still enter the transport // (but not before, because this may stop the actor in the middle of nowhere) @@ -44,6 +46,9 @@ namespace OpenRA.Mods.Common.Activities return false; } + if (enterAircraft != null && !enterAircraft.AtLandAltitude) + return false; + return true; } diff --git a/OpenRA.Mods.Common/Activities/UnloadCargo.cs b/OpenRA.Mods.Common/Activities/UnloadCargo.cs index f54401b8bc..00902f28a4 100644 --- a/OpenRA.Mods.Common/Activities/UnloadCargo.cs +++ b/OpenRA.Mods.Common/Activities/UnloadCargo.cs @@ -24,13 +24,27 @@ namespace OpenRA.Mods.Common.Activities readonly Cargo cargo; readonly INotifyUnload[] notifiers; readonly bool unloadAll; + readonly Aircraft aircraft; + readonly bool assignTargetOnFirstRun; + readonly WDist unloadRange; - public UnloadCargo(Actor self, bool unloadAll) + Target destination; + + public UnloadCargo(Actor self, WDist unloadRange, bool unloadAll = true) + : this(self, Target.Invalid, unloadRange, unloadAll) + { + assignTargetOnFirstRun = true; + } + + public UnloadCargo(Actor self, Target destination, WDist unloadRange, bool unloadAll = true) { this.self = self; cargo = self.Trait(); notifiers = self.TraitsImplementing().ToArray(); this.unloadAll = unloadAll; + aircraft = self.TraitOrDefault(); + this.destination = destination; + this.unloadRange = unloadRange; } public Pair? ChooseExitSubCell(Actor passenger) @@ -53,6 +67,23 @@ namespace OpenRA.Mods.Common.Activities .Where(c => pos.CanEnterCell(c, null, true) != pos.CanEnterCell(c, null, false)); } + protected override void OnFirstRun(Actor self) + { + if (assignTargetOnFirstRun) + destination = Target.FromCell(self.World, self.Location); + + // Move to the target destination + if (aircraft != null) + QueueChild(self, new Land(self, destination, unloadRange)); + else + { + var cell = self.World.Map.Clamp(this.self.World.Map.CellContaining(destination.CenterPosition)); + QueueChild(self, new Move(self, cell, unloadRange)); + } + + QueueChild(self, new Wait(cargo.Info.BeforeUnloadDelay)); + } + public override Activity Tick(Actor self) { if (ChildActivity != null) @@ -62,50 +93,52 @@ namespace OpenRA.Mods.Common.Activities return this; } - cargo.Unloading = false; if (IsCanceling || cargo.IsEmpty(self)) return NextActivity; - if (!cargo.CanUnload()) + if (cargo.CanUnload()) + { + foreach (var inu in notifiers) + inu.Unloading(self); + + var actor = cargo.Peek(self); + var spawn = self.CenterPosition; + + var exitSubCell = ChooseExitSubCell(actor); + if (exitSubCell == null) + { + self.NotifyBlocker(BlockedExitCells(actor)); + QueueChild(self, new Wait(10), true); + return this; + } + + cargo.Unload(self); + self.World.AddFrameEndTask(w => + { + if (actor.Disposed) + return; + + var move = actor.Trait(); + var pos = actor.Trait(); + + actor.CancelActivity(); + pos.SetVisualPosition(actor, spawn); + actor.QueueActivity(move.MoveIntoWorld(actor, exitSubCell.Value.First, exitSubCell.Value.Second)); + actor.SetTargetLine(Target.FromCell(w, exitSubCell.Value.First, exitSubCell.Value.Second), Color.Green, false); + w.Add(actor); + }); + } + + if (!unloadAll || !cargo.CanUnload()) { Cancel(self, true); - return NextActivity; + if (cargo.Info.AfterUnloadDelay > 0) + QueueChild(self, new Wait(cargo.Info.AfterUnloadDelay, false), true); + + if (aircraft != null && !aircraft.Info.LandWhenIdle) + QueueChild(self, new TakeOff(self), true); } - foreach (var inu in notifiers) - inu.Unloading(self); - - var actor = cargo.Peek(self); - var spawn = self.CenterPosition; - - var exitSubCell = ChooseExitSubCell(actor); - if (exitSubCell == null) - { - self.NotifyBlocker(BlockedExitCells(actor)); - QueueChild(self, new Wait(10), true); - return this; - } - - cargo.Unload(self); - self.World.AddFrameEndTask(w => - { - if (actor.Disposed) - return; - - var move = actor.Trait(); - var pos = actor.Trait(); - - actor.CancelActivity(); - pos.SetVisualPosition(actor, spawn); - actor.QueueActivity(move.MoveIntoWorld(actor, exitSubCell.Value.First, exitSubCell.Value.Second)); - actor.SetTargetLine(Target.FromCell(w, exitSubCell.Value.First, exitSubCell.Value.Second), Color.Green, false); - w.Add(actor); - }); - - if (!unloadAll || cargo.IsEmpty(self)) - return NextActivity; - - cargo.Unloading = true; return this; } } diff --git a/OpenRA.Mods.Common/Scripting/Global/ReinforcementsGlobal.cs b/OpenRA.Mods.Common/Scripting/Global/ReinforcementsGlobal.cs index 7a023d3a1c..7f41f6c224 100644 --- a/OpenRA.Mods.Common/Scripting/Global/ReinforcementsGlobal.cs +++ b/OpenRA.Mods.Common/Scripting/Global/ReinforcementsGlobal.cs @@ -150,44 +150,14 @@ namespace OpenRA.Mods.Common.Scripting else { var aircraft = transport.TraitOrDefault(); + + // Scripted cargo aircraft must turn to default position before unloading. + // TODO: pass facing through UnloadCargo instead. if (aircraft != null) - { - var destination = entryPath.Last(); - - // Try to find an alternative landing spot if we can't land at the current destination - if (!aircraft.CanLand(destination) && dropRange > 0) - { - var locomotors = cargo.Passengers - .Select(a => a.Info.TraitInfoOrDefault()) - .Where(m => m != null) - .Distinct() - .Select(m => m.LocomotorInfo) - .ToList(); - - foreach (var c in transport.World.Map.FindTilesInCircle(destination, dropRange)) - { - if (!aircraft.CanLand(c)) - continue; - - if (!locomotors.All(m => domainIndex.IsPassable(destination, c, m))) - continue; - - destination = c; - break; - } - } - - transport.QueueActivity(new Land(transport, Target.FromCell(transport.World, destination), facing: aircraft.Info.InitialFacing)); - transport.QueueActivity(new Wait(15)); - } + transport.QueueActivity(new Land(transport, Target.FromCell(transport.World, entryPath.Last()), WDist.FromCells(dropRange), aircraft.Info.InitialFacing)); if (cargo != null) - { - transport.QueueActivity(new UnloadCargo(transport, true)); - transport.QueueActivity(new WaitFor(() => cargo.IsEmpty(transport))); - } - - transport.QueueActivity(new Wait(aircraft != null ? 50 : 25)); + transport.QueueActivity(new UnloadCargo(transport, WDist.FromCells(dropRange))); } if (exitFunc != null) diff --git a/OpenRA.Mods.Common/Scripting/Properties/TransportProperties.cs b/OpenRA.Mods.Common/Scripting/Properties/TransportProperties.cs index 82324a693b..bf27ac1d40 100644 --- a/OpenRA.Mods.Common/Scripting/Properties/TransportProperties.cs +++ b/OpenRA.Mods.Common/Scripting/Properties/TransportProperties.cs @@ -42,9 +42,15 @@ namespace OpenRA.Mods.Common.Scripting [ScriptActorPropertyActivity] [Desc("Command transport to unload passengers.")] - public void UnloadPassengers() + public void UnloadPassengers(CPos? cell = null, int unloadRange = 5) { - Self.QueueActivity(new UnloadCargo(Self, true)); + if (cell.HasValue) + { + var destination = Target.FromCell(Self.World, cell.Value); + Self.QueueActivity(new UnloadCargo(Self, destination, WDist.FromCells(unloadRange))); + } + else + Self.QueueActivity(new UnloadCargo(Self, WDist.FromCells(unloadRange))); } } } diff --git a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs index b7b5eacc42..272835d24b 100644 --- a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs +++ b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs @@ -664,7 +664,8 @@ namespace OpenRA.Mods.Common.Traits protected virtual void OnBecomingIdle(Actor self) { - var atLandAltitude = self.World.Map.DistanceAboveTerrain(CenterPosition) == LandAltitude; + var altitude = self.World.Map.DistanceAboveTerrain(CenterPosition); + var atLandAltitude = altitude == LandAltitude; // Work-around to prevent players from accidentally canceling resupply by pressing 'Stop', // by re-queueing Resupply as long as resupply hasn't finished and aircraft is still on resupplier. @@ -692,6 +693,8 @@ namespace OpenRA.Mods.Common.Traits // Will go away soon (in a separate PR) with the arrival of ActionsWhenIdle. self.QueueActivity(new FlyCircle(self, -1, Info.IdleTurnSpeed > -1 ? Info.IdleTurnSpeed : TurnSpeed)); } + else if (!atLandAltitude && altitude != Info.CruiseAltitude && !Info.LandWhenIdle) + self.QueueActivity(new TakeOff(self)); } #region Implement IPositionable diff --git a/OpenRA.Mods.Common/Traits/Cargo.cs b/OpenRA.Mods.Common/Traits/Cargo.cs index 2ffbed323d..5ad050599a 100644 --- a/OpenRA.Mods.Common/Traits/Cargo.cs +++ b/OpenRA.Mods.Common/Traits/Cargo.cs @@ -47,9 +47,21 @@ namespace OpenRA.Mods.Common.Traits [Desc("Voice to play when ordered to unload the passengers.")] public readonly string UnloadVoice = "Action"; + [Desc("Radius to search for a load/unload location if the ordered cell is blocked.")] + public readonly WDist LoadRange = WDist.FromCells(5); + [Desc("Which direction the passenger will face (relative to the transport) when unloading.")] public readonly int PassengerFacing = 128; + [Desc("Delay (in ticks) before continuing after loading a passenger.")] + public readonly int AfterLoadDelay = 8; + + [Desc("Delay (in ticks) before unloading the first passenger.")] + public readonly int BeforeUnloadDelay = 8; + + [Desc("Delay (in ticks) before continuing after unloading a passenger.")] + public readonly int AfterUnloadDelay = 25; + [Desc("Cursor to display when able to unload the passengers.")] public readonly string UnloadCursor = "deploy"; @@ -96,15 +108,16 @@ namespace OpenRA.Mods.Common.Traits CPos currentCell; public IEnumerable CurrentAdjacentCells { get; private set; } - public bool Unloading { get; internal set; } public IEnumerable Passengers { get { return cargo; } } public int PassengerCount { get { return cargo.Count; } } + enum State { Free, Locked } + State state = State.Free; + public Cargo(ActorInitializer init, CargoInfo info) { self = init.Self; Info = info; - Unloading = false; checkTerrainType = info.UnloadTerrainTypes.Count > 0; if (init.Contains()) @@ -195,11 +208,7 @@ namespace OpenRA.Mods.Common.Traits if (!order.Queued) self.CancelActivity(); - Unloading = true; - if (aircraft != null) - self.QueueActivity(new Land(self)); - - self.QueueActivity(new UnloadCargo(self, true)); + self.QueueActivity(new UnloadCargo(self, Info.LoadRange)); } } @@ -218,13 +227,13 @@ namespace OpenRA.Mods.Common.Traits return false; } - return !IsEmpty(self) && (aircraft == null || aircraft.CanLand(self.Location)) + return !IsEmpty(self) && (aircraft == null || aircraft.CanLand(self.Location, blockedByMobile: false)) && CurrentAdjacentCells != null && CurrentAdjacentCells.Any(c => Passengers.Any(p => p.Trait().CanEnterCell(c, null, immediate))); } public bool CanLoad(Actor self, Actor a) { - return (reserves.Contains(a) || HasSpace(GetWeight(a))) && self.IsAtGroundLevel(); + return reserves.Contains(a) || HasSpace(GetWeight(a)); } internal bool ReserveSpace(Actor a) @@ -241,6 +250,7 @@ namespace OpenRA.Mods.Common.Traits reserves.Add(a); reservedWeight += w; + LockForPickup(self); return true; } @@ -252,11 +262,43 @@ namespace OpenRA.Mods.Common.Traits reservedWeight -= GetWeight(a); reserves.Remove(a); + ReleaseLock(self); if (loadingToken != ConditionManager.InvalidConditionToken) loadingToken = conditionManager.RevokeCondition(self, loadingToken); } + // Prepare for transport pickup + bool LockForPickup(Actor self) + { + if (state == State.Locked) + return false; + + state = State.Locked; + + self.CancelActivity(); + + var air = self.TraitOrDefault(); + if (air != null && !air.AtLandAltitude) + self.QueueActivity(new Land(self)); + + self.QueueActivity(new WaitFor(() => state != State.Locked, false)); + return true; + } + + void ReleaseLock(Actor self) + { + if (reservedWeight != 0) + return; + + state = State.Free; + + self.QueueActivity(new Wait(Info.AfterLoadDelay, false)); + var air = self.TraitOrDefault(); + if (air != null) + self.QueueActivity(new TakeOff(self)); + } + public string CursorForOrder(Actor self, Order order) { if (order.OrderString != "Unload") @@ -353,6 +395,7 @@ namespace OpenRA.Mods.Common.Traits { reservedWeight -= w; reserves.Remove(a); + ReleaseLock(self); if (loadingToken != ConditionManager.InvalidConditionToken) loadingToken = conditionManager.RevokeCondition(self, loadingToken); diff --git a/OpenRA.Mods.Common/Traits/Carryall.cs b/OpenRA.Mods.Common/Traits/Carryall.cs index d328723e71..3c2727b592 100644 --- a/OpenRA.Mods.Common/Traits/Carryall.cs +++ b/OpenRA.Mods.Common/Traits/Carryall.cs @@ -23,11 +23,11 @@ namespace OpenRA.Mods.Common.Traits [Desc("Transports actors with the `Carryable` trait.")] public class CarryallInfo : ITraitInfo, Requires, Requires { - [Desc("Delay on the ground while attaching an actor to the carryall.")] - public readonly int LoadingDelay = 0; + [Desc("Delay (in ticks) on the ground while attaching an actor to the carryall.")] + public readonly int BeforeLoadDelay = 0; - [Desc("Delay on the ground while detacting an actor to the carryall.")] - public readonly int UnloadingDelay = 0; + [Desc("Delay (in ticks) on the ground while detaching an actor from the carryall.")] + public readonly int BeforeUnloadDelay = 0; [Desc("Carryable attachment point relative to body.")] public readonly WVec LocalOffset = WVec.Zero; @@ -317,7 +317,7 @@ namespace OpenRA.Mods.Common.Traits self.CancelActivity(); self.SetTargetLine(order.Target, Color.Yellow); - self.QueueActivity(order.Queued, new PickupUnit(self, order.Target.Actor, Info.LoadingDelay)); + self.QueueActivity(order.Queued, new PickupUnit(self, order.Target.Actor, Info.BeforeLoadDelay)); } } diff --git a/OpenRA.Mods.Common/Traits/Passenger.cs b/OpenRA.Mods.Common/Traits/Passenger.cs index be15a43f90..9b52696ba2 100644 --- a/OpenRA.Mods.Common/Traits/Passenger.cs +++ b/OpenRA.Mods.Common/Traits/Passenger.cs @@ -168,9 +168,13 @@ namespace OpenRA.Mods.Common.Traits public bool Reserve(Actor self, Cargo cargo) { + if (cargo == ReservedCargo) + return true; + Unreserve(self); if (!cargo.ReserveSpace(self)) return false; + ReservedCargo = cargo; return true; } @@ -181,6 +185,7 @@ namespace OpenRA.Mods.Common.Traits { if (ReservedCargo == null) return; + ReservedCargo.UnreserveSpace(self); ReservedCargo = null; } diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20190314/RenameCarryallDelays.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20190314/RenameCarryallDelays.cs new file mode 100644 index 0000000000..8119377344 --- /dev/null +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20190314/RenameCarryallDelays.cs @@ -0,0 +1,42 @@ +#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.Collections.Generic; + +namespace OpenRA.Mods.Common.UpdateRules.Rules +{ + public class RenameCarryallDelays : UpdateRule + { + public override string Name { get { return "Rename Carryall and Cargo delay parameters"; } } + public override string Description + { + get + { + return "Carryall's LoadingDelay and UnloadingDelay parameters have been renamed\n" + + "to BeforeLoadDelay and BeforeUnloadDelay to match new parameters on Cargo."; + } + } + + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + { + foreach (var carryall in actorNode.ChildrenMatching("Carryall")) + { + foreach (var node in carryall.ChildrenMatching("LoadingDelay")) + node.RenameKey("BeforeLoadDelay"); + + foreach (var node in carryall.ChildrenMatching("UnloadingDelay")) + node.RenameKey("BeforeUnloadDelay"); + } + + yield break; + } + } +} diff --git a/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs b/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs index ed5429990b..1e9d984b88 100644 --- a/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs +++ b/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs @@ -129,6 +129,7 @@ namespace OpenRA.Mods.Common.UpdateRules new RemovePlaceBuildingPalettes(), new RenameHoversOffsetModifier(), new AddAirAttackTypes(), + new RenameCarryallDelays(), }) }; diff --git a/mods/cnc/rules/aircraft.yaml b/mods/cnc/rules/aircraft.yaml index fa76be8e98..66e48c42b5 100644 --- a/mods/cnc/rules/aircraft.yaml +++ b/mods/cnc/rules/aircraft.yaml @@ -12,7 +12,7 @@ TRAN: Queue: Aircraft.GDI, Aircraft.Nod Description: Fast Infantry Transport Helicopter.\n Unarmed Aircraft: - LandWhenIdle: true + LandWhenIdle: false TurnSpeed: 5 Speed: 150 AltitudeVelocity: 0c100 @@ -43,6 +43,7 @@ TRAN: Types: Infantry MaxWeight: 10 PipCount: 10 + AfterUnloadDelay: 40 SpawnActorOnDeath: Actor: TRAN.Husk SelectionDecorations: diff --git a/mods/d2k/rules/aircraft.yaml b/mods/d2k/rules/aircraft.yaml index 4d9505664b..d18424a5fc 100644 --- a/mods/d2k/rules/aircraft.yaml +++ b/mods/d2k/rules/aircraft.yaml @@ -34,8 +34,8 @@ carryall.reinforce: Actor: carryall.huskVTOL RequiresCondition: !cruising Carryall: - LoadingDelay: 10 - UnloadingDelay: 15 + BeforeLoadDelay: 10 + BeforeUnloadDelay: 15 LocalOffset: 0, 0, -128 RenderSprites: Image: carryall @@ -52,8 +52,8 @@ carryall: Inherits: carryall.reinforce -Carryall: AutoCarryall: - LoadingDelay: 10 - UnloadingDelay: 15 + BeforeLoadDelay: 10 + BeforeUnloadDelay: 15 LocalOffset: 0, 0, -128 Aircraft: MinAirborneAltitude: 400 diff --git a/mods/ra/rules/aircraft.yaml b/mods/ra/rules/aircraft.yaml index 1a655afb8d..bc17991ac3 100644 --- a/mods/ra/rules/aircraft.yaml +++ b/mods/ra/rules/aircraft.yaml @@ -238,6 +238,7 @@ TRAN: Range: 6c0 Type: GroundPosition Aircraft: + LandWhenIdle: false TurnSpeed: 5 Speed: 128 AltitudeVelocity: 0c58 @@ -261,6 +262,7 @@ TRAN: Types: Infantry MaxWeight: 8 PipCount: 8 + AfterUnloadDelay: 40 SpawnActorOnDeath: Actor: TRAN.Husk SelectionDecorations: diff --git a/mods/ts/rules/aircraft.yaml b/mods/ts/rules/aircraft.yaml index bd85459854..41cf0b9272 100644 --- a/mods/ts/rules/aircraft.yaml +++ b/mods/ts/rules/aircraft.yaml @@ -190,7 +190,7 @@ ORCATRAN: Prerequisites: ~disabled RenderSprites: Aircraft: - LandWhenIdle: true + LandWhenIdle: false TurnSpeed: 5 Speed: 84 InitialFacing: 0 @@ -210,6 +210,7 @@ ORCATRAN: PipCount: 5 UnloadVoice: Move EjectOnDeath: true + AfterUnloadDelay: 40 SpawnActorOnDeath: Actor: ORCATRAN.Husk @@ -236,6 +237,8 @@ TRNSPORT: Carryall: Voice: Move LocalOffset: 0,0,-317 + BeforeLoadDelay: 10 + BeforeUnloadDelay: 10 Health: HP: 17500 Armor: