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;
  using System.Collections.Generic;
  using System.Linq;
  using OpenRA.Mods.RA.Activities;
  using OpenRA.Mods.RA.Buildings;
  using OpenRA.Mods.RA.Move;
  using OpenRA.Traits;
  
  namespace OpenRA.Mods.RA.Missions
  {
  	class MonsterTankMadnessScriptInfo : ITraitInfo, Requires<SpawnMapActorsInfo>
  	{
  		public readonly string[] FirstStartUnits = null;
  		public readonly string[] SecondStartUnits = null;
  		public readonly string[] ThirdStartUnits = null;
  		public readonly string[] FirstBaseUnits = null;
  		public readonly string[] CivilianEvacuees = null;
  
  		public object Create(ActorInitializer init) { return new MonsterTankMadnessScript(this); }
  	}
  
  	class MonsterTankMadnessScript : IHasObjectives, IWorldLoaded, ITick
  	{
  		MonsterTankMadnessScriptInfo info;
  
  		public MonsterTankMadnessScript(MonsterTankMadnessScriptInfo info)
  		{
  			this.info = info;
  		}
  
  		public event Action<bool> OnObjectivesUpdated = notify => { };
  
  		public IEnumerable<Objective> Objectives { get { return objectives.Values; } }
  
  		Dictionary<int, Objective> objectives = new Dictionary<int, Objective>
  		{
  			{ FindOutpostID, new Objective(ObjectiveType.Primary, FindOutpost, ObjectiveStatus.InProgress) },
  			{ EvacuateDemitriID, new Objective(ObjectiveType.Primary, EvacuateDemitri, ObjectiveStatus.InProgress) },
  			{ InfiltrateRadarDomeID, new Objective(ObjectiveType.Primary, InfiltrateRadarDome, ObjectiveStatus.InProgress) },
  		};
  
  		const int FindOutpostID = 0;
  		const string FindOutpost = "Find our outpost and start repairs on it.";
  
  		const int EvacuateDemitriID = 1;
  		const string EvacuateDemitri = "Find and evacuate Dr. Demitri. He is missing -- likely hiding in the village to the far south.";
  
  		const int InfiltrateRadarDomeID = 2;
  		const string InfiltrateRadarDome = "Reprogram the Super Tanks by sending a spy into the Soviet radar dome.";
  
  		//const string Briefing = "Dr. Demitri, creator of a Soviet Super Tank, wants to defect."
  		//					+ " We planned to extract him while the Soviets were testing their new weapon, but something has gone wrong."
  		//					+ " The Super Tanks are out of control, and Demitri is missing -- likely hiding in the village to the far south."
  		//					+ " Find our outpost and start repairs on it, then find and evacuate Demitri."
  		//					+ " As for the tanks, we can reprogram them. Send a spy into the Soviet radar dome in the NE, turning the tanks on their creators.";
  
  		World world;
  
  		Player neutral;
  		Player greece;
  		Player ussr;
  		Player badGuy;
  		Player turkey;
  
  		Actor startEntryPoint;
  		Actor startMovePoint;
  		Actor startBridgeEndPoint;
  		Actor alliedBaseTopLeft;
  		Actor alliedBaseBottomRight;
  		Actor alliedBaseEntryPoint;
  		Actor alliedBaseMovePoint;
  
  		Actor demitriChurch;
  		Actor demitriChurchSpawnPoint;
  		Actor demitriTriggerAreaCenter;
  		Actor demitri;
  		Actor demitriLZ;
  		Actor demitriLZFlare;
  		Actor demitriChinook;
  
  		Actor provingGroundsCameraPoint;
  
  		Actor[] superTanks;
  
  		Actor hospital;
  		Actor hospitalCivilianSpawnPoint;
  		Actor hospitalSuperTankPoint;
  
  		Actor superTankDome;
  
  		bool hospitalEvacuated;
  		bool superTanksDestroyed;
  
  		int baseTransferredTick = -1;
  		int superTankDomeInfiltratedTick = -1;
  
  		void MissionAccomplished(string text)
  		{
  			MissionUtils.CoopMissionAccomplished(world, text, greece);
  		}
  
  		void MissionFailed(string text)
  		{
  			MissionUtils.CoopMissionFailed(world, text, greece);
  		}
  
  		public void Tick(Actor self)
  		{
  			if (greece.WinState != WinState.Undefined) return;
  
  			if (world.FrameNumber == 1)
  				SpawnAndMoveBridgeUnits(info.FirstStartUnits);
  
  			else if (world.FrameNumber == 25 * 3)
  				SpawnAndMoveBridgeUnits(info.SecondStartUnits);
  
  			else if (world.FrameNumber == 25 * 8)
  				SpawnAndMoveBridgeUnits(info.ThirdStartUnits);
  
  			MissionUtils.CapOre(ussr);
  
  			if (!hospitalEvacuated && !hospital.IsDead() && MissionUtils.AreaSecuredWithUnits(world, greece, hospital.CenterLocation, 5))
  			{
  				EvacuateCivilians();
  				hospitalEvacuated = true;
  			}
  
  			if (baseTransferredTick == -1)
  			{
  				var actorsInBase = world.FindUnits(alliedBaseTopLeft.CenterLocation, alliedBaseBottomRight.CenterLocation).Where(a => a != a.Owner.PlayerActor);
  				if (actorsInBase.Any(a => a.Owner == greece))
  				{
  					SetupAlliedBase(actorsInBase);
  					baseTransferredTick = world.FrameNumber;
  					objectives[FindOutpostID].Status = ObjectiveStatus.Completed;
  					OnObjectivesUpdated(true);
  				}
  			}
  			else if (superTankDomeInfiltratedTick == -1)
  			{
  				if (world.FrameNumber == baseTransferredTick + 25 * 100)
  					foreach (var tank in superTanks.Where(t => !t.IsDead() && t.IsInWorld))
  						tank.QueueActivity(false, new Move.Move(hospitalSuperTankPoint.Location, 2));
  
  				else if (world.FrameNumber == baseTransferredTick + 25 * 180)
  					foreach (var tank in superTanks.Where(t => !t.IsDead() && t.IsInWorld))
  						tank.QueueActivity(false, new Move.Move(alliedBaseBottomRight.Location, 2));
  
  				else if (world.FrameNumber == baseTransferredTick + 25 * 280)
  					foreach (var tank in superTanks.Where(t => !t.IsDead() && t.IsInWorld))
  						tank.QueueActivity(false, new Move.Move(demitriTriggerAreaCenter.Location, 2));
  
  				else if (world.FrameNumber == baseTransferredTick + 25 * 480)
  					foreach (var tank in superTanks.Where(t => !t.IsDead() && t.IsInWorld))
  						tank.QueueActivity(false, new Move.Move(demitriLZ.Location, 4));
  			}
  			else
  			{
  				if (world.FrameNumber % 25 == 0)
  					foreach (var tank in superTanks.Where(t => !t.IsDead() && t.IsInWorld && t.IsIdle))
  						MissionUtils.AttackNearestLandActor(false, tank, ussr);
  				if (world.FrameNumber == superTankDomeInfiltratedTick + 25 * 180)
  				{
  					foreach (var actor in world.Actors.Where(a => !a.IsDead() && (a.Owner == ussr || a.Owner == badGuy)))
  						actor.Kill(actor);
  				}
  				if (world.FrameNumber == superTankDomeInfiltratedTick + 25 * 181)
  				{
  					foreach (var tank in superTanks.Where(t => !t.IsDead()))
  						tank.Kill(tank);
  					superTanksDestroyed = true;
  				}
  			}
  			if (objectives[EvacuateDemitriID].Status != ObjectiveStatus.Completed)
  			{
  				if (demitri == null)
  				{
  					if (demitriChurch.IsDead())
  					{
  						objectives[EvacuateDemitriID].Status = ObjectiveStatus.Failed;
  						OnObjectivesUpdated(true);
  						MissionFailed("Dr. Demitri was killed.");
  					}
  
  					else if (MissionUtils.AreaSecuredWithUnits(world, greece, demitriTriggerAreaCenter.CenterLocation, 3))
  					{
  						demitri = world.CreateActor("demitri", greece, demitriChurchSpawnPoint.Location, null);
  						demitri.QueueActivity(new Move.Move(demitriTriggerAreaCenter.Location, 0));
  						demitriLZFlare = world.CreateActor("flare", greece, demitriLZ.Location, null);
  						Sound.Play("flaren1.aud");
  						var chinookEntry = new CPos(demitriLZ.Location.X, 0);
  						demitriChinook = MissionUtils.ExtractUnitWithChinook(world, greece, demitri, chinookEntry, demitriLZ.Location, chinookEntry);
  					}
  				}
  				else if (demitri.IsDead())
  				{
  					objectives[EvacuateDemitriID].Status = ObjectiveStatus.Failed;
  					OnObjectivesUpdated(true);
  					MissionFailed("Dr. Demitri was killed.");
  				}
  				else if (demitriChinook != null && !demitriChinook.IsDead() && !world.Map.IsInMap(demitriChinook.Location) && demitriChinook.Trait<Cargo>().Passengers.Contains(demitri))
  				{
  					demitriLZFlare.Destroy();
  					SpawnAndMoveAlliedBaseUnits(info.FirstBaseUnits);
  					objectives[EvacuateDemitriID].Status = ObjectiveStatus.Completed;
  					OnObjectivesUpdated(true);
  				}
  			}
  			if (!world.Actors.Any(a => a.Owner == greece && a.IsInWorld && !a.IsDead()
  				&& ((a.HasTrait<Building>() && !a.HasTrait<Wall>()) || a.HasTrait<BaseBuilding>() || a.HasTrait<Mobile>())))
  			{
  				MissionFailed("The remaining Allied forces in the area have been wiped out.");
  			}
  			if (superTankDomeInfiltratedTick == -1 && superTankDome.IsDead())
  			{
  				objectives[InfiltrateRadarDomeID].Status = ObjectiveStatus.Failed;
  				OnObjectivesUpdated(true);
  				MissionFailed("The Soviet radar dome was destroyed.");
  			}
  			if (superTanksDestroyed && objectives[EvacuateDemitriID].Status == ObjectiveStatus.Completed)
  			{
  				MissionAccomplished("Dr. Demitri has been extracted and the super tanks have been dealt with.");
  			}
  		}
  
  		void SetupAlliedBase(IEnumerable<Actor> actors)
  		{
  			foreach (var actor in actors)
  			{
  				// hack hack hack
  				actor.ChangeOwner(greece);
  				if (actor.Info.Name == "pbox")
  				{
  					actor.AddTrait(new TransformedAction(s => s.Trait<Cargo>().Load(s, world.CreateActor(false, "e1", greece, null, null))));
  					actor.QueueActivity(new Transform(actor, "hbox.e1") { SkipMakeAnims = true });
  				}
  				else if (actor.Info.Name == "proc")
  					actor.QueueActivity(new Transform(actor, "proc") { SkipMakeAnims = true });
  				foreach (var c in actor.TraitsImplementing<INotifyCapture>())
  					c.OnCapture(actor, actor, neutral, greece);
  			}
  		}
  
  		void EvacuateCivilians()
  		{
  			foreach (var unit in info.CivilianEvacuees)
  			{
  				var actor = world.CreateActor(unit, neutral, hospitalCivilianSpawnPoint.Location, null);
  				actor.Trait<Mobile>().Nudge(actor, actor, true);
  				actor.QueueActivity(new Move.Move(alliedBaseEntryPoint.Location, 0));
  				actor.QueueActivity(new RemoveSelf());
  			}
  		}
  
  		void SpawnAndMoveBridgeUnits(string[] units)
  		{
  			Sound.Play("reinfor1.aud");
  			foreach (var unit in units)
  				world.CreateActor(unit, greece, startEntryPoint.Location, Util.GetFacing(startBridgeEndPoint.CenterLocation - startEntryPoint.CenterLocation, 0))
  				.QueueActivity(new Move.Move(startMovePoint.Location, 0));
  		}
  
  		void SpawnAndMoveAlliedBaseUnits(string[] units)
  		{
  			Sound.Play("reinfor1.aud");
  			foreach (var unit in units)
  				world.CreateActor(unit, greece, alliedBaseEntryPoint.Location, Util.GetFacing(alliedBaseMovePoint.CenterLocation - alliedBaseEntryPoint.CenterLocation, 0))
  				.QueueActivity(new Move.Move(alliedBaseMovePoint.Location, 0));
  		}
  
  		void OnSuperTankDomeInfiltrated(Actor spy)
  		{
  			if (superTankDomeInfiltratedTick != -1) return;
  			superTankDome.QueueActivity(new Transform(superTankDome, "dome") { SkipMakeAnims = true });
  			turkey.Stances[greece] = turkey.Stances[neutral] = Stance.Ally;
  			greece.Stances[turkey] = neutral.Stances[turkey] = Stance.Ally;
  			greece.Shroud.ExploreAll(world);
  			foreach (var tank in superTanks.Where(t => !t.IsDead() && t.IsInWorld))
  				MissionUtils.AttackNearestLandActor(false, tank, ussr);
  			superTankDomeInfiltratedTick = world.FrameNumber;
  			objectives[InfiltrateRadarDomeID].Status = ObjectiveStatus.Completed;
  			OnObjectivesUpdated(true);
  		}
  
  		public void WorldLoaded(World w)
  		{
  			world = w;
  
  			neutral = w.Players.Single(p => p.InternalName == "Neutral");
  			greece = w.Players.Single(p => p.InternalName == "Greece");
  			ussr = w.Players.Single(p => p.InternalName == "USSR");
  			badGuy = w.Players.Single(p => p.InternalName == "BadGuy");
  
gt: 			turkey = w.Players.Single(p => p.InternalName == "Turkey");