context: #region Copyright & License Information
  /*
   * Copyright 2007-2019 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, either version 3 of
   * the License, or (at your option) any later version. For more
   * information, see COPYING.
   */
  #endregion
  
  using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.Linq;
  using OpenRA.Support;
  using OpenRA.Traits;
  
  namespace OpenRA.Mods.Common.Traits
  {
  	[Desc("Manages AI MCVs.")]
  	public class McvManagerBotModuleInfo : ConditionalTraitInfo
  	{
  		[Desc("Actor types that are considered MCVs (deploy into base builders).")]
  		public readonly HashSet<string> McvTypes = new HashSet<string>();
  
  		[Desc("Actor types that are considered construction yards (base builders).")]
  		public readonly HashSet<string> ConstructionYardTypes = new HashSet<string>();
  
  		[Desc("Actor types that are able to produce MCVs.")]
  		public readonly HashSet<string> McvFactoryTypes = new HashSet<string>();
  
  		[Desc("Try to maintain at least this many ConstructionYardTypes, build an MCV if number is below this.")]
  		public readonly int MinimumConstructionYardCount = 1;
  
  		[Desc("Delay (in ticks) between looking for and giving out orders to new MCVs.")]
  		public readonly int ScanForNewMcvInterval = 20;
  
  		[Desc("Minimum distance in cells from center of the base when checking for MCV deployment location.")]
  		public readonly int MinBaseRadius = 2;
  
  		[Desc("Maximum distance in cells from center of the base when checking for MCV deployment location.",
  			"Only applies if RestrictMCVDeploymentFallbackToBase is enabled and there's at least one construction yard.")]
  		public readonly int MaxBaseRadius = 20;
  
  		[Desc("Should deployment of additional MCVs be restricted to MaxBaseRadius if explicit deploy locations are missing or occupied?")]
  		public readonly bool RestrictMCVDeploymentFallbackToBase = true;
  
  		public override object Create(ActorInitializer init) { return new McvManagerBotModule(init.Self, this); }
  	}
  
  	public class McvManagerBotModule : ConditionalTrait<McvManagerBotModuleInfo>, IBotTick, IBotPositionsUpdated, IGameSaveTraitData
  	{
  		public CPos GetRandomBaseCenter()
  		{
  			var randomConstructionYard = world.Actors.Where(a => a.Owner == player &&
  				Info.ConstructionYardTypes.Contains(a.Info.Name))
  				.RandomOrDefault(world.LocalRandom);
  
  			return randomConstructionYard != null ? randomConstructionYard.Location : initialBaseCenter;
  		}
  
  		readonly World world;
  		readonly Player player;
  
  		readonly Predicate<Actor> unitCannotBeOrdered;
  
  		IBotPositionsUpdated[] notifyPositionsUpdated;
  		IBotRequestUnitProduction[] requestUnitProduction;
  
  		CPos initialBaseCenter;
  		int scanInterval;
  		bool firstTick = true;
  
  		// MCVs that the bot already knows about. Any MCV not on this list needs to be given an order.
  		List<Actor> activeMCVs = new List<Actor>();
  
  		public McvManagerBotModule(Actor self, McvManagerBotModuleInfo info)
  			: base(info)
  		{
  			world = self.World;
  			player = self.Owner;
  			unitCannotBeOrdered = a => a.Owner != player || a.IsDead || !a.IsInWorld;
  		}
  
  		protected override void TraitEnabled(Actor self)
  		{
  			notifyPositionsUpdated = player.PlayerActor.TraitsImplementing<IBotPositionsUpdated>().ToArray();
  			requestUnitProduction = player.PlayerActor.TraitsImplementing<IBotRequestUnitProduction>().ToArray();
  
  			// Avoid all AIs reevaluating assignments on the same tick, randomize their initial evaluation delay.
  			scanInterval = world.LocalRandom.Next(Info.ScanForNewMcvInterval, Info.ScanForNewMcvInterval * 2);
  		}
  
  		void IBotPositionsUpdated.UpdatedBaseCenter(CPos newLocation)
  		{
  			initialBaseCenter = newLocation;
  		}
  
  		void IBotPositionsUpdated.UpdatedDefenseCenter(CPos newLocation) { }
  
  		void IBotTick.BotTick(IBot bot)
  		{
  			if (firstTick)
  			{
  				DeployMcvs(bot, false);
  				firstTick = false;
  			}
  
  			if (--scanInterval <= 0)
  			{
  				scanInterval = Info.ScanForNewMcvInterval;
  				DeployMcvs(bot, true);
  
  				// No construction yards - Build a new MCV
  				if (ShouldBuildMCV())
  				{
  					var unitBuilder = requestUnitProduction.FirstOrDefault(Exts.IsTraitEnabled);
  					if (unitBuilder != null)
  					{
  						var mcvInfo = AIUtils.GetInfoByCommonName(Info.McvTypes, player);
  						if (unitBuilder.RequestedProductionCount(bot, mcvInfo.Name) == 0)
  							unitBuilder.RequestUnitProduction(bot, mcvInfo.Name);
  					}
  				}
  			}
  		}
  
  		bool ShouldBuildMCV()
  		{
  			// Only build MCV if we don't already have one in the field.
  			var allowedToBuildMCV = AIUtils.CountActorByCommonName(Info.McvTypes, player) == 0;
  			if (!allowedToBuildMCV)
  				return false;
  
  			// Build MCV if we don't have the desired number of construction yards, unless we have no factory (can't build it).
  			return AIUtils.CountBuildingByCommonName(Info.ConstructionYardTypes, player) < Info.MinimumConstructionYardCount &&
  				AIUtils.CountBuildingByCommonName(Info.McvFactoryTypes, player) > 0;
  		}
  
  		void DeployMcvs(IBot bot, bool chooseLocation)
  		{
  			activeMCVs.RemoveAll(unitCannotBeOrdered);
  
  			var newMCVs = world.ActorsHavingTrait<Transforms>()
  				.Where(a => a.Owner == player &&
  					a.IsIdle &&
  					Info.McvTypes.Contains(a.Info.Name) &&
  					!activeMCVs.Contains(a));
  
  			foreach (var a in newMCVs)
  				activeMCVs.Add(a);
  
  			foreach (var mcv in activeMCVs)
  				DeployMcv(bot, mcv, chooseLocation);
  		}
  
  		// Find any MCV and deploy them at a sensible location.
  		void DeployMcv(IBot bot, Actor mcv, bool move)
  		{
  			if (move)
  			{
  				// If we lack a base, we need to make sure we don't restrict deployment of the MCV to the base!
  				var restrictToBase = Info.RestrictMCVDeploymentFallbackToBase && AIUtils.CountBuildingByCommonName(Info.ConstructionYardTypes, player) > 0;
  
  				var transformsInfo = mcv.Info.TraitInfo<TransformsInfo>();
  				var desiredLocation = ChooseMcvDeployLocation(transformsInfo.IntoActor, transformsInfo.Offset, restrictToBase);
  				if (desiredLocation == null)
  					return;
  
  				bot.QueueOrder(new Order("Move", mcv, Target.FromCell(world, desiredLocation.Value), true));
  			}
  
  			// If the MCV has to move first, we can't be sure it reaches the destination alive, so we only
  			// update base and defense center if the MCV is deployed immediately (i.e. at game start).
  			// TODO: This could be adressed via INotifyTransform.
  			foreach (var n in notifyPositionsUpdated)
  			{
  				n.UpdatedBaseCenter(mcv.Location);
  				n.UpdatedDefenseCenter(mcv.Location);
  			}
  
  			bot.QueueOrder(new Order("DeployTransform", mcv, true));
  		}
  
  		CPos? ChooseMcvDeployLocation(string actorType, CVec offset, bool distanceToBaseIsImportant)
  		{
  			var actorInfo = world.Map.Rules.Actors[actorType];
  			var bi = actorInfo.TraitInfoOrDefault<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 = world.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(world.LocalRandom);
  
  				foreach (var cell in cells)
  				{
  					if (!world.CanPlaceBuilding(cell + offset, actorInfo, bi, null))
  						continue;
  
  					if (distanceToBaseIsImportant && !bi.IsCloseEnoughToBase(world, player, actorInfo, cell))
  						continue;
  
  					return cell;
  				}
  
  				return null;
  			};
  
  			var baseCenter = GetRandomBaseCenter();
  
  			return findPos(baseCenter, baseCenter, Info.MinBaseRadius,
  				distanceToBaseIsImportant ? Info.MaxBaseRadius : world.Map.Grid.MaximumTileSearchRange);
  		}
  
  		List<MiniYamlNode> IGameSaveTraitData.IssueTraitData(Actor self)
  		{
  			if (IsTraitDisabled)
  				return null;
  
  			return new List<MiniYamlNode>()
  			{
  				new MiniYamlNode("InitialBaseCenter", FieldSaver.FormatValue(initialBaseCenter)),
  				new MiniYamlNode("ActiveMCVs", FieldSaver.FormatValue(activeMCVs.Select(a => a.ActorID).ToArray()))
  			};
  		}
  
  		void IGameSaveTraitData.ResolveTraitData(Actor self, List<MiniYamlNode> data)
  		{
  			if (self.World.IsReplay)
  				return;
  
  			var initialBaseCenterNode = data.FirstOrDefault(n => n.Key == "InitialBaseCenter");
  			if (initialBaseCenterNode != null)
  				initialBaseCenter = FieldLoader.GetValue<CPos>("InitialBaseCenter", initialBaseCenterNode.Value.Value);
  
  			var activeMCVsNode = data.FirstOrDefault(n => n.Key == "ActiveMCVs");
  
gt: 			if (activeMCVsNode != null)