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.Drawing;
  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, IPositionableInfo, IFacingInfo, IOccupySpaceInfo, IMoveInfo, 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 HashSet<string> RepairBuildings = new HashSet<string> { };
  		[ActorReference]
  		public readonly HashSet<string> RearmBuildings = new HashSet<string> { };
  		public readonly int InitialFacing = 0;
  		public readonly int ROT = 255;
  		public readonly int Speed = 1;
  
  		[Desc("Minimum altitude where this aircraft is considered airborne")]
  		public readonly int MinAirborneAltitude = 1;
  		public readonly HashSet<string> LandableTerrainTypes = new HashSet<string>();
  
  		[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";
  
  		[UpgradeGrantedReference]
  		[Desc("The upgrades to grant to self while airborne.")]
  		public readonly string[] AirborneUpgrades = { };
  
  		[UpgradeGrantedReference]
  		[Desc("The upgrades to grant to self while at cruise altitude.")]
  		public readonly string[] CruisingUpgrades = { };
  
  		[Desc("Can the actor hover in place mid-air? If not, then the actor will have to remain in motion (circle around).")]
  		public readonly bool CanHover = false;
  
  		[Desc("Will this actor try to land after it has no more commands?")]
  		public readonly bool LandWhenIdle = true;
  
  		[Desc("Does this actor need to turn before landing?")]
  		public readonly bool TurnToLand = false;
  
  		public readonly WDist LandAltitude = WDist.Zero;
  
  		[Desc("How fast this actor ascends or descends when using horizontal take off/landing.")]
  		public readonly WAngle MaximumPitch = WAngle.FromDegrees(10);
  
  		[Desc("How fast this actor ascends or descends when using vertical take off/landing.")]
  		public readonly WDist AltitudeVelocity = new WDist(43);
  
  		[Desc("Sound to play when the actor is taking off.")]
  		public readonly string TakeoffSound = null;
  
  		[Desc("Sound to play when the actor is landing.")]
  		public readonly string LandingSound = null;
  
  		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 : ITick, ISync, IFacing, IPositionable, IMove, IIssueOrder, IResolveOrder, IOrderVoice, IDeathActorInitModifier,
  		INotifyCreated, INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyActorDisposing
  	{
  		static readonly Pair<CPos, SubCell>[] NoCells = { };
  
  		public readonly bool IsPlane;
  		public readonly AircraftInfo Info;
  		readonly Actor self;
  
  		UpgradeManager um;
  		IDisposable reservation;
  		IEnumerable<int> speedModifiers;
  
  		[Sync] public int Facing { get; set; }
  		[Sync] public WPos CenterPosition { get; private set; }
  		public CPos TopLeft { get { return self.World.Map.CellContaining(CenterPosition); } }
  		public int ROT { get { return Info.ROT; } }
  
  		bool airborne;
  		bool cruising;
  
  		public Aircraft(ActorInitializer init, AircraftInfo info)
  		{
  			Info = info;
  			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;
  
  			// TODO: HACK: This is a hack until we can properly distinguish between airplane and helicopter!
  			// Or until the activities get unified enough so that it doesn't matter.
  			IsPlane = !info.CanHover;
  		}
  
  		public void Created(Actor self)
  		{
  			um = self.TraitOrDefault<UpgradeManager>();
  			speedModifiers = self.TraitsImplementing<ISpeedModifier>().ToArray().Select(sm => sm.GetSpeedModifier());
  		}
  
  		public void AddedToWorld(Actor self)
  		{
  			self.World.ActorMap.AddInfluence(self, this);
  			self.World.ActorMap.AddPosition(self, this);
  			self.World.ScreenMap.Add(self);
  			var altitude = self.World.Map.DistanceAboveTerrain(CenterPosition);
  			if (altitude.Length >= Info.MinAirborneAltitude)
  				OnAirborneAltitudeReached();
  			if (altitude == Info.CruiseAltitude)
  				OnCruisingAltitudeReached();
  		}
  
  		bool firstTick = true;
  		public virtual void Tick(Actor self)
  		{
  			if (firstTick)
  			{
  				firstTick = false;
  
  				// TODO: Aircraft husks don't properly unreserve.
  				if (self.Info.HasTraitInfo<FallsToEarthInfo>())
  					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 = self.World.Map.DistanceAboveTerrain(CenterPosition).Length;
  			if (altitude != Info.CruiseAltitude.Length)
  				return WVec.Zero;
  
  			// PERF: Avoid LINQ.
  			var repulsionForce = WVec.Zero;
  			foreach (var actor in self.World.FindActorsInCircle(self.CenterPosition, Info.IdealSeparation))
  			{
  				if (actor.IsDead)
  					continue;
  
  				var ai = actor.Info.TraitInfoOrDefault<AircraftInfo>();
  				if (ai == null || !ai.Repulsable || ai.CruiseAltitude != Info.CruiseAltitude)
  					continue;
  
  				repulsionForce += GetRepulsionForce(actor);
  			}
  
  			if (Info.CanHover)
  				return repulsionForce;
  
  			// Non-hovering actors mush always keep moving forward, so they need extra calculations.
  			var currentDir = FlyStep(Facing);
  			var length = currentDir.HorizontalLength * repulsionForce.HorizontalLength;
  			if (length == 0)
  				return WVec.Zero;
  
  			var dot = WVec.Dot(currentDir, repulsionForce) / length;
  
  			// avoid stalling the plane
  			return dot >= 0 ? repulsionForce : WVec.Zero;
  		}
  
  		public WVec GetRepulsionForce(Actor other)
  		{
  			if (self == other || other.CenterPosition.Z < self.CenterPosition.Z)
  				return WVec.Zero;
  
  
gt: 			var d = self.CenterPosition - other.CenterPosition;