context: #region Copyright & License Information
  /*
   * Copyright 2007-2012 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.Collections.Generic;
  using System.Drawing;
  using System.Linq;
  using OpenRA.FileFormats;
  using OpenRA.Mods.RA.Activities;
  using OpenRA.Mods.RA.Air;
  using OpenRA.Mods.RA.Buildings;
  using OpenRA.Mods.RA.Effects;
  using OpenRA.Mods.RA.Move;
  using OpenRA.Network;
  using OpenRA.Traits;
  using OpenRA.Widgets;
  
  namespace OpenRA.Mods.RA.Missions
  {
  	class Allies02ScriptInfo : TraitInfo<Allies02Script>, Requires<SpawnMapActorsInfo> { }
  
  	class Allies02Script : IHasObjectives, IWorldLoaded, ITick
  	{
  		public event ObjectivesUpdatedEventHandler OnObjectivesUpdated = notify => { };
  
  		public IEnumerable<Objective> Objectives { get { return objectives.Values; } }
  
  		Dictionary<int, Objective> objectives = new Dictionary<int, Objective>()
  		{
  			{ FindEinsteinID, new Objective(ObjectiveType.Primary, FindEinstein, ObjectiveStatus.InProgress) },
  			{ DestroySamSitesID, new Objective(ObjectiveType.Primary, DestroySamSites, ObjectiveStatus.InProgress) },
  			{ ExtractEinsteinID, new Objective(ObjectiveType.Primary, ExtractEinstein, ObjectiveStatus.Inactive) },
  			{ MaintainPresenceID, new Objective(ObjectiveType.Primary, MaintainPresence, ObjectiveStatus.InProgress) },
  			{ FewDeathsID, new Objective(ObjectiveType.Secondary, "", ObjectiveStatus.InProgress) }
  		};
  
  		const int FindEinsteinID = 0;
  		const int DestroySamSitesID = 1;
  		const int ExtractEinsteinID = 2;
  		const int MaintainPresenceID = 3;
  		const int FewDeathsID = 4;
  
  		const string FindEinstein = "Find Einstein's crashed helicopter. Tanya must survive.";
  		const string DestroySamSites = "Destroy the SAM sites. Tanya must survive.";
  		const string ExtractEinstein = "Wait for the helicopter and extract Einstein. Tanya and Einstein must survive.";
  		const string MaintainPresence = "Maintain an Allied presence in the area. Reinforcements will arrive soon.";
  		const string FewDeathsTemplate = "Lose fewer than {0}/{1} units.";
  
  		const int DeathsThreshold = 200;
  
  		Actor sam1;
  		Actor sam2;
  		Actor sam3;
  		Actor sam4;
  		Actor tanya;
  		Actor einstein;
  		Actor engineer;
  
  		Actor chinookHusk;
  		Actor allies2BasePoint;
  		Actor reinforcementsEntryPoint;
  		Actor extractionLZEntryPoint;
  		Actor extractionLZ;
  		Actor badgerEntryPoint1;
  		Actor badgerEntryPoint2;
  		Actor badgerDropPoint1;
  		Actor badgerDropPoint2;
  		Actor badgerDropPoint3;
  		Actor parabombPoint1;
  		Actor parabombPoint2;
  		Actor sovietRallyPoint;
  		Actor flamersEntryPoint;
  		Actor tanksEntryPoint;
  		Actor townPoint;
  		Actor sovietTownAttackPoint1;
  		Actor sovietTownAttackPoint2;
  		Actor yakEntryPoint;
  		Actor yakAttackPoint;
  		Actor yak;
  
  		Actor einsteinChinook;
  
  		World world;
  		Player allies;
  		Player allies1;
  		Player allies2;
  		Player soviets;
  
  		Actor sovietBarracks;
  		Actor sovietWarFactory;
  
  		CountdownTimer reinforcementsTimer;
  		CountdownTimerWidget reinforcementsTimerWidget;
  
  		const string InfantryQueueName = "Infantry";
  		const string VehicleQueueName = "Vehicle";
  		static readonly string[] SovietInfantry = { "e1", "e2", "e3" };
  		static readonly string[] SovietVehicles1 = { "3tnk" };
  		static readonly string[] SovietVehicles2 = { "3tnk", "v2rl" };
  		const int SovietVehiclesUpgradeTicks = 1500 * 4;
  		const int SovietGroupSize = 20;
  
  		const int ReinforcementsTicks = 1500 * 12;
  		static readonly string[] Reinforcements =
  		{
  			"2tnk", "2tnk", "2tnk", "2tnk", "2tnk", "2tnk",
  			"1tnk", "1tnk",
  			"jeep",
  			"e1", "e1", "e1", "e1",
  			"e3", "e3",
  			"mcv",
  			"truk", "truk", "truk", "truk", "truk", "truk"
  		};
  		int currentReinforcement = -1;
  
  		const int ParabombTicks = 750;
  
  		const int FlamersTicks = 1500 * 2;
  		static readonly string[] Flamers = { "e4", "e4", "e4", "e4", "e4" };
  		const string ApcName = "apc";
  
  		const int ParatroopersTicks = 1500 * 5;
  		static readonly string[] Badger1Passengers = { "e1", "e1", "e1", "e2", "3tnk" };
  		static readonly string[] Badger2Passengers = { "e1", "e1", "e1", "e2", "e2" };
  		static readonly string[] Badger3Passengers = { "e1", "e1", "e1", "e2", "e2" };
  
  		const int TanksTicks = 1500 * 11;
  		static readonly string[] Tanks = { "3tnk", "3tnk", "3tnk", "3tnk", "3tnk", "3tnk", "3tnk", "3tnk" };
  
  		const string SignalFlareName = "flare";
  		const string YakName = "yak";
  
  		const int AlliedTownTransferRange = 15;
  		const int SovietTownAttackGroupRange = 5;
  		const int SovietTownMoveNearEnough = 3;
  
  		void MissionFailed(string text)
  		{
  			if (allies1.WinState != WinState.Undefined)
  			{
  				return;
  			}
  			allies1.WinState = allies2.WinState = WinState.Lost;
  			if (reinforcementsTimer != null)
  			{
  				reinforcementsTimerWidget.Visible = false;
  			}
  			foreach (var actor in world.Actors.Where(a => a.IsInWorld && (a.Owner == allies1 || a.Owner == allies2) && !a.IsDead()))
  			{
  				actor.Kill(actor);
  			}
  			Game.AddChatLine(Color.Red, "Mission failed", text);
  			Sound.Play("misnlst1.aud");
  		}
  
  		void MissionAccomplished(string text)
  		{
  			if (allies1.WinState != WinState.Undefined)
  			{
  				return;
  			}
  			allies1.WinState = allies2.WinState = WinState.Won;
  			if (reinforcementsTimer != null)
  			{
  				reinforcementsTimerWidget.Visible = false;
  			}
  			Game.AddChatLine(Color.Blue, "Mission accomplished", text);
  			Sound.Play("misnwon1.aud");
  		}
  
  		public void Tick(Actor self)
  		{
  			if (allies1.WinState != WinState.Undefined)
  			{
  				return;
  			}
  			if (world.FrameNumber % 50 == 1 && chinookHusk.IsInWorld)
  			{
  				world.Add(new Smoke(world, chinookHusk.CenterLocation, "smoke_m"));
  			}
  			if (world.FrameNumber == 1)
  			{
  				InitializeSovietFactories();
  				StartReinforcementsTimer();
  			}
  			reinforcementsTimer.Tick();
  			if (world.FrameNumber == ParatroopersTicks)
  			{
  				MissionUtils.Paradrop(world, soviets, Badger1Passengers, badgerEntryPoint1.Location, badgerDropPoint1.Location);
  				MissionUtils.Paradrop(world, soviets, Badger2Passengers, badgerEntryPoint1.Location + new CVec(3, 0), badgerDropPoint2.Location);
  				MissionUtils.Paradrop(world, soviets, Badger3Passengers, badgerEntryPoint1.Location + new CVec(6, 0), badgerDropPoint3.Location);
  			}
  			if (world.FrameNumber == ParabombTicks)
  			{
  				MissionUtils.Parabomb(world, soviets, badgerEntryPoint2.Location, parabombPoint1.Location);
  				MissionUtils.Parabomb(world, soviets, badgerEntryPoint2.Location + new CVec(0, 3), parabombPoint2.Location);
  			}
  			if (allies1 != allies2)
  			{
  				if (world.FrameNumber == TanksTicks)
  				{
  					RushSovietUnits();
  				}
  				if (world.FrameNumber == FlamersTicks)
  				{
  					RushSovietFlamers();
  				}
  				if (yak == null || (yak != null && !yak.IsDead() && (yak.GetCurrentActivity() is FlyCircle || yak.IsIdle)))
  				{
  					var alliedUnitsNearYakPoint = world.FindAliveCombatantActorsInCircle(yakAttackPoint.CenterLocation, 10)
  						.Where(a => a.Owner != soviets && a.HasTrait<IMove>() && a != tanya && a != einstein && a != engineer);
  					if (alliedUnitsNearYakPoint.Any())
  					{
  						YakStrafe(alliedUnitsNearYakPoint);
  					}
  				}
  			}
  			if (currentReinforcement > -1 && currentReinforcement < Reinforcements.Length && world.FrameNumber % 25 == 0)
  			{
  				SpawnAlliedUnit(Reinforcements[currentReinforcement++]);
  			}
  			if (world.FrameNumber % 25 == 0)
  			{
  				BuildSovietUnits();
  				ManageSovietUnits();
  			}
  			UpdateDeaths();
  			if (objectives[FindEinsteinID].Status == ObjectiveStatus.InProgress)
  			{
  				if (AlliesNearTown())
  				{
  					objectives[FindEinsteinID].Status = ObjectiveStatus.Completed;
  					OnObjectivesUpdated(true);
  					TransferTownUnitsToAllies();
  					SovietsAttackTown();
  				}
  			}
  			if (objectives[DestroySamSitesID].Status == ObjectiveStatus.InProgress)
  			{
  				if ((sam1.Destroyed || sam1.Owner != soviets)
  					&& (sam2.Destroyed || sam2.Owner != soviets)
  					&& (sam3.Destroyed || sam3.Owner != soviets)
  					&& (sam4.Destroyed || sam4.Owner != soviets))
  				{
  					objectives[DestroySamSitesID].Status = ObjectiveStatus.Completed;
  					objectives[ExtractEinsteinID].Status = ObjectiveStatus.InProgress;
  					OnObjectivesUpdated(true);
  					SpawnSignalFlare();
  					Sound.Play("flaren1.aud");
  					ExtractEinsteinAtLZ();
  				}
  			}
  			if (objectives[ExtractEinsteinID].Status == ObjectiveStatus.InProgress && einsteinChinook != null)
  			{
  				if (einsteinChinook.Destroyed)
  				{
  					objectives[ExtractEinsteinID].Status = ObjectiveStatus.Failed;
  					objectives[MaintainPresenceID].Status = ObjectiveStatus.Failed;
  					OnObjectivesUpdated(true);
  					MissionFailed("The extraction helicopter was destroyed.");
  				}
  				else if (!world.Map.IsInMap(einsteinChinook.Location) && einsteinChinook.Trait<Cargo>().Passengers.Contains(einstein))
  				{
  					objectives[ExtractEinsteinID].Status = ObjectiveStatus.Completed;
  					objectives[MaintainPresenceID].Status = ObjectiveStatus.Completed;
  					if (objectives[FewDeathsID].Status == ObjectiveStatus.InProgress)
  					{
  						objectives[FewDeathsID].Status = ObjectiveStatus.Completed;
  					}
  					OnObjectivesUpdated(true);
  					MissionAccomplished("Einstein was rescued.");
  				}
  			}
  			if (tanya.Destroyed)
  			{
  				MissionFailed("Tanya was killed.");
  			}
  			else if (einstein.Destroyed)
  			{
  				MissionFailed("Einstein was killed.");
  			}
  			world.AddFrameEndTask(w =>
  			{
  				if (!world.FindAliveCombatantActorsInCircle(allies2BasePoint.CenterLocation, 20)
  					.Any(a => a.HasTrait<Building>() && !a.HasTrait<Wall>() && (a.Owner == allies || a.Owner == allies2)))
  				{
  					objectives[MaintainPresenceID].Status = ObjectiveStatus.Failed;
  					OnObjectivesUpdated(true);
  					MissionFailed("The Allied reinforcements have been defeated.");
  				}
  			});
  		}
  
  		void UpdateDeaths()
  		{
  			var unitDeaths = allies1.Deaths + allies2.Deaths;
  			objectives[FewDeathsID].Text = FewDeathsTemplate.F(unitDeaths, DeathsThreshold);
  			OnObjectivesUpdated(false);
  			if (unitDeaths >= DeathsThreshold && objectives[FewDeathsID].Status == ObjectiveStatus.InProgress)
  			{
  				objectives[FewDeathsID].Status = ObjectiveStatus.Failed;
  				OnObjectivesUpdated(true);
  			}
  		}
  
  		void YakStrafe(IEnumerable<Actor> candidates)
  		{
  			if (yak == null)
  			{
  				yak = world.CreateActor(YakName, new TypeDictionary
  				{
  					new LocationInit(yakEntryPoint.Location),
  					new OwnerInit(soviets),
  					new FacingInit(Util.GetFacing(yakAttackPoint.Location - yakEntryPoint.Location, 0)),
  					new AltitudeInit(Rules.Info[YakName].Traits.Get<PlaneInfo>().CruiseAltitude)
  				});
  			}
  			if (yak.Trait<LimitedAmmo>().HasAmmo())
  			{
  				yak.QueueActivity(new FlyAttack(Target.FromActor(candidates.Random(world.SharedRandom))));
  			}
  			else
  			{
  				yak.QueueActivity(new FlyOffMap());
  				yak.QueueActivity(new RemoveSelf());
  			}
  		}
  
  		void BuildSovietUnits()
  		{
  			if (!sovietBarracks.Destroyed)
  			{
  				BuildSovietUnit(InfantryQueueName, SovietInfantry.Random(world.SharedRandom));
  			}
  			if (!sovietWarFactory.Destroyed)
  			{
  				var vehicles = world.FrameNumber >= SovietVehiclesUpgradeTicks ? SovietVehicles2 : SovietVehicles1;
  				BuildSovietUnit(VehicleQueueName, vehicles.Random(world.SharedRandom));
  			}
  		}
  
  		void ManageSovietUnits()
  		{
  			var idleSovietUnitsAtRP = world.FindAliveCombatantActorsInCircle(sovietRallyPoint.CenterLocation, 3).Where(a => a.Owner == soviets && a.IsIdle && a.HasTrait<IMove>());
  
gt: 			if (idleSovietUnitsAtRP.Count() >= SovietGroupSize)