context: #region Copyright & License Information
  /*
   * Copyright 2007-2011 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.FileFormats;
  using OpenRA.Mods.RA.Activities;
  using OpenRA.Traits;
  
  namespace OpenRA.Mods.RA.Move
  {
  	public class MobileInfo : ITraitInfo, IFacingInfo, UsesInit<FacingInit>, UsesInit<LocationInit>, UsesInit<SubCellInit>
  	{
  		[FieldLoader.LoadUsing("LoadSpeeds")]
  		public readonly Dictionary<string, TerrainInfo> TerrainSpeeds;
  		public readonly string[] Crushes;
  		public readonly int WaitAverage = 60;
  		public readonly int WaitSpread = 20;
  		public readonly int InitialFacing = 128;
  		public readonly int ROT = 255;
  		public readonly int Speed = 1;
  		public readonly bool OnRails = false;
  		public readonly bool SharesCell = false;
  		public readonly int Altitude;
  
  		public virtual object Create(ActorInitializer init) { return new Mobile(init, this); }
  
  		static object LoadSpeeds(MiniYaml y)
  		{
  			Dictionary<string, TerrainInfo> ret = new Dictionary<string, TerrainInfo>();
  			foreach (var t in y.NodesDict["TerrainSpeeds"].Nodes)
  			{
  				var speed = FieldLoader.GetValue<decimal>("speed", t.Value.Value);
  				var cost = t.Value.NodesDict.ContainsKey("PathingCost")
  					? FieldLoader.GetValue<int>("cost", t.Value.NodesDict["PathingCost"].Value)
  					: (int)(10000 / speed);
  				ret.Add(t.Key, new TerrainInfo { Speed = speed, Cost = cost });
  			}
  
  			return ret;
  		}
  
  		public class TerrainInfo
  		{
  			public int Cost = int.MaxValue;
  			public decimal Speed = 0;
  		}
  
  		public int MovementCostForCell(World world, CPos cell)
  		{
  			if (!world.Map.IsInMap(cell.X, cell.Y))
  				return int.MaxValue;
  
  			var type = world.GetTerrainType(cell);
  			if (!TerrainSpeeds.ContainsKey(type))
  				return int.MaxValue;
  
  			return TerrainSpeeds[type].Cost;
  		}
  
  		public readonly Dictionary<SubCell, PVecInt> SubCellOffsets = new Dictionary<SubCell, PVecInt>()
  		{
  			{SubCell.TopLeft, new PVecInt(-7,-6)},
  			{SubCell.TopRight, new PVecInt(6,-6)},
  			{SubCell.Center, new PVecInt(0,0)},
  			{SubCell.BottomLeft, new PVecInt(-7,6)},
  			{SubCell.BottomRight, new PVecInt(6,6)},
  			{SubCell.FullCell, new PVecInt(0,0)},
  		};
  
  		static bool IsMovingInMyDirection(Actor self, Actor other)
  		{
  			if (!other.IsMoving()) return false;
  			if (self == null) return true;
  
  			var selfMobile = self.TraitOrDefault<Mobile>();
  			if (selfMobile == null) return false;
  
  			var otherMobile = other.TraitOrDefault<Mobile>();
  			if (otherMobile == null) return false;
  
  			// Sign of dot-product indicates (roughly) if vectors are facing in same or opposite directions:
  			var dp = CVec.Dot((selfMobile.toCell - self.Location), (otherMobile.toCell - other.Location));
  			if (dp <= 0) return false;
  
  			return true;
  		}
  
  		public bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor, bool checkTransientActors, bool blockedByMovers)
  		{
  			if (MovementCostForCell(world, cell) == int.MaxValue)
  				return false;
  
  			if (SharesCell && world.ActorMap.HasFreeSubCell(cell))
  				return true;
  
  			var blockingActors = world.ActorMap.GetUnitsAt(cell)
  				.Where(x => x != ignoreActor)
  				// Neutral/enemy units are blockers. Allied units that are moving are not blockers.
  				.Where(x => blockedByMovers || ((self.Owner.Stances[x.Owner] != Stance.Ally) || !IsMovingInMyDirection(self, x)))
  				.ToList();
  
  			if (checkTransientActors && blockingActors.Count > 0)
  			{
  				// Non-sharable unit can enter a cell with shareable units only if it can crush all of them
  				if (Crushes == null)
  					return false;
  
  				if (blockingActors.Any(a => !(a.HasTrait<ICrushable>() &&
  											 a.TraitsImplementing<ICrushable>().Any(b => b.CrushableBy(Crushes, self.Owner)))))
  					return false;
  			}
  
  			return true;
  		}
  	}
  
  	public class Mobile : IIssueOrder, IResolveOrder, IOrderVoice, IOccupySpace, IMove, IFacing, ISync
  	{
  		public readonly Actor self;
  		public readonly MobileInfo Info;
  		public bool IsMoving { get; internal set; }
  
  		int __facing;
  		CPos __fromCell, __toCell;
  		public SubCell fromSubCell, toSubCell;
  
  		//int __altitude;
  
  		[Sync] public int Facing
  		{
  			get { return __facing; }
  			set { __facing = value; }
  		}
  
  		[Sync] public int Altitude { get; set; }
  
  		public int ROT { get { return Info.ROT; } }
  		public int InitialFacing { get { return Info.InitialFacing; } }
  
  		[Sync] public PPos PxPosition { get; set; }
  		[Sync] public CPos fromCell { get { return __fromCell; } }
  		[Sync] public CPos toCell { get { return __toCell; } }
  
  		[Sync] public int PathHash;	// written by Move.EvalPath, to temporarily debug this crap.
  
  		public void SetLocation(CPos from, SubCell fromSub, CPos to, SubCell toSub)
  		{
  			if (fromCell == from && toCell == to) return;
  			RemoveInfluence();
  			__fromCell = from;
  			__toCell = to;
  			fromSubCell = fromSub;
  			toSubCell = toSub;
  			AddInfluence();
  		}
  
  		const int avgTicksBeforePathing = 5;
  		const int spreadTicksBeforePathing = 5;
  		internal int ticksBeforePathing = 0;
  
  		public Mobile(ActorInitializer init, MobileInfo info)
  		{
  			this.self = init.self;
  			this.Info = info;
  
  			toSubCell = fromSubCell = info.SharesCell ? SubCell.Center : SubCell.FullCell;
  			if (init.Contains<SubCellInit>())
  			{
  				this.fromSubCell = this.toSubCell = init.Get<SubCellInit, SubCell>();
  			}
  
  			if (init.Contains<LocationInit>())
  			{
  				this.__fromCell = this.__toCell = init.Get<LocationInit, CPos>();
  				this.PxPosition = Util.CenterOfCell(fromCell) + info.SubCellOffsets[fromSubCell];
  			}
  
  			this.Facing = init.Contains<FacingInit>() ? init.Get<FacingInit, int>() : info.InitialFacing;
  			this.Altitude = init.Contains<AltitudeInit>() ? init.Get<AltitudeInit, int>() : 0;
  		}
  
  		public void SetPosition(Actor self, CPos cell)
  		{
  			SetLocation(cell,fromSubCell, cell,fromSubCell);
  			PxPosition = Util.CenterOfCell(fromCell) + Info.SubCellOffsets[fromSubCell];
  			FinishedMoving(self);
  		}
  
  		public void SetPxPosition(Actor self, PPos px)
  		{
  			var cell = px.ToCPos();
  			SetLocation(cell,fromSubCell, cell,fromSubCell);
  			PxPosition = px;
  			FinishedMoving(self);
  		}
  
  		public void AdjustPxPosition(Actor self, PPos px)	/* visual hack only */
  		{
  			PxPosition = px;
  		}
  
  		public IEnumerable<IOrderTargeter> Orders { get { yield return new MoveOrderTargeter(Info); } }
  
  		// Note: Returns a valid order even if the unit can't move to the target
  		public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
  		{
  			if (order is MoveOrderTargeter)
  			{
  				if (Info.OnRails) return null;
  				return new Order("Move", self, queued) { TargetLocation = target.CenterLocation.ToCPos() };
  			}
  			return null;
  		}
  
  		public CPos NearestMoveableCell(CPos target)
  		{
  			return NearestMoveableCell(target, 1, 10);
  		}
  
  		public CPos NearestMoveableCell(CPos target, int minRange, int maxRange)
  		{
  			return NearestCell(target, CanEnterCell, minRange, maxRange);
  		}
  
  		public CPos NearestCell(CPos target, Func<CPos, bool> check, int minRange, int maxRange)
  		{
  			if (check(target))
  				return target;
  
  			foreach (var tile in self.World.FindTilesInCircle(target, maxRange))
  				if (check(tile))
  					return tile;
  
  			// Couldn't find a cell
  			return target;
  		}
  
  		void PerformMoveInner(Actor self, CPos targetLocation, bool queued)
  		{
  			var currentLocation = NearestMoveableCell(targetLocation);
  
  			if (!CanEnterCell(currentLocation))
  			{
  				if (queued) self.CancelActivity();
  				return;
  			}
  
  			if (!queued) self.CancelActivity();
  
  			ticksBeforePathing = avgTicksBeforePathing + self.World.SharedRandom.Next(-spreadTicksBeforePathing, spreadTicksBeforePathing);
  
  			self.QueueActivity(new Move(currentLocation, 8));
  
  			self.SetTargetLine(Target.FromCell(currentLocation), Color.Green);
  		}
  
  		protected void PerformMove(Actor self, CPos targetLocation, bool queued)
  		{
  			if (queued)
  				self.QueueActivity(new CallFunc(() => PerformMoveInner(self, targetLocation, true)));
  			else
  
gt: 				PerformMoveInner(self, targetLocation, false);