diff --git a/OpenRA.Mods.Common/Activities/Air/HeliLand.cs b/OpenRA.Mods.Common/Activities/Air/HeliLand.cs index dc4ab0ee04..163a37fc17 100644 --- a/OpenRA.Mods.Common/Activities/Air/HeliLand.cs +++ b/OpenRA.Mods.Common/Activities/Air/HeliLand.cs @@ -26,7 +26,7 @@ namespace OpenRA.Mods.Common.Activities bool landingInitiated; public HeliLand(Actor self, bool requireSpace, Actor ignoreActor = null) - : this(self, requireSpace, self.Info.TraitInfo().LandAltitude, ignoreActor) { } + : this(self, requireSpace, self.Trait().LandAltitude, ignoreActor) { } public HeliLand(Actor self, bool requireSpace, WDist landAltitude, Actor ignoreActor = null) { diff --git a/OpenRA.Mods.Common/Activities/DeliverUnit.cs b/OpenRA.Mods.Common/Activities/DeliverUnit.cs index 8285bc867b..86082dc31a 100644 --- a/OpenRA.Mods.Common/Activities/DeliverUnit.cs +++ b/OpenRA.Mods.Common/Activities/DeliverUnit.cs @@ -18,44 +18,34 @@ namespace OpenRA.Mods.Common.Activities public class DeliverUnit : Activity { readonly Actor self; - readonly Carryable carryable; readonly Carryall carryall; - readonly IPositionable positionable; readonly BodyOrientation body; - readonly IFacing carryableFacing; - readonly IFacing carryallFacing; - readonly CPos destination; + readonly IFacing facing; + CPos? destination; - enum DeliveryState { Transport, Land, Wait, Release, TakeOff, Done, Aborted } - - DeliveryState state; - - public DeliverUnit(Actor self, CPos destination) + public DeliverUnit(Actor self, CPos? destination = null) { this.self = self; this.destination = destination; - carryallFacing = self.Trait(); + facing = self.Trait(); carryall = self.Trait(); body = self.Trait(); - - carryable = carryall.Carryable.Trait(); - positionable = carryall.Carryable.Trait(); - carryableFacing = carryall.Carryable.Trait(); - state = DeliveryState.Transport; } CPos? FindDropLocation(CPos targetCell, WDist maxSearchDistance) { + var positionable = carryall.Carryable.Trait(); + // The easy case - if (positionable.CanEnterCell(targetCell)) + if (positionable.CanEnterCell(targetCell, self)) 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 (!positionable.CanEnterCell(c)) + if (!positionable.CanEnterCell(c, self)) continue; var delta = self.World.Map.CenterOfCell(c) - centerPosition; @@ -66,14 +56,6 @@ namespace OpenRA.Mods.Common.Activities return null; } - // Check if we can drop the unit at our current location. - bool CanDropHere() - { - 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 (ChildActivity != null) @@ -83,120 +65,80 @@ namespace OpenRA.Mods.Common.Activities return this; } - if (IsCanceling) + if (IsCanceling || carryall.State != Carryall.CarryallState.Carrying || carryall.Carryable.IsDead) return NextActivity; - if ((carryall.State == Carryall.CarryallState.Idle || carryall.Carryable.IsDead) && state != DeliveryState.TakeOff) - state = DeliveryState.Aborted; + if (destination == null) + destination = self.Location; - switch (state) + var targetLocation = FindDropLocation(destination.Value, carryall.Info.DropRange); + + // Can't land, so wait at the target until something changes + if (!targetLocation.HasValue) { - case DeliveryState.Transport: - { - var targetLocation = FindDropLocation(destination, carryall.Info.DropRange); - - // Can't land, so wait at the target until something changes - if (!targetLocation.HasValue) - { - QueueChild(self, new HeliFly(self, Target.FromCell(self.World, destination)), true); - QueueChild(self, 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))); - QueueChild(self, new HeliFly(self, Target.FromPos(targetPosition - body.LocalToWorld(localOffset))), true); - QueueChild(self, new Turn(self, facing)); - return this; - } - - QueueChild(self, new HeliFly(self, Target.FromPos(targetPosition)), true); - return this; - } - - state = DeliveryState.Land; - return this; - } - - case DeliveryState.Land: - { - if (!CanDropHere()) - { - state = DeliveryState.Transport; - return this; - } - - // 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) - { - QueueChild(self, new HeliLand(self, false, -new WDist(carryall.CarryableOffset.Z)), true); - return this; - } - - state = carryall.Info.UnloadingDelay > 0 ? DeliveryState.Wait : DeliveryState.Release; - return this; - } - - case DeliveryState.Wait: - state = DeliveryState.Release; - QueueChild(self, new Wait(carryall.Info.UnloadingDelay, false), true); - return this; - - case DeliveryState.Release: - if (!CanDropHere()) - { - state = DeliveryState.Transport; - return this; - } - - Release(); - state = DeliveryState.TakeOff; - return this; - - case DeliveryState.TakeOff: - QueueChild(self, new HeliFly(self, Target.FromPos(self.CenterPosition)), true); - state = DeliveryState.Done; - return this; - - case DeliveryState.Aborted: - carryall.UnreserveCarryable(self); - break; + QueueChild(self, new HeliFly(self, Target.FromCell(self.World, destination.Value)), true); + QueueChild(self, new Wait(25)); + return this; } - return NextActivity; + // Move to drop-off location + 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))); + QueueChild(self, new HeliFly(self, Target.FromPos(targetPosition - body.LocalToWorld(localOffset))), true); + QueueChild(self, new Turn(self, facing)); + return this; + } + + QueueChild(self, new HeliFly(self, Target.FromPos(targetPosition)), true); + return this; + } + + // Make sure that the carried actor is on the ground before releasing it + if (self.World.Map.DistanceAboveTerrain(carryablePosition) != WDist.Zero) + QueueChild(self, new HeliLand(self, true), true); + + // Pause briefly before releasing for visual effect + if (carryall.Info.UnloadingDelay > 0) + QueueChild(self, new Wait(carryall.Info.UnloadingDelay, false), true); + + // Release carried actor + QueueChild(self, new CallFunc(Release)); + QueueChild(self, new HeliFly(self, Target.FromPos(self.CenterPosition))); + return this; } void Release() { + self.Trait().RemoveInfluence(); + 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); + carryall.Carryable.Trait().SetPosition(carryall.Carryable, targetLocation, SubCell.FullCell); // HACK: directly manipulate the turret facings to match the new orientation // This can eventually go away, when we make turret facings relative to the body - var facingDelta = carryallFacing.Facing - carryableFacing.Facing; + var carryableFacing = carryall.Carryable.Trait(); + var facingDelta = facing.Facing - carryableFacing.Facing; foreach (var t in carryall.Carryable.TraitsImplementing()) t.TurretFacing += facingDelta; - carryableFacing.Facing = carryallFacing.Facing; + carryableFacing.Facing = facing.Facing; // Put back into world self.World.AddFrameEndTask(w => { var cargo = carryall.Carryable; + var carryable = carryall.Carryable.Trait(); w.Add(cargo); carryall.DetachCarryable(self); carryable.UnReserve(cargo); diff --git a/OpenRA.Mods.Common/Activities/PickupUnit.cs b/OpenRA.Mods.Common/Activities/PickupUnit.cs index 54147d9291..3d6c8942a5 100644 --- a/OpenRA.Mods.Common/Activities/PickupUnit.cs +++ b/OpenRA.Mods.Common/Activities/PickupUnit.cs @@ -30,7 +30,7 @@ namespace OpenRA.Mods.Common.Activities readonly int delay; - enum PickupState { Intercept, LockCarryable, MoveToCarryable, Turn, Land, Wait, Pickup, Aborted } + enum PickupState { Intercept, LockCarryable, MoveToCarryable, Turn, Land, Wait, Pickup } PickupState state; @@ -49,6 +49,11 @@ namespace OpenRA.Mods.Common.Activities state = PickupState.Intercept; } + protected override void OnFirstRun(Actor self) + { + carryall.ReserveCarryable(self, cargo); + } + public override Activity Tick(Actor self) { if (ChildActivity != null) @@ -67,7 +72,7 @@ namespace OpenRA.Mods.Common.Activities return NextActivity; } - if (carryall.State == Carryall.CarryallState.Idle) + if (carryall.State != Carryall.CarryallState.Reserved) return NextActivity; switch (state) @@ -78,9 +83,10 @@ namespace OpenRA.Mods.Common.Activities return this; case PickupState.LockCarryable: - state = PickupState.MoveToCarryable; if (!carryable.LockForPickup(cargo, self)) - state = PickupState.Aborted; + Cancel(self); + + state = PickupState.MoveToCarryable; return this; case PickupState.MoveToCarryable: @@ -129,19 +135,14 @@ namespace OpenRA.Mods.Common.Activities } case PickupState.Wait: - state = PickupState.Pickup; QueueChild(self, new Wait(delay, false), true); + state = PickupState.Pickup; return this; case PickupState.Pickup: // Remove our carryable from world Attach(self); - return NextActivity; - - case PickupState.Aborted: - // We got cancelled - carryall.UnreserveCarryable(self); - break; + return this; } return NextActivity; diff --git a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs index 098470f514..231d0b94c3 100644 --- a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs +++ b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs @@ -193,6 +193,8 @@ namespace OpenRA.Mods.Common.Traits public bool ForceLanding { get; private set; } CPos? landingCell; + public WDist LandAltitude { get; private set; } + bool airborne; bool cruising; bool firstTick = true; @@ -216,6 +218,17 @@ namespace OpenRA.Mods.Common.Traits SetPosition(self, init.Get()); Facing = init.Contains() ? init.Get() : Info.InitialFacing; + LandAltitude = info.LandAltitude; + } + + public void AddLandingOffset(int offset) + { + LandAltitude += new WDist(offset); + } + + public void SubtractLandingOffset(int offset) + { + LandAltitude -= new WDist(offset); } public virtual IEnumerable GetVariableObservers() @@ -435,7 +448,7 @@ namespace OpenRA.Mods.Common.Traits { // Map.DistanceAboveTerrain(WPos pos) is called directly because Aircraft is an IPositionable trait // and all calls occur in Tick methods. - if (self.World.Map.DistanceAboveTerrain(CenterPosition) != Info.LandAltitude) + if (self.World.Map.DistanceAboveTerrain(CenterPosition) != LandAltitude) return null; // Not on the resupplier. return self.World.ActorMap.GetActorsAt(self.Location) @@ -481,7 +494,7 @@ namespace OpenRA.Mods.Common.Traits ReservedActor = null; MayYieldReservation = false; - if (self.World.Map.DistanceAboveTerrain(CenterPosition).Length <= Info.LandAltitude.Length) + if (self.World.Map.DistanceAboveTerrain(CenterPosition).Length <= LandAltitude.Length) self.QueueActivity(new TakeOff(self)); } @@ -591,7 +604,7 @@ namespace OpenRA.Mods.Common.Traits protected virtual void OnBecomingIdle(Actor self) { - var atLandAltitude = self.World.Map.DistanceAboveTerrain(CenterPosition) == Info.LandAltitude; + var atLandAltitude = self.World.Map.DistanceAboveTerrain(CenterPosition) == 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. diff --git a/OpenRA.Mods.Common/Traits/Carryall.cs b/OpenRA.Mods.Common/Traits/Carryall.cs index 10d165f310..43928d5dcb 100644 --- a/OpenRA.Mods.Common/Traits/Carryall.cs +++ b/OpenRA.Mods.Common/Traits/Carryall.cs @@ -35,13 +35,29 @@ namespace OpenRA.Mods.Common.Traits [Desc("Radius around the target drop location that are considered if the target tile is blocked.")] public readonly WDist DropRange = WDist.FromCells(5); + [Desc("Cursor to display when able to unload the passengers.")] + public readonly string UnloadCursor = "deploy"; + + [Desc("Cursor to display when unable to unload the passengers.")] + public readonly string UnloadBlockedCursor = "deploy-blocked"; + + [Desc("Allow moving and unloading with one order using force-move")] + public readonly bool AllowDropOff = false; + + [Desc("Cursor to display when able to drop off the passengers at location.")] + public readonly string DropOffCursor = "ability"; + + [Desc("Cursor to display when unable to drop off the passengers at location.")] + public readonly string DropOffBlockedCursor = "move-blocked"; + [VoiceReference] public readonly string Voice = "Action"; public virtual object Create(ActorInitializer init) { return new Carryall(init.Self, this); } } - public class Carryall : INotifyKilled, ISync, ITick, IRender, INotifyActorDisposing, IIssueOrder, IResolveOrder, IOrderVoice + public class Carryall : INotifyKilled, ISync, ITick, IRender, INotifyActorDisposing, IIssueOrder, IResolveOrder, + IOrderVoice, IIssueDeployOrder { public enum CarryallState { @@ -52,9 +68,11 @@ namespace OpenRA.Mods.Common.Traits public readonly CarryallInfo Info; readonly AircraftInfo aircraftInfo; + readonly Aircraft aircraft; readonly BodyOrientation body; readonly IMove move; readonly IFacing facing; + readonly Actor self; // The actor we are currently carrying. [Sync] public Actor Carryable { get; private set; } @@ -74,9 +92,11 @@ namespace OpenRA.Mods.Common.Traits State = CarryallState.Idle; aircraftInfo = self.Info.TraitInfoOrDefault(); + aircraft = self.Trait(); body = self.Trait(); move = self.Trait(); facing = self.Trait(); + this.self = self; } void ITick.Tick(Actor self) @@ -143,6 +163,7 @@ namespace OpenRA.Mods.Common.Traits self.World.ScreenMap.AddOrUpdate(self); CarryableOffset = OffsetForCarryable(self, carryable); + aircraft.AddLandingOffset(-CarryableOffset.Z); return true; } @@ -152,6 +173,7 @@ namespace OpenRA.Mods.Common.Traits self.World.ScreenMap.AddOrUpdate(self); carryablePreview = null; + aircraft.SubtractLandingOffset(-CarryableOffset.Z); CarryableOffset = WVec.Zero; } @@ -219,14 +241,22 @@ namespace OpenRA.Mods.Common.Traits yield return b; } + // Check if we can drop the unit at our current location. + public bool CanUnload() + { + var localOffset = CarryableOffset.Rotate(body.QuantizeOrientation(self, self.Orientation)); + var targetCell = self.World.Map.CellContaining(self.CenterPosition + body.LocalToWorld(localOffset)); + return Carryable != null && Carryable.Trait().CanEnterCell(targetCell, self); + } + IEnumerable IIssueOrder.Orders { get { - if (State != CarryallState.Carrying) - yield return new CarryallPickupOrderTargeter(); - else - yield return new CarryallDeliverUnitTargeter(aircraftInfo, CarryableOffset); + yield return new CarryallPickupOrderTargeter(); + yield return new DeployOrderTargeter("Unload", 10, + () => CanUnload() ? Info.UnloadCursor : Info.UnloadBlockedCursor); + yield return new CarryallDeliverUnitTargeter(aircraftInfo, Info, CarryableOffset); } } @@ -238,43 +268,43 @@ namespace OpenRA.Mods.Common.Traits return null; } + Order IIssueDeployOrder.IssueDeployOrder(Actor self, bool queued) + { + return new Order("Unload", self, queued); + } + + bool IIssueDeployOrder.CanIssueDeployOrder(Actor self) { return true; } + void IResolveOrder.ResolveOrder(Actor self, Order order) { - if (State == CarryallState.Carrying) + if (order.OrderString == "DeliverUnit") { - if (order.OrderString == "DeliverUnit") - { - var cell = self.World.Map.Clamp(self.World.Map.CellContaining(order.Target.CenterPosition)); - if (!aircraftInfo.MoveIntoShroud && !self.Owner.Shroud.IsExplored(cell)) - return; + var cell = self.World.Map.Clamp(self.World.Map.CellContaining(order.Target.CenterPosition)); + if (!aircraftInfo.MoveIntoShroud && !self.Owner.Shroud.IsExplored(cell)) + return; - var targetLocation = move.NearestMoveableCell(cell); - self.SetTargetLine(Target.FromCell(self.World, targetLocation), Color.Yellow); - self.QueueActivity(order.Queued, new DeliverUnit(self, targetLocation)); - } - else if (order.OrderString == "Unload") - { - var targetLocation = move.NearestMoveableCell(self.Location); - self.SetTargetLine(Target.FromCell(self.World, targetLocation), Color.Yellow); - self.QueueActivity(order.Queued, new DeliverUnit(self, targetLocation)); - } + var targetLocation = move.NearestMoveableCell(cell); + self.SetTargetLine(Target.FromCell(self.World, targetLocation), Color.Yellow); + self.QueueActivity(order.Queued, new DeliverUnit(self, targetLocation)); } - else + else if (order.OrderString == "Unload") { - if (order.OrderString == "PickupUnit") - { - if (order.Target.Type != TargetType.Actor) - return; + if (!order.Queued && !CanUnload()) + return; - if (!ReserveCarryable(self, order.Target.Actor)) - return; + self.QueueActivity(order.Queued, new DeliverUnit(self)); + } - if (!order.Queued) - self.CancelActivity(); + if (order.OrderString == "PickupUnit") + { + if (order.Target.Type != TargetType.Actor) + return; - self.SetTargetLine(order.Target, Color.Yellow); - self.QueueActivity(order.Queued, new PickupUnit(self, order.Target.Actor, Info.LoadingDelay)); - } + if (!order.Queued) + self.CancelActivity(); + + self.SetTargetLine(order.Target, Color.Yellow); + self.QueueActivity(order.Queued, new PickupUnit(self, order.Target.Actor, Info.LoadingDelay)); } } @@ -327,41 +357,46 @@ namespace OpenRA.Mods.Common.Traits class CarryallDeliverUnitTargeter : AircraftMoveOrderTargeter { readonly AircraftInfo aircraftInfo; + readonly CarryallInfo info; readonly WVec carryableOffset; - public CarryallDeliverUnitTargeter(AircraftInfo aircraftInfo, WVec carryableOffset) + public CarryallDeliverUnitTargeter(AircraftInfo aircraftInfo, CarryallInfo info, WVec carryableOffset) : base(aircraftInfo) { OrderID = "DeliverUnit"; OrderPriority = 6; this.carryableOffset = carryableOffset; this.aircraftInfo = aircraftInfo; + this.info = info; } public override bool CanTarget(Actor self, Target target, List othersAtTarget, ref TargetModifiers modifiers, ref string cursor) { - if (modifiers.HasModifier(TargetModifiers.ForceMove)) + if (!info.AllowDropOff || !modifiers.HasModifier(TargetModifiers.ForceMove)) return false; + cursor = info.DropOffCursor; var type = target.Type; - if (type == TargetType.Actor && self == target.Actor) - { - var altitude = self.World.Map.DistanceAboveTerrain(self.CenterPosition); - if (altitude.Length - carryableOffset.Z < aircraftInfo.MinAirborneAltitude) - { - cursor = "deploy"; - OrderID = "Unload"; - return true; - } - } - else if ((type == TargetType.Actor && target.Actor.Info.HasTraitInfo()) + + if ((type == TargetType.Actor && target.Actor.Info.HasTraitInfo()) || (target.Type == TargetType.FrozenActor && target.FrozenActor.Info.HasTraitInfo())) { - cursor = "move-blocked"; + cursor = info.DropOffBlockedCursor; return true; } - return base.CanTarget(self, target, othersAtTarget, ref modifiers, ref cursor); + var location = self.World.Map.CellContaining(target.CenterPosition); + var explored = self.Owner.Shroud.IsExplored(location); + cursor = self.World.Map.Contains(location) ? + (self.World.Map.GetTerrainInfo(location).CustomCursor ?? info.DropOffCursor) : + info.DropOffBlockedCursor; + + IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue); + + if (!explored && !aircraftInfo.MoveIntoShroud) + cursor = info.DropOffBlockedCursor; + + return true; } } }