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.Drawing;
  using System.Linq;
  using OpenRA.FileFormats;
  using OpenRA.Mods.RA.Activities;
  using OpenRA.Mods.RA.Buildings;
  using OpenRA.Mods.RA.Move;
  using OpenRA.Mods.RA.Render;
  using OpenRA.Graphics;
  using OpenRA.Traits;
  using OpenRA.Widgets;
  
  namespace OpenRA.Mods.RA.Missions
  {
  	class Allies04ScriptInfo : TraitInfo<Allies04Script>, Requires<SpawnMapActorsInfo> { }
  
  	class Allies04Script : IHasObjectives, IWorldLoaded, ITick
  	{
  		public event Action<bool> OnObjectivesUpdated = notify => { };
  
  		public IEnumerable<Objective> Objectives { get { return objectives.Values; } }
  
  		Dictionary<int, Objective> objectives = new Dictionary<int, Objective>
  		{
  			{ InfiltrateID, new Objective(ObjectiveType.Primary, "", ObjectiveStatus.InProgress) },
  			{ DestroyID, new Objective(ObjectiveType.Primary, Destroy, ObjectiveStatus.Inactive) }
  		};
  
  		const int InfiltrateID = 0;
  		const int DestroyID = 1;
  		const string Destroy = "Secure the laboratory and destroy the rest of the Soviet base. Ensure that the laboratory is not destroyed.";
  		const string Infiltrate = "The Soviets are currently developing a new defensive system named the \"Iron Curtain\" at their main research laboratory."
  								+ " Get our {0} into the laboratory undetected.";
  
  		Actor hijackTruck;
  		Actor baseGuard;
  		Actor baseGuardMovePos;
  		Actor baseGuardTruckPos;
  		Actor lab;
  		int baseGuardTicks = 100;
  
  		Actor allies1Spy;
  		Actor allies2Spy;
  		bool allies1SpyInfiltratedLab;
  		bool allies2SpyInfiltratedLab;
  		int frameInfiltrated = -1;
  
  		CountdownTimer destroyBaseTimer;
  		CountdownTimerWidget destroyBaseTimerWidget;
  
  		Player allies1;
  		Player allies2;
  		Player soviets;
  		Player neutral;
  		World world;
  
  		List<Patrol> patrols;
  		CPos[] patrolPoints1;
  		CPos[] patrolPoints2;
  		CPos[] patrolPoints3;
  		CPos[] patrolPoints4;
  		CPos[] patrolPoints5;
  
  		Actor reinforcementsEntryPoint;
  		Actor reinforcementsUnloadPoint;
  
  		Actor spyReinforcementsEntryPoint;
  		Actor spyReinforcementsUnloadPoint;
  		Actor spyReinforcementsExitPoint;
  
  		string difficulty;
  		int destroyBaseTicks;
  
  		int nextCivilianMove = 1;
  
  		Actor bridgeTank;
  		Actor bridgeAttackPoint;
  		Actor bridge;
  		bool attackingBridge;
  
  		bool attackingTown = true;
  		Actor[] townAttackers;
  
  		void MissionFailed(string text)
  		{
  			MissionUtils.CoopMissionFailed(world, text, allies1, allies2);
  		}
  
  		void MissionAccomplished(string text)
  		{
  			MissionUtils.CoopMissionAccomplished(world, text, allies1, allies2);
  		}
  
  		public void Tick(Actor self)
  		{
  			if (allies1.WinState != WinState.Undefined) return;
  
  			if (world.FrameNumber == 1)
  				InsertSpies();
  
  			if (frameInfiltrated != -1)
  			{
  				if (world.FrameNumber == frameInfiltrated + 100)
  				{
  					Sound.Play("aarrivs1.aud");
  					Game.MoveViewport(reinforcementsUnloadPoint.Location.ToFloat2());
  					world.AddFrameEndTask(w => SendReinforcements());
  				}
  				if (world.FrameNumber == frameInfiltrated + 200)
  				{
  					Sound.Play("timergo1.aud");
  					destroyBaseTimer = new CountdownTimer(destroyBaseTicks, OnDestroyBaseTimerExpired, true);
  					destroyBaseTimerWidget = new CountdownTimerWidget(destroyBaseTimer, "Secure lab in: {0}");
  					Ui.Root.AddChild(destroyBaseTimerWidget);
  				}
  
  				if (world.FrameNumber >= frameInfiltrated + 200)
  					destroyBaseTimer.Tick();
  
  				if (world.FrameNumber == frameInfiltrated + 1500 * 12 && !bridgeTank.IsDead() && bridgeTank.IsInWorld && !bridge.IsDead())
  				{
  					bridgeTank.QueueActivity(new Attack(Target.FromPos(bridge.CenterLocation), 4));
  					attackingBridge = true;
  				}
  				if (attackingBridge && bridge.IsDead())
  				{
  					if (!bridgeTank.IsDead())
  						bridgeTank.CancelActivity();
  					attackingBridge = false;
  				}
  
  				if (world.FrameNumber == frameInfiltrated + 1500 * 6)
  					foreach (var attacker in townAttackers.Where(a => !a.IsDead() && a.IsInWorld))
  					{
  						attacker.CancelActivity();
  						attacker.QueueActivity(new AttackMove.AttackMoveActivity(attacker, new Move.Move(reinforcementsUnloadPoint.Location + new CVec(10, -15), 3)));
  					}
  			}
  
  			if (attackingTown)
  			{
  				foreach (var attacker in townAttackers.Where(u => u.IsIdle && !u.IsDead() && u.IsInWorld))
  				{
  					var enemies = world.Actors.Where(u => u.Owner == neutral && u.HasTrait<ITargetable>()
  						&& ((u.HasTrait<Building>() && !u.HasTrait<Wall>() && !u.HasTrait<Bridge>()) || u.HasTrait<Mobile>()) && !u.IsDead() && u.IsInWorld);
  
  					var enemy = enemies.OrderBy(u => (attacker.CenterLocation - u.CenterLocation).LengthSquared).FirstOrDefault();
  					if (enemy != null)
  						attacker.QueueActivity(new AttackMove.AttackMoveActivity(attacker, new Attack(Target.FromActor(enemy), 3)));
  					else
  					{
  						attackingTown = false;
  						break;
  					}
  				}
  			}
  
  			foreach (var patrol in patrols)
  				patrol.DoPatrol();
  
  			MissionUtils.CapOre(soviets);
  
  			BaseGuardTick();
  
  			if (allies1Spy.IsDead() || (allies2Spy != null && allies2Spy.IsDead()))
  			{
  				objectives[InfiltrateID].Status = ObjectiveStatus.Failed;
  				OnObjectivesUpdated(true);
  				MissionFailed("{0} spy was killed.".F(allies1 != allies2 ? "A" : "The"));
  			}
  			else if (lab.IsDead())
  				MissionFailed("The Soviet research laboratory was destroyed.");
  			else if (!world.Actors.Any(a => (a.Owner == allies1 || a.Owner == allies2) && !a.IsDead()
  				&& (a.HasTrait<Building>() && !a.HasTrait<Wall>()) || a.HasTrait<BaseBuilding>()))
  			{
  				objectives[DestroyID].Status = ObjectiveStatus.Failed;
  				OnObjectivesUpdated(true);
  				MissionFailed("The remaining Allied forces in the area have been wiped out.");
  			}
  			else if (SovietBaseDestroyed() && objectives[InfiltrateID].Status == ObjectiveStatus.Completed)
  			{
  				objectives[DestroyID].Status = ObjectiveStatus.Completed;
  				OnObjectivesUpdated(true);
  				MissionAccomplished("The Soviet research laboratory has been secured successfully.");
  			}
  			if (world.FrameNumber == nextCivilianMove)
  			{
  				var civilians = world.Actors.Where(a => !a.IsDead() && a.IsInWorld && a.Owner == neutral && a.HasTrait<Mobile>());
  				if (civilians.Any())
  				{
  					var civilian = civilians.Random(world.SharedRandom);
  					civilian.Trait<Mobile>().Nudge(civilian, civilian, true);
  					nextCivilianMove += world.SharedRandom.Next(1, 75);
  				}
  			}
  		}
  
  		bool SovietBaseDestroyed()
  		{
  			return !world.Actors.Any(a => a.Owner == soviets && a.IsInWorld && !a.IsDead()
  				&& a.HasTrait<Building>() && !a.HasTrait<Wall>() && !a.HasTrait<Allies04TrivialBuilding>() && a != lab);
  		}
  
  		void OnDestroyBaseTimerExpired(CountdownTimer t)
  		{
  			if (SovietBaseDestroyed() && objectives[InfiltrateID].Status == ObjectiveStatus.Completed) return;
  			objectives[DestroyID].Status = ObjectiveStatus.Failed;
  			OnObjectivesUpdated(true);
  			MissionFailed("The Soviet research laboratory was not secured in time.");
  		}
  
  		void BaseGuardTick()
  		{
  			if (baseGuardTicks <= 0 || baseGuard.IsDead() || !baseGuard.IsInWorld) return;
  
  			if (hijackTruck.Location == baseGuardTruckPos.Location)
  			{
  				if (--baseGuardTicks <= 0)
  					baseGuard.QueueActivity(new Move.Move(baseGuardMovePos.Location));
  			}
  			else
  				baseGuardTicks = 100;
  		}
  
  		void OnLabInfiltrated(Actor spy)
  		{
  			if (spy == allies1Spy) allies1SpyInfiltratedLab = true;
  			else if (spy == allies2Spy) allies2SpyInfiltratedLab = true;
  
  			if (allies1SpyInfiltratedLab && (allies2SpyInfiltratedLab || allies2Spy == null))
  			{
  				objectives[InfiltrateID].Status = ObjectiveStatus.Completed;
  				objectives[DestroyID].Status = ObjectiveStatus.InProgress;
  				OnObjectivesUpdated(true);
  				frameInfiltrated = world.FrameNumber;
  
  				foreach (var actor in world.Actors.Where(a => !a.IsDead() && a.HasTrait<Allies04TransformOnLabInfiltrate>()))
  					actor.QueueActivity(false, new Transform(actor, actor.Info.Traits.Get<Allies04TransformOnLabInfiltrateInfo>().ToActor) { SkipMakeAnims = true });
  			}
  		}
  
  		void SendReinforcements()
  		{
  			var lst = world.CreateActor("lst.unselectable", new TypeDictionary 
  			{ 
  				new OwnerInit(allies1),
  				new LocationInit(reinforcementsEntryPoint.Location)
  			});
  			lst.Trait<Cargo>().Load(lst, world.CreateActor(false, "mcv", new TypeDictionary { new OwnerInit(allies1) }));
  			if (allies1 != allies2)
  				lst.Trait<Cargo>().Load(lst, world.CreateActor(false, "mcv", new TypeDictionary { new OwnerInit(allies2) }));
  
  			lst.QueueActivity(new Move.Move(reinforcementsUnloadPoint.Location));
  			lst.QueueActivity(new Wait(10));
  
  			lst.QueueActivity(new CallFunc(() =>
  			{
  				allies1.PlayerActor.Trait<PlayerResources>().GiveCash(allies1 == allies2 ? 5000 : 2500);
  				if (allies1 != allies2)
  					allies2.PlayerActor.Trait<PlayerResources>().GiveCash(2500);
  			}));
  
  			lst.AddTrait(new TransformedAction(self =>
  			{
  				self.QueueActivity(new Wait(10));
  				self.QueueActivity(new Move.Move(reinforcementsEntryPoint.Location));
  				self.QueueActivity(new RemoveSelf());
  			}));
  			lst.QueueActivity(new UnloadCargo(true));
  			lst.QueueActivity(new Transform(lst, "lst.unselectable.nocargo") { SkipMakeAnims = true });
  		}
  
  		class Patrol
  		{
  			Actor[] actors;
  			CPos[] points;
  			int pointIndex;
  			World world;
  
  			public Patrol(World world, string[] actorNames, Player owner, CPos[] points, int pointIndex)
  			{
  				this.world = world;
  				this.points = points;
  				this.pointIndex = pointIndex;
  				var td = new TypeDictionary { new OwnerInit(owner), new LocationInit(points[pointIndex]) };
  				actors = actorNames.Select(a => world.CreateActor(a, td)).ToArray();
  			}
  
  			public void DoPatrol()
  			{
  				if (actors.Any(a => a.IsDead() || !a.IsIdle || !a.IsInWorld)) return;
  
  				pointIndex = (pointIndex + 1) % points.Length;
  				foreach (var actor in actors.Where(a => !a.IsDead() && a.IsInWorld))
  				{
  					actor.QueueActivity(new Wait(world.SharedRandom.Next(50, 75)));
  					actor.QueueActivity(new AttackMove.AttackMoveActivity(actor, new Move.Move(points[pointIndex], 0)));
  				}
  			}
  		}
  
  		void InsertSpies()
  		{
  			var lst = world.CreateActor("lst.unselectable", new TypeDictionary 
  			{ 
  				new OwnerInit(allies1),
  				new LocationInit(spyReinforcementsEntryPoint.Location)
  			});
  
  			allies1Spy = world.CreateActor(false, "spy.strong", new TypeDictionary { new OwnerInit(allies1) });
  			lst.Trait<Cargo>().Load(lst, allies1Spy);
  			if (allies1 != allies2)
  			{
  				allies2Spy = world.CreateActor(false, "spy.strong", new TypeDictionary { new OwnerInit(allies2) });
  				lst.Trait<Cargo>().Load(lst, allies2Spy);
  			}
  
  			lst.AddTrait(new TransformedAction(self =>
  			{
  				self.QueueActivity(new Wait(10));
  				self.QueueActivity(new Move.Move(spyReinforcementsExitPoint.Location));
  				self.QueueActivity(new RemoveSelf());
  			}));
  			lst.QueueActivity(new Move.Move(spyReinforcementsUnloadPoint.Location));
  			lst.QueueActivity(new Wait(10));
  			lst.QueueActivity(new UnloadCargo(true));
  			lst.QueueActivity(new Transform(lst, "lst.unselectable.nocargo") { SkipMakeAnims = true });
  		}
  
  		void SetupSubStances()
  		{
  			foreach (var actor in world.Actors.Where(a => a.IsInWorld && a.Owner == soviets && !a.IsDead() && a.HasTrait<TargetableSubmarine>()))
  				actor.Trait<AutoTarget>().stance = UnitStance.Defend;
  		}
  
  		public void WorldLoaded(World w)
  		{
  			world = w;
  
  			difficulty = w.LobbyInfo.GlobalSettings.Difficulty;
  
gt: 			Game.Debug("{0} difficulty selected".F(difficulty));