context: #region Copyright & License Information
  /*
   * Copyright 2007-2020 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.Generic;
  
  namespace OpenRA.Traits
  {
  	[Desc("Required for shroud and fog visibility checks. Add this to the player actor.")]
  	public class ShroudInfo : TraitInfo, ILobbyOptions
  	{
  		[Translate]
  		[Desc("Descriptive label for the fog checkbox in the lobby.")]
  		public readonly string FogCheckboxLabel = "Fog of War";
  
  		[Translate]
  		[Desc("Tooltip description for the fog checkbox in the lobby.")]
  		public readonly string FogCheckboxDescription = "Line of sight is required to view enemy forces";
  
  		[Desc("Default value of the fog checkbox in the lobby.")]
  		public readonly bool FogCheckboxEnabled = true;
  
  		[Desc("Prevent the fog enabled state from being changed in the lobby.")]
  		public readonly bool FogCheckboxLocked = false;
  
  		[Desc("Whether to display the fog checkbox in the lobby.")]
  		public readonly bool FogCheckboxVisible = true;
  
  		[Desc("Display order for the fog checkbox in the lobby.")]
  		public readonly int FogCheckboxDisplayOrder = 0;
  
  		[Translate]
  		[Desc("Descriptive label for the explored map checkbox in the lobby.")]
  		public readonly string ExploredMapCheckboxLabel = "Explored Map";
  
  		[Translate]
  		[Desc("Tooltip description for the explored map checkbox in the lobby.")]
  		public readonly string ExploredMapCheckboxDescription = "Initial map shroud is revealed";
  
  		[Desc("Default value of the explore map checkbox in the lobby.")]
  		public readonly bool ExploredMapCheckboxEnabled = false;
  
  		[Desc("Prevent the explore map enabled state from being changed in the lobby.")]
  		public readonly bool ExploredMapCheckboxLocked = false;
  
  		[Desc("Whether to display the explore map checkbox in the lobby.")]
  		public readonly bool ExploredMapCheckboxVisible = true;
  
  		[Desc("Display order for the explore map checkbox in the lobby.")]
  		public readonly int ExploredMapCheckboxDisplayOrder = 0;
  
  		IEnumerable<LobbyOption> ILobbyOptions.LobbyOptions(Ruleset rules)
  		{
  			yield return new LobbyBooleanOption("explored", ExploredMapCheckboxLabel, ExploredMapCheckboxDescription,
  				ExploredMapCheckboxVisible, ExploredMapCheckboxDisplayOrder, ExploredMapCheckboxEnabled, ExploredMapCheckboxLocked);
  			yield return new LobbyBooleanOption("fog", FogCheckboxLabel, FogCheckboxDescription,
  				FogCheckboxVisible, FogCheckboxDisplayOrder, FogCheckboxEnabled, FogCheckboxLocked);
  		}
  
  		public override object Create(ActorInitializer init) { return new Shroud(init.Self, this); }
  	}
  
  	public class Shroud : ISync, INotifyCreated, ITick
  	{
  		public enum SourceType : byte { PassiveVisibility, Shroud, Visibility }
  		public event Action<PPos> OnShroudChanged;
  
  		enum ShroudCellType : byte { Shroud, Fog, Visible }
  		class ShroudSource
  		{
  			public readonly SourceType Type;
  			public readonly PPos[] ProjectedCells;
  
  			public ShroudSource(SourceType type, PPos[] projectedCells)
  			{
  				Type = type;
  				ProjectedCells = projectedCells;
  			}
  		}
  
  		readonly Actor self;
  		readonly ShroudInfo info;
  		readonly Map map;
  
  		// Individual shroud modifier sources (type and area)
  		readonly Dictionary<object, ShroudSource> sources = new Dictionary<object, ShroudSource>();
  
  		// Per-cell count of each source type, used to resolve the final cell type
  		readonly ProjectedCellLayer<short> passiveVisibleCount;
  		readonly ProjectedCellLayer<short> visibleCount;
  		readonly ProjectedCellLayer<short> generatedShroudCount;
  		readonly ProjectedCellLayer<bool> explored;
  		readonly ProjectedCellLayer<bool> touched;
  
  		// Per-cell cache of the resolved cell type (shroud/fog/visible)
  		readonly ProjectedCellLayer<ShroudCellType> resolvedType;
  
  		[Sync]
  		bool disabled;
  		public bool Disabled
  		{
  			get
  			{
  				return disabled;
  			}
  
  			set
  			{
  				if (disabled == value)
  					return;
  
  				disabled = value;
  			}
  		}
  
  		bool fogEnabled;
  		public bool FogEnabled { get { return !Disabled && fogEnabled; } }
  		public bool ExploreMapEnabled { get; private set; }
  
  		public int Hash { get; private set; }
  
  		// Enabled at runtime on first use
  		bool shroudGenerationEnabled;
  		bool passiveVisibilityEnabled;
  
  		public Shroud(Actor self, ShroudInfo info)
  		{
  			this.self = self;
  			this.info = info;
  			map = self.World.Map;
  
  			passiveVisibleCount = new ProjectedCellLayer<short>(map);
  			visibleCount = new ProjectedCellLayer<short>(map);
  			generatedShroudCount = new ProjectedCellLayer<short>(map);
  			explored = new ProjectedCellLayer<bool>(map);
  			touched = new ProjectedCellLayer<bool>(map);
  
  			// Defaults to 0 = Shroud
  			resolvedType = new ProjectedCellLayer<ShroudCellType>(map);
  		}
  
  		void INotifyCreated.Created(Actor self)
  		{
  			var gs = self.World.LobbyInfo.GlobalSettings;
  			fogEnabled = gs.OptionOrDefault("fog", info.FogCheckboxEnabled);
  
  			ExploreMapEnabled = gs.OptionOrDefault("explored", info.ExploredMapCheckboxEnabled);
  			if (ExploreMapEnabled)
  				self.World.AddFrameEndTask(w => ExploreAll());
  		}
  
  		void ITick.Tick(Actor self)
  		{
  			if (OnShroudChanged == null)
  				return;
  
  			foreach (var puv in map.ProjectedCellBounds)
  			{
  				if (!touched[puv])
  					continue;
  
  				touched[puv] = false;
  
  				var type = ShroudCellType.Shroud;
  
  				if (explored[puv] && (!shroudGenerationEnabled || generatedShroudCount[puv] == 0 || visibleCount[puv] > 0))
  				{
  					var count = visibleCount[puv];
  					if (passiveVisibilityEnabled)
  						count += passiveVisibleCount[puv];
  
  					type = count > 0 ? ShroudCellType.Visible : ShroudCellType.Fog;
  				}
  
  				var oldResolvedType = resolvedType[puv];
  				resolvedType[puv] = type;
  				if (type != oldResolvedType)
  					OnShroudChanged(puv);
  			}
  
  			Hash = Sync.HashPlayer(self.Owner) + self.World.WorldTick;
  		}
  
  		public static IEnumerable<PPos> ProjectedCellsInRange(Map map, WPos pos, WDist minRange, WDist maxRange, int maxHeightDelta = -1)
  		{
  			// Account for potential extra half-cell from odd-height terrain
  			var r = (maxRange.Length + 1023 + 512) / 1024;
  			var minLimit = minRange.LengthSquared;
  			var maxLimit = maxRange.LengthSquared;
  
  			// Project actor position into the shroud plane
  			var projectedPos = pos - new WVec(0, pos.Z, pos.Z);
  			var projectedCell = map.CellContaining(projectedPos);
  
gt: 			var projectedHeight = pos.Z / 512;