context: #region Copyright & License Information
  /*
   * Copyright 2007-2015 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;
  
  namespace OpenRA.Traits
  {
  	[Desc("Required for shroud and fog visibility checks. Add this to the player actor.")]
  	public class ShroudInfo : ITraitInfo
  	{
  		public object Create(ActorInitializer init) { return new Shroud(init.Self); }
  	}
  
  	public class Shroud
  	{
  		[Sync] public bool Disabled;
  
  		public event Action<IEnumerable<PPos>> CellsChanged;
  
  		readonly Actor self;
  		readonly Map map;
  
  		readonly CellLayer<short> visibleCount;
  		readonly CellLayer<short> generatedShroudCount;
  		readonly CellLayer<bool> explored;
  
  		// Cache of visibility that was added, so no matter what crazy trait code does, it
  		// can't make us invalid.
  		readonly Dictionary<Actor, PPos[]> visibility = new Dictionary<Actor, PPos[]>();
  		readonly Dictionary<Actor, PPos[]> generation = new Dictionary<Actor, PPos[]>();
  
  		public int Hash { get; private set; }
  
  		static readonly Func<PPos, bool> TruthPredicate = _ => true;
  		readonly Func<PPos, bool> shroudEdgeTest;
  		readonly Func<PPos, bool> isExploredTest;
  		readonly Func<PPos, bool> isVisibleTest;
  
  		public Shroud(Actor self)
  		{
  			this.self = self;
  			map = self.World.Map;
  
  			visibleCount = new CellLayer<short>(map);
  			generatedShroudCount = new CellLayer<short>(map);
  			explored = new CellLayer<bool>(map);
  
  			shroudEdgeTest = map.Contains;
  
  			isExploredTest = IsExplored;
  			isVisibleTest = IsVisible;
  		}
  
  		void Invalidate(IEnumerable<PPos> changed)
  		{
  			if (CellsChanged != null)
  				CellsChanged(changed);
  
  			var oldHash = Hash;
  			Hash = Sync.HashPlayer(self.Owner) + self.World.WorldTick * 3;
  
  			// Invalidate may be called multiple times in one world tick, which is decoupled from rendering.
  			if (oldHash == Hash)
  				Hash += 1;
  		}
  
  		public static IEnumerable<PPos> ProjectedCellsInRange(Map map, WPos pos, WDist range)
  		{
  			// Account for potential extra half-cell from odd-height terrain
  			var r = (range.Length + 1023 + 512) / 1024;
  			var limit = range.LengthSquared;
  
  			// Project actor position into the shroud plane
  			var projectedPos = pos - new WVec(0, pos.Z, pos.Z);
  			var projectedCell = map.CellContaining(projectedPos);
  
  			foreach (var c in map.FindTilesInCircle(projectedCell, r, true))
  				if ((map.CenterOfCell(c) - projectedPos).HorizontalLengthSquared <= limit)
  					yield return (PPos)c.ToMPos(map);
  		}
  
  		public static IEnumerable<PPos> ProjectedCellsInRange(Map map, CPos cell, WDist range)
  		{
  			return ProjectedCellsInRange(map, map.CenterOfCell(cell), range);
  		}
  
  		public void AddProjectedVisibility(Actor a, PPos[] visible)
  		{
  			if (!a.Owner.IsAlliedWith(self.Owner))
  				return;
  
  			foreach (var puv in visible)
  			{
  				// Force cells outside the visible bounds invisible
  				if (!map.Contains(puv))
  					continue;
  
  				var uv = (MPos)puv;
  				visibleCount[uv]++;
  				explored[uv] = true;
  			}
  
  			if (visibility.ContainsKey(a))
  				throw new InvalidOperationException("Attempting to add duplicate actor visibility");
  
  			visibility[a] = visible;
  			Invalidate(visible);
  		}
  
  		public void RemoveVisibility(Actor a)
  		{
  			PPos[] visible;
  			if (!visibility.TryGetValue(a, out visible))
  				return;
  
  			foreach (var puv in visible)
  			{
  				// Cells outside the visible bounds don't increment visibleCount
  				if (map.Contains(puv))
  					visibleCount[(MPos)puv]--;
  			}
  
  			visibility.Remove(a);
  			Invalidate(visible);
  		}
  
  		public void AddProjectedShroudGeneration(Actor a, PPos[] shrouded)
  		{
  			if (a.Owner.IsAlliedWith(self.Owner))
  				return;
  
  			foreach (var uv in shrouded)
  				generatedShroudCount[(MPos)uv]++;
  
  			if (generation.ContainsKey(a))
  				throw new InvalidOperationException("Attempting to add duplicate shroud generation");
  
  			generation[a] = shrouded;
  			Invalidate(shrouded);
  		}
  
  		public void RemoveShroudGeneration(Actor a)
  		{
  			PPos[] shrouded;
  			if (!generation.TryGetValue(a, out shrouded))
  				return;
  
  			foreach (var uv in shrouded)
  				generatedShroudCount[(MPos)uv]--;
  
  			generation.Remove(a);
  			Invalidate(shrouded);
  		}
  
  		public void UpdatePlayerStance(World w, Player player, Stance oldStance, Stance newStance)
  		{
  			if (oldStance == newStance)
  				return;
  
  			foreach (var a in w.Actors.Where(a => a.Owner == player))
  			{
  				PPos[] visible = null;
  				PPos[] shrouded = null;
  				foreach (var p in self.World.Players)
  				{
  					if (p.Shroud.visibility.TryGetValue(self, out visible))
  					{
  						p.Shroud.RemoveVisibility(self);
  						p.Shroud.AddProjectedVisibility(self, visible);
  					}
  
  					if (p.Shroud.generation.TryGetValue(self, out shrouded))
  					{
  						p.Shroud.RemoveShroudGeneration(self);
  						p.Shroud.AddProjectedShroudGeneration(self, shrouded);
  					}
  				}
  			}
  		}
  
  		public void ExploreProjectedCells(World world, IEnumerable<PPos> cells)
  		{
  			var changed = new HashSet<PPos>();
  			foreach (var puv in cells)
  			{
  				var uv = (MPos)puv;
  				if (!explored[uv])
  				{
  					explored[uv] = true;
  					changed.Add(puv);
  				}
  			}
  
  			Invalidate(changed);
  		}
  
  		public void Explore(Shroud s)
  		{
  			if (map.Bounds != s.map.Bounds)
  				throw new ArgumentException("The map bounds of these shrouds do not match.", "s");
  
  			var changed = new List<PPos>();
  			foreach (var puv in map.ProjectedCellBounds)
  			{
  				var uv = (MPos)puv;
  				if (!explored[uv] && s.explored[uv])
  				{
  					explored[uv] = true;
  					changed.Add(puv);
  				}
  			}
  
  			Invalidate(changed);
  		}
  
  		public void ExploreAll(World world)
  		{
  
gt: 			var changed = new List<PPos>();