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.GameRules;
  using OpenRA.Traits;
  
  namespace OpenRA.Mods.Common.Traits
  {
  	public abstract class AttackBaseInfo : UpgradableTraitInfo, ITraitInfo
  	{
  		[Desc("Armament names")]
  		public readonly string[] Armaments = { "primary", "secondary" };
  
  		public readonly string Cursor = "attack";
  		public readonly string OutsideRangeCursor = "attackoutsiderange";
  
  		[Desc("Does the attack type require the attacker to enter the target's cell?")]
  		public readonly bool AttackRequiresEnteringCell = false;
  
  		[Desc("Does not care about shroud or fog. Enables the actor to launch an attack against a target even if he has no visibility of it.")]
  		public readonly bool IgnoresVisibility = false;
  
  		public abstract object Create(ActorInitializer init);
  	}
  
  	public abstract class AttackBase : UpgradableTrait<AttackBaseInfo>, IIssueOrder, IResolveOrder, IOrderVoice, ISync
  	{
  		[Sync] public bool IsAttacking { get; internal set; }
  		public IEnumerable<Armament> Armaments { get { return getArmaments(); } }
  
  		protected Lazy<IFacing> facing;
  		protected Lazy<Building> building;
  		protected Lazy<IPositionable> positionable;
  		protected Func<IEnumerable<Armament>> getArmaments;
  
  		readonly Actor self;
  
  		public AttackBase(Actor self, AttackBaseInfo info)
  			: base(info)
  		{
  			this.self = self;
  
  			var armaments = Exts.Lazy(() => self.TraitsImplementing<Armament>()
  				.Where(a => info.Armaments.Contains(a.Info.Name)).ToArray());
  
  			getArmaments = () => armaments.Value;
  
  			facing = Exts.Lazy(() => self.TraitOrDefault<IFacing>());
  			building = Exts.Lazy(() => self.TraitOrDefault<Building>());
  			positionable = Exts.Lazy(() => self.Trait<IPositionable>());
  		}
  
  		protected virtual bool CanAttack(Actor self, Target target)
  		{
  			if (!self.IsInWorld || IsTraitDisabled)
  				return false;
  
  			if (!HasAnyValidWeapons(target))
  				return false;
  
  			// Building is under construction or is being sold
  			if (building.Value != null && !building.Value.BuildComplete)
  				return false;
  
  			if (!target.IsValidFor(self))
  				return false;
  
  			if (Armaments.All(a => a.IsReloading))
  				return false;
  
  			if (self.IsDisabled())
  				return false;
  
  			return true;
  		}
  
  		public virtual void DoAttack(Actor self, Target target)
  		{
  			if (!CanAttack(self, target))
  				return;
  
  			foreach (var a in Armaments)
  				a.CheckFire(self, facing.Value, target);
  		}
  
  		public IEnumerable<IOrderTargeter> Orders
  		{
  			get
  			{
  				var armament = Armaments.FirstOrDefault(a => a.Weapon.Warheads.Any(w => (w is DamageWarhead)));
  				if (armament == null || IsTraitDisabled)
  					yield break;
  
  				var negativeDamage = (armament.Weapon.Warheads.FirstOrDefault(w => (w is DamageWarhead)) as DamageWarhead).Damage < 0;
  				yield return new AttackOrderTargeter(this, "Attack", 6, negativeDamage);
  			}
  		}
  
  		public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
  		{
  			if (order is AttackOrderTargeter)
  			{
  				switch (target.Type)
  				{
  					case TargetType.Actor:
  						return new Order("Attack", self, queued) { TargetActor = target.Actor };
  					case TargetType.FrozenActor:
  						return new Order("Attack", self, queued) { ExtraData = target.FrozenActor.ID };
  					case TargetType.Terrain:
  						return new Order("Attack", self, queued) { TargetLocation = self.World.Map.CellContaining(target.CenterPosition) };
  				}
  			}
  
  			return null;
  		}
  
  		public virtual void ResolveOrder(Actor self, Order order)
  		{
  			if (order.OrderString == "Attack")
  			{
  				var target = self.ResolveFrozenActorOrder(order, Color.Red);
  				if (!target.IsValidFor(self))
  					return;
  
  				self.SetTargetLine(target, Color.Red);
  				AttackTarget(target, order.Queued, true);
  			}
  		}
  
  		public string VoicePhraseForOrder(Actor self, Order order)
  		{
  			return order.OrderString == "Attack" ? "Attack" : null;
  		}
  
  		public abstract Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove);
  
  		public bool HasAnyValidWeapons(Target t)
  		{
  			if (IsTraitDisabled)
  				return false;
  
  			if (Info.AttackRequiresEnteringCell && !positionable.Value.CanEnterCell(t.Actor.Location, null, false))
  				return false;
  
  			return Armaments.Any(a => a.Weapon.IsValidAgainst(t, self.World, self));
  		}
  
  		public WRange GetMinimumRange()
  		{
  			if (IsTraitDisabled)
  				return WRange.Zero;
  
  			return Armaments.Where(a => !a.IsTraitDisabled)
  				.Select(a => a.Weapon.MinRange).Min();
  		}
  
  		public WRange GetMaximumRange()
  		{
  			if (IsTraitDisabled)
  				return WRange.Zero;
  
  			return Armaments.Where(a => !a.IsTraitDisabled)
  				.Select(a => a.Weapon.Range)
  				.Append(WRange.Zero).Max();
  		}
  
  		public Armament ChooseArmamentForTarget(Target t) { return Armaments.FirstOrDefault(a => a.Weapon.IsValidAgainst(t, self.World, self)); }
  
  		public void AttackTarget(Target target, bool queued, bool allowMove)
  		{
  			if (self.IsDisabled() || IsTraitDisabled)
  				return;
  
  			if (!target.IsValidFor(self))
  				return;
  
  			if (!queued)
  				self.CancelActivity();
  
  			self.QueueActivity(GetAttackActivity(self, target, allowMove));
  		}
  
  		public bool IsReachableTarget(Target target, bool allowMove)
  		{
  			return HasAnyValidWeapons(target)
  				&& (target.IsInRange(self.CenterPosition, GetMaximumRange()) || (allowMove && self.HasTrait<IMove>()));
  		}
  
  		class AttackOrderTargeter : IOrderTargeter
  		{
  			readonly bool negativeDamage;
  			readonly AttackBase ab;
  
  			public AttackOrderTargeter(AttackBase ab, string order, int priority, bool negativeDamage)
  			{
  				this.ab = ab;
  				this.OrderID = order;
  				this.OrderPriority = priority;
  				this.negativeDamage = negativeDamage;
  			}
  
  			public string OrderID { get; private set; }
  			public int OrderPriority { get; private set; }
  			public bool OverrideSelection { get { return true; } }
  
  			bool CanTargetActor(Actor self, Target target, TargetModifiers modifiers, ref string cursor)
  			{
  				IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue);
  
  				var a = ab.ChooseArmamentForTarget(target);
  				cursor = a != null && !target.IsInRange(self.CenterPosition, a.Weapon.Range)
  					? ab.Info.OutsideRangeCursor
  					: ab.Info.Cursor;
  
  				if (target.Type == TargetType.Actor && target.Actor == self)
  					return false;
  
  				if (!ab.HasAnyValidWeapons(target))
  					return false;
  
  				if (modifiers.HasModifier(TargetModifiers.ForceAttack))
  					return true;
  
  				if (modifiers.HasModifier(TargetModifiers.ForceMove))
  					return false;
  
  				if (target.RequiresForceFire)
  					return false;
  
  				var targetableRelationship = negativeDamage ? Stance.Ally : Stance.Enemy;
  
  				var owner = target.Type == TargetType.FrozenActor ? target.FrozenActor.Owner : target.Actor.Owner;
  				return self.Owner.Stances[owner] == targetableRelationship;
  			}
  
  			bool CanTargetLocation(Actor self, CPos location, List<Actor> actorsAtLocation, TargetModifiers modifiers, ref string cursor)
  			{
  				if (!self.World.Map.Contains(location))
  					return false;
  
  				IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue);
  
  				cursor = ab.Info.Cursor;
  
  				if (negativeDamage)
  					return false;
  
  				if (!ab.HasAnyValidWeapons(Target.FromCell(self.World, location)))
  					return false;
  
  				if (modifiers.HasModifier(TargetModifiers.ForceAttack))
  				{
  					var targetRange = (self.World.Map.CenterOfCell(location) - self.CenterPosition).HorizontalLengthSquared;
  
gt: 					if (targetRange > ab.GetMaximumRange().RangeSquared)