#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.Activities; using OpenRA.Mods.Common.Traits; using OpenRA.Traits; namespace OpenRA.Mods.Common.Activities { public class DeliverUnit : Activity { readonly Actor self; readonly Carryall carryall; readonly BodyOrientation body; readonly IFacing facing; CPos? destination; public DeliverUnit(Actor self, CPos? destination = null) { this.self = self; this.destination = destination; facing = self.Trait(); carryall = self.Trait(); body = self.Trait(); } CPos? FindDropLocation(CPos targetCell, WDist maxSearchDistance) { var positionable = carryall.Carryable.Trait(); // The easy case 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, self)) continue; var delta = self.World.Map.CenterOfCell(c) - centerPosition; if (delta.LengthSquared < maxSearchDistance.LengthSquared) return c; } return null; } public override Activity Tick(Actor self) { if (ChildActivity != null) { ChildActivity = ActivityUtils.RunActivity(self, ChildActivity); if (ChildActivity != null) return this; } if (IsCanceling || carryall.State != Carryall.CarryallState.Carrying || carryall.Carryable.IsDead) return NextActivity; if (destination == null) destination = self.Location; var targetLocation = FindDropLocation(destination.Value, 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.Value)), true); QueueChild(self, new Wait(25)); return this; } // 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); 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 carryableFacing = carryall.Carryable.Trait(); var facingDelta = facing.Facing - carryableFacing.Facing; foreach (var t in carryall.Carryable.TraitsImplementing()) t.TurretFacing += facingDelta; 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); carryable.Detached(cargo); }); } } }