context: #region Copyright & License Information
  /*
   * Copyright 2007-2015 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. For more information,
   * see COPYING.
   */
  #endregion
  
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using OpenRA.Activities;
  using OpenRA.Mods.Common.Activities;
  using OpenRA.Mods.Common.Orders;
  using OpenRA.Primitives;
  using OpenRA.Traits;
  
  namespace OpenRA.Mods.Common.Traits
  {
  	public class AircraftInfo : ITraitInfo, IFacingInfo, IOccupySpaceInfo, ICruiseAltitudeInfo, UsesInit<LocationInit>, UsesInit<FacingInit>
  	{
  		public readonly WDist CruiseAltitude = new WDist(1280);
  		public readonly WDist IdealSeparation = new WDist(1706);
  		[Desc("Whether the aircraft can be repulsed.")]
  		public readonly bool Repulsable = true;
  		[Desc("The speed at which the aircraft is repulsed from other aircraft. Specify -1 for normal movement speed.")]
  		public readonly int RepulsionSpeed = -1;
  
  		[ActorReference]
  		public readonly string[] RepairBuildings = { "fix" };
  		[ActorReference]
  		public readonly string[] RearmBuildings = { "hpad", "afld" };
  		public readonly int InitialFacing = 0;
  		public readonly int ROT = 255;
  		public readonly int Speed = 1;
  		public readonly string[] LandableTerrainTypes = { };
  
  		[Desc("Can the actor be ordered to move in to shroud?")]
  		public readonly bool MoveIntoShroud = true;
  
  		public virtual object Create(ActorInitializer init) { return new Aircraft(init, this); }
  		public int GetInitialFacing() { return InitialFacing; }
  		public WDist GetCruiseAltitude() { return CruiseAltitude; }
  
  		[VoiceReference] public readonly string Voice = "Action";
  
  		public IReadOnlyDictionary<CPos, SubCell> OccupiedCells(ActorInfo info, CPos location, SubCell subCell = SubCell.Any) { return new ReadOnlyDictionary<CPos, SubCell>(); }
  		bool IOccupySpaceInfo.SharesCell { get { return false; } }
  	}
  
  	public class Aircraft : IFacing, IPositionable, ISync, IIssueOrder, IOrderVoice, INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyActorDisposing
  	{
  		static readonly Pair<CPos, SubCell>[] NoCells = { };
  
  		readonly AircraftInfo info;
  		readonly Actor self;
  
  		[Sync] public int Facing { get; set; }
  		[Sync] public WPos CenterPosition { get; private set; }
  		public CPos TopLeft { get { return self.World.Map.CellContaining(CenterPosition); } }
  		public IDisposable Reservation;
  		public int ROT { get { return info.ROT; } }
  
  		public Aircraft(ActorInitializer init, AircraftInfo info)
  		{
  			this.info = info;
  			this.self = init.Self;
  
  			if (init.Contains<LocationInit>())
  				SetPosition(self, init.Get<LocationInit, CPos>());
  
  			if (init.Contains<CenterPositionInit>())
  				SetPosition(self, init.Get<CenterPositionInit, WPos>());
  
  			Facing = init.Contains<FacingInit>() ? init.Get<FacingInit, int>() : info.InitialFacing;
  		}
  
          bool firstTick = true;
          public virtual void Tick(Actor self)
          {
              if (firstTick)
              {
                  firstTick = false;
  
                  // TODO: Aircraft husks don't properly unreserve.
                  if (self.HasTrait<FallsToEarth>())
                      return;
  
                  ReserveSpawnBuilding();
  
                  var host = GetActorBelow();
                  if (host == null)
                      return;
  
                  self.QueueActivity(new TakeOff(self));
              }
  
              Repulse();
          }
  
  		public void Repulse()
  		{
  			var repulsionForce = GetRepulsionForce();
  
  			var repulsionFacing = Util.GetFacing(repulsionForce, -1);
  			if (repulsionFacing == -1)
  				return;
  
  			var speed = info.RepulsionSpeed != -1 ? info.RepulsionSpeed : MovementSpeed;
  			SetPosition(self, CenterPosition + FlyStep(speed, repulsionFacing));
  		}
  
  		public virtual WVec GetRepulsionForce()
  		{
  			if (!info.Repulsable)
  				return WVec.Zero;
  
  			// Repulsion only applies when we're flying!
  			var altitude = CenterPosition.Z;
  			if (altitude != info.CruiseAltitude.Length)
  				return WVec.Zero;
  
  			return self.World.FindActorsInCircle(self.CenterPosition, info.IdealSeparation)
  				.Where(a => !a.IsDead && a.HasTrait<Aircraft>() && a.Info.Traits.Get<AircraftInfo>().CruiseAltitude == info.CruiseAltitude)
  				.Select(GetRepulsionForce)
  				.Aggregate(WVec.Zero, (a, b) => a + b);
  		}
  
  		public WVec GetRepulsionForce(Actor other)
  		{
  			if (self == other || other.CenterPosition.Z < self.CenterPosition.Z)
  				return WVec.Zero;
  
  			var d = self.CenterPosition - other.CenterPosition;
  			var distSq = d.HorizontalLengthSquared;
  			if (distSq > info.IdealSeparation.LengthSquared)
  				return WVec.Zero;
  
  			if (distSq < 1)
  			{
  				var yaw = self.World.SharedRandom.Next(0, 1023);
  				var rot = new WRot(WAngle.Zero, WAngle.Zero, new WAngle(yaw));
  				return new WVec(1024, 0, 0).Rotate(rot);
  			}
  
  			return (d * 1024 * 8) / (int)distSq;
  		}
  
  		public Actor GetActorBelow()
  		{
  			// 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).Length != 0)
  				return null; // not on the ground.
  
  			return self.World.ActorMap.GetUnitsAt(self.Location)
  				.FirstOrDefault(a => a.HasTrait<Reservable>());
  		}
  
  		protected void ReserveSpawnBuilding()
  		{
  			/* HACK: not spawning in the air, so try to assoc. with our afld. */
  			var afld = GetActorBelow();
  			if (afld == null)
  				return;
  
  			var res = afld.TraitOrDefault<Reservable>();
  
  			if (res != null)
  			{
  				UnReserve();
  				Reservation = res.Reserve(afld, self, this);
  			}
  		}
  
  		public void UnReserve()
  		{
  			if (Reservation != null)
  			{
  				Reservation.Dispose();
  				Reservation = null;
  			}
  		}
  
  		public void Disposing(Actor self)
  		{
  			UnReserve();
  		}
  
  		public void SetPosition(Actor self, WPos pos)
  		{
  			CenterPosition = pos;
  
  			if (self.IsInWorld)
  			{
  				self.World.ScreenMap.Update(self);
  				self.World.ActorMap.UpdatePosition(self, this);
  			}
  		}
  
  		// Changes position, but not altitude
  		public void SetPosition(Actor self, CPos cell, SubCell subCell = SubCell.Any)
  		{
  			SetPosition(self, self.World.Map.CenterOfCell(cell) + new WVec(0, 0, CenterPosition.Z));
  		}
  
  		public void SetVisualPosition(Actor self, WPos pos) { SetPosition(self, pos); }
  
  		public void AddedToWorld(Actor self)
  		{
  			self.World.ActorMap.AddInfluence(self, this);
  			self.World.ActorMap.AddPosition(self, this);
  			self.World.ScreenMap.Add(self);
  		}
  
  		public void RemovedFromWorld(Actor self)
  		{
  			UnReserve();
  			self.World.ActorMap.RemoveInfluence(self, this);
  			self.World.ActorMap.RemovePosition(self, this);
  			self.World.ScreenMap.Remove(self);
  		}
  
  		public bool AircraftCanEnter(Actor a)
  		{
  			if (self.AppearsHostileTo(a))
  				return false;
  
  			return info.RearmBuildings.Contains(a.Info.Name)
  				|| info.RepairBuildings.Contains(a.Info.Name);
  		}
  
  		public bool IsLeavingCell(CPos location, SubCell subCell = SubCell.Any) { return false; } // TODO: Handle landing
  		public SubCell GetValidSubCell(SubCell preferred) { return SubCell.Invalid; }
  		public SubCell GetAvailableSubCell(CPos a, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, bool checkTransientActors = true)
  		{
  			// Does not use any subcell
  			return SubCell.Invalid;
  		}
  
  		public bool CanEnterCell(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true) { return true; }
  
  		public bool CanEnterTargetNow(Actor self, Target target)
  		{
  			if (target.Positions.Any(p => self.World.ActorMap.GetUnitsAt(self.World.Map.CellContaining(p)).Any(a => a != self && a != target.Actor)))
  				return false;
  			var res = target.Actor.TraitOrDefault<Reservable>();
  			if (res == null)
  				return true;
  			UnReserve();
  			Reservation = res.Reserve(target.Actor, self, this);
  			return true;
  		}
  
  		public int MovementSpeed
  		{
  			get
  			{
  
gt: 				var modifiers = self.TraitsImplementing<ISpeedModifier>()