context: #region Copyright & License Information
  /*
   * Copyright 2007-2014 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.Mods.RA.Activities;
  using OpenRA.Mods.RA.Air;
  using OpenRA.Mods.RA.Buildings;
  using OpenRA.Mods.RA.Move;
  using OpenRA.Primitives;
  using OpenRA.Support;
  using OpenRA.Traits;
  
  namespace OpenRA.Mods.RA.AI
  {
  	public sealed class HackyAIInfo : IBotInfo, ITraitInfo
  	{
  		public readonly string Name = "Unnamed Bot";
  		public readonly int SquadSize = 8;
  
  		public readonly string[] BuildingQueues = { "Building" };
  		public readonly string[] DefenseQueues = { "Defense" };
  
  		public readonly int AssignRolesInterval = 20;
  		public readonly int RushInterval = 600;
  		public readonly int AttackForceInterval = 30;
  
  		[Desc("How long to wait (in ticks) between structure production checks when there is no active production.")]
  		public readonly int StructureProductionInactiveDelay = 125;
  
  		[Desc("How long to wait (in ticks) between structure production checks ticks when actively building things.")]
  		public readonly int StructureProductionActiveDelay = 10;
  
  		[Desc("Minimum range at which to build defensive structures near a combat hotspot.")]
  		public readonly int MinimumDefenseRadius = 5;
  
  		[Desc("Maximum range at which to build defensive structures near a combat hotspot.")]
  		public readonly int MaximumDefenseRadius = 20;
  
  		[Desc("Try to build another production building if there is too much cash.")]
  		public readonly int NewProductionCashThreshold = 5000;
  
  		[Desc("Only produce units as long as there are less than this amount of units idling inside the base.")]
  		public readonly int IdleBaseUnitsMaximum = 12;
  
  		[Desc("Radius in cells around enemy BaseBuilder (Construction Yard) where AI scans for targets to rush.")]
  		public readonly int RushAttackScanRadius = 15;
  
  		[Desc("Radius in cells around the base that should be scanned for units to be protected.")]
  		public readonly int ProtectUnitScanRadius = 15;
  
  		[Desc("Radius in cells around a factory scanned for rally points by the AI.")]
  		public readonly int RallyPointScanRadius = 8;
  
  		[Desc("Radius in cells around the center of the base to expand.")]
  		public readonly int MaxBaseRadius = 20;
  
  		public readonly string[] UnitQueues = { "Vehicle", "Infantry", "Plane", "Ship", "Aircraft" };
  		public readonly bool ShouldRepairBuildings = true;
  
  		string IBotInfo.Name { get { return this.Name; } }
  
  		[FieldLoader.LoadUsing("LoadUnits")]
  		public readonly Dictionary<string, float> UnitsToBuild = null;
  
  		[FieldLoader.LoadUsing("LoadBuildings")]
  		public readonly Dictionary<string, float> BuildingFractions = null;
  
  		[FieldLoader.LoadUsing("LoadUnitsCommonNames")]
  		public readonly Dictionary<string, string[]> UnitsCommonNames = null;
  
  		[FieldLoader.LoadUsing("LoadBuildingsCommonNames")]
  		public readonly Dictionary<string, string[]> BuildingCommonNames = null;
  
  		[FieldLoader.LoadUsing("LoadBuildingLimits")]
  		public readonly Dictionary<string, int> BuildingLimits = null;
  
  		static object LoadList<T>(MiniYaml y, string field)
  		{
  			var nd = y.ToDictionary();
  			return nd.ContainsKey(field)
  				? nd[field].ToDictionary(my => FieldLoader.GetValue<T>(field, my.Value))
  				: new Dictionary<string, T>();
  		}
  
  		static object LoadUnits(MiniYaml y) { return LoadList<float>(y, "UnitsToBuild"); }
  		static object LoadBuildings(MiniYaml y) { return LoadList<float>(y, "BuildingFractions"); }
  
  		static object LoadUnitsCommonNames(MiniYaml y) { return LoadList<string[]>(y, "UnitsCommonNames"); }
  		static object LoadBuildingsCommonNames(MiniYaml y) { return LoadList<string[]>(y, "BuildingCommonNames"); }
  
  		static object LoadBuildingLimits(MiniYaml y) { return LoadList<int>(y, "BuildingLimits"); }
  
  		public object Create(ActorInitializer init) { return new HackyAI(this, init); }
  	}
  
  	public class Enemy { public int Aggro; }
  
  	public enum BuildingType { Building, Defense, Refinery }
  
  	public sealed class HackyAI : ITick, IBot, INotifyDamage
  	{
  		public MersenneTwister random { get; private set; }
  		public readonly HackyAIInfo Info;
  		public CPos baseCenter { get; private set; }
  		public Player p { get; private set; }
  
  		PowerManager playerPower;
  		SupportPowerManager supportPowerMngr;
  		PlayerResources playerResource;
  		bool enabled;
  		int ticks;
  
  		HashSet<int> resourceTypeIndices;
  
  		RushFuzzy rushFuzzy = new RushFuzzy();
  
  		Cache<Player, Enemy> aggro = new Cache<Player, Enemy>(_ => new Enemy());
  		List<BaseBuilder> builders = new List<BaseBuilder>();
  
  		List<Squad> squads = new List<Squad>();
  		List<Actor> unitsHangingAroundTheBase = new List<Actor>();
  
  		// Units that the ai already knows about. Any unit not on this list needs to be given a role.
  		List<Actor> activeUnits = new List<Actor>();
  
  		public const int feedbackTime = 30;		// ticks; = a bit over 1s. must be >= netlag.
  
  		public readonly World world;
  		public Map Map { get { return world.Map; } }
  		IBotInfo IBot.Info { get { return this.Info; } }
  
  		public HackyAI(HackyAIInfo info, ActorInitializer init)
  		{
  			Info = info;
  			world = init.world;
  		}
  
  		public static void BotDebug(string s, params object[] args)
  		{
  			if (Game.Settings.Debug.BotDebug)
  				Game.Debug(s, args);
  		}
  
  		// Called by the host's player creation code
  		public void Activate(Player p)
  		{
  			this.p = p;
  			enabled = true;
  			playerPower = p.PlayerActor.Trait<PowerManager>();
  			supportPowerMngr = p.PlayerActor.Trait<SupportPowerManager>();
  			playerResource = p.PlayerActor.Trait<PlayerResources>();
  
  			foreach (var building in Info.BuildingQueues) 
  				builders.Add(new BaseBuilder(this, building, p, playerPower, playerResource));
  			foreach (var defense in Info.DefenseQueues) 
  				builders.Add(new BaseBuilder(this, defense, p, playerPower, playerResource));
  
  			random = new MersenneTwister((int)p.PlayerActor.ActorID);
  
  			resourceTypeIndices = new HashSet<int>(
  				Map.Rules.Actors["world"].Traits
  				.WithInterface<ResourceTypeInfo>()
  				.Select(t => world.TileSet.GetTerrainIndex(t.TerrainType)));
  		}
  
  		ActorInfo ChooseRandomUnitToBuild(ProductionQueue queue)
  		{
  			var buildableThings = queue.BuildableItems();
  			if (!buildableThings.Any())
  				return null;
  
  			var unit = buildableThings.ElementAtOrDefault(random.Next(buildableThings.Count()));
  			return HasAdequateAirUnits(unit) ? unit : null;
  		}
  
  		ActorInfo ChooseUnitToBuild(ProductionQueue queue)
  		{
  			var buildableThings = queue.BuildableItems();
  			if (!buildableThings.Any())
  				return null;
  
  			var myUnits = p.World
  				.ActorsWithTrait<IPositionable>()
  				.Where(a => a.Actor.Owner == p)
  				.Select(a => a.Actor.Info.Name).ToArray();
  
  			foreach (var unit in Info.UnitsToBuild.Shuffle(random))
  				if (buildableThings.Any(b => b.Name == unit.Key))
  					if (myUnits.Count(a => a == unit.Key) < unit.Value * myUnits.Length)
  						if (HasAdequateAirUnits(Map.Rules.Actors[unit.Key]))
  							return Map.Rules.Actors[unit.Key];
  
  			return null;
  		}
  
  		int CountBuilding(string frac, Player owner)
  		{
  			return world.ActorsWithTrait<Building>()
  				.Count(a => a.Actor.Owner == owner && a.Actor.Info.Name == frac);
  		}
  
  		int CountUnits(string unit, Player owner)
  		{
  			return world.ActorsWithTrait<IPositionable>()
  				.Count(a => a.Actor.Owner == owner && a.Actor.Info.Name == unit);
  		}
  
  		int? CountBuildingByCommonName(string commonName, Player owner)
  		{
  			if (!Info.BuildingCommonNames.ContainsKey(commonName))
  				return null;
  
  			return world.ActorsWithTrait<Building>()
  				.Count(a => a.Actor.Owner == owner && Info.BuildingCommonNames[commonName].Contains(a.Actor.Info.Name));
  		}
  
  		public ActorInfo GetBuildingInfoByCommonName(string commonName, Player owner)
  		{
  			if (commonName == "ConstructionYard")
  				return Map.Rules.Actors.Where(k => Info.BuildingCommonNames[commonName].Contains(k.Key)).Random(random).Value;
  
  			return GetInfoByCommonName(Info.BuildingCommonNames, commonName, owner);
  		}
  
  		public ActorInfo GetUnitInfoByCommonName(string commonName, Player owner)
  		{
  			return GetInfoByCommonName(Info.UnitsCommonNames, commonName, owner);
  		}
  
  		public ActorInfo GetInfoByCommonName(Dictionary<string, string[]> names, string commonName, Player owner)
  		{
  			if (!names.Any() || !names.ContainsKey(commonName))
  				throw new InvalidOperationException("Can't find {0} in the HackyAI UnitsCommonNames definition.".F(commonName));
  
  			return Map.Rules.Actors.Where(k => names[commonName].Contains(k.Key)).Random(random).Value;
  		}
  
  		public bool HasAdequateFact()
  		{
  			// Require at least one construction yard, unless we have no vehicles factory (can't build it).
  			return CountBuildingByCommonName("ConstructionYard", p) > 0 ||
  				CountBuildingByCommonName("VehiclesFactory", p) == 0;
  		}
  
  		public bool HasAdequateProc()
  		{
  			// Require at least one refinery, unless we have no power (can't build it).
  			return CountBuildingByCommonName("Refinery", p) > 0 ||
  				CountBuildingByCommonName("Power", p) == 0;
  		}
  
  		public bool HasMinimumProc()
  		{
  			// Require at least two refineries, unless we have no power (can't build it)
  			// or barracks (higher priority?)
  			return CountBuildingByCommonName("Refinery", p) >= 2 ||
  				CountBuildingByCommonName("Power", p) == 0 ||
  				CountBuildingByCommonName("Barracks", p) == 0;
  		}
  
  		// For mods like RA (number of building must match the number of aircraft)
  		bool HasAdequateAirUnits(ActorInfo actorInfo)
  		{
  			if (!actorInfo.Traits.Contains<ReloadsInfo>() && actorInfo.Traits.Contains<LimitedAmmoInfo>() 
  				&& actorInfo.Traits.Contains<AircraftInfo>())
  			{
  				var countOwnAir = CountUnits(actorInfo.Name, p);
  				var countBuildings = CountBuilding(actorInfo.Traits.Get<AircraftInfo>().RearmBuildings.FirstOrDefault(), p);
  				if (countOwnAir >= countBuildings)
  					return false;
  			}
  
  			return true;
  		}
  
  		CPos defenseCenter;
  		public CPos? ChooseBuildLocation(string actorType, bool distanceToBaseIsImportant, BuildingType type)
  		{
  			var bi = Map.Rules.Actors[actorType].Traits.GetOrDefault<BuildingInfo>();
  			if (bi == null)
  				return null;
  
  			// Find the buildable cell that is closest to pos and centered around center
  			Func<CPos, CPos, int, int, CPos?> findPos = (center, target, minRange, maxRange) =>
  			{
  				var cells = Map.FindTilesInAnnulus(center, minRange, maxRange);
  
  				// Sort by distance to target if we have one
  				if (center != target)
  					cells = cells.OrderBy(c => (c - target).LengthSquared);
  				else
  					cells = cells.Shuffle(random);
  
  				foreach (var cell in cells)
  				{
  					if (!world.CanPlaceBuilding(actorType, bi, cell, null))
  						continue;
  
  					if (distanceToBaseIsImportant && !bi.IsCloseEnoughToBase(world, p, actorType, cell))
  						continue;
  
  					return cell;
  				}
  
  				return null;
  			};
  
  			switch (type)
  			{
  				case BuildingType.Defense:
  
  					// Build near the closest enemy structure
  					var closestEnemy = world.Actors.Where(a => !a.Destroyed && a.HasTrait<Building>() && p.Stances[a.Owner] == Stance.Enemy)
  						.ClosestTo(world.Map.CenterOfCell(defenseCenter));
  
  					var targetCell = closestEnemy != null ? closestEnemy.Location : baseCenter;
  					return findPos(defenseCenter, targetCell, Info.MinimumDefenseRadius, Info.MaximumDefenseRadius);
  
  				case BuildingType.Refinery:
  
  					// Try and place the refinery near a resource field
  					var nearbyResources = Map.FindTilesInCircle(baseCenter, Info.MaxBaseRadius)
  						.Where(a => resourceTypeIndices.Contains(Map.GetTerrainIndex(a)))
  						.Shuffle(random);
  
  					foreach (var c in nearbyResources)
  					{
  						var found = findPos(c, baseCenter, 0, Info.MaxBaseRadius);
  						if (found != null)
  							return found;
  					}
  
  					// Try and find a free spot somewhere else in the base
  					return findPos(baseCenter, baseCenter, 0, Info.MaxBaseRadius);
  
  				case BuildingType.Building:
  					return findPos(baseCenter, baseCenter, 0, distanceToBaseIsImportant ? Info.MaxBaseRadius : Map.MaxTilesInCircleRange);
  			}
  
  			// Can't find a build location
  			return null;		
  		}
  
  		public void Tick(Actor self)
  		{
  			if (!enabled)
  				return;
  
  			ticks++;
  
  			if (ticks == 1)
  				InitializeBase(self);
  
  			if (ticks % feedbackTime == 0)
  				ProductionUnits(self);
  			
  			AssignRolesToIdleUnits(self);
  			SetRallyPointsForNewProductionBuildings(self);
  			TryToUseSupportPower(self);
  
  			foreach (var b in builders)
  				b.Tick();
  		}
  
  		internal Actor ChooseEnemyTarget()
  		{
  			if (p.WinState != WinState.Undefined)
  				return null;
  
  			var liveEnemies = world.Players
  				.Where(q => p != q && p.Stances[q] == Stance.Enemy && q.WinState == WinState.Undefined);
  
  			if (!liveEnemies.Any())
  				return null;
  
  			var leastLikedEnemies = liveEnemies
  				.GroupBy(e => aggro[e].Aggro)
  				.MaxByOrDefault(g => g.Key);
  
  			var enemy = (leastLikedEnemies != null) ?
  				leastLikedEnemies.Random(random) : liveEnemies.FirstOrDefault();
  
  			// Pick something worth attacking owned by that player
  			var target = world.Actors
  
gt: 				.Where(a => a.Owner == enemy && a.HasTrait<IOccupySpace>())