context: #region Copyright & License Information
  /*
   * Copyright 2007-2021 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;
  using System.Linq;
  using OpenRA.Graphics;
  using OpenRA.Primitives;
  using OpenRA.Traits;
  
  namespace OpenRA.Mods.Common.Traits
  {
  	public struct ResourceLayerContents
  	{
  		public static readonly ResourceLayerContents Empty = default;
  		public readonly string Type;
  		public readonly int Density;
  
  		public ResourceLayerContents(string type, int density)
  		{
  			Type = type;
  			Density = density;
  		}
  	}
  
  	[TraitLocation(SystemActors.World)]
  	[Desc("Attach this to the world actor.")]
  	public class ResourceLayerInfo : TraitInfo, IResourceLayerInfo, Requires<BuildingInfluenceInfo>
  	{
  		public class ResourceTypeInfo
  		{
  			[FieldLoader.Require]
  			[Desc("Resource index in the binary map data.")]
  			public readonly byte ResourceIndex = 0;
  
  			[FieldLoader.Require]
  			[Desc("Terrain type used to determine unit movement and minimap colors.")]
  			public readonly string TerrainType = null;
  
  			[FieldLoader.Require]
  			[Desc("Terrain types that this resource can spawn on.")]
  			public readonly HashSet<string> AllowedTerrainTypes = null;
  
  			[Desc("Maximum number of resource units allowed in a single cell.")]
  			public readonly int MaxDensity = 10;
  
  			public ResourceTypeInfo(MiniYaml yaml)
  			{
  				FieldLoader.Load(this, yaml);
  			}
  		}
  
  		[FieldLoader.LoadUsing(nameof(LoadResourceTypes))]
  		public readonly Dictionary<string, ResourceTypeInfo> ResourceTypes = null;
  
  		[Desc("Override the density saved in maps with values calculated based on the number of neighbouring resource cells.")]
  		public readonly bool RecalculateResourceDensity = false;
  
  		// Copied to EditorResourceLayerInfo, ResourceRendererInfo
  		protected static object LoadResourceTypes(MiniYaml yaml)
  		{
  			var ret = new Dictionary<string, ResourceTypeInfo>();
  			var resources = yaml.Nodes.FirstOrDefault(n => n.Key == "ResourceTypes");
  			if (resources != null)
  				foreach (var r in resources.Value.Nodes)
  					ret[r.Key] = new ResourceTypeInfo(r.Value);
  
  			return ret;
  		}
  
  		bool IResourceLayerInfo.TryGetTerrainType(string resourceType, out string terrainType)
  		{
  			if (resourceType == null || !ResourceTypes.TryGetValue(resourceType, out var resourceInfo))
  			{
  				terrainType = null;
  				return false;
  			}
  
  			terrainType = resourceInfo.TerrainType;
  			return true;
  		}
  
  		bool IResourceLayerInfo.TryGetResourceIndex(string resourceType, out byte index)
  		{
  			if (resourceType == null || !ResourceTypes.TryGetValue(resourceType, out var resourceInfo))
  			{
  				index = 0;
  				return false;
  			}
  
  			index = resourceInfo.ResourceIndex;
  			return true;
  		}
  
  		public override object Create(ActorInitializer init) { return new ResourceLayer(init.Self, this); }
  	}
  
  	public class ResourceLayer : IResourceLayer, IWorldLoaded
  	{
  		readonly ResourceLayerInfo info;
  		readonly World world;
  		protected readonly Map Map;
  		protected readonly BuildingInfluence BuildingInfluence;
  		protected readonly CellLayer<ResourceLayerContents> Content;
  		protected readonly Dictionary<byte, string> ResourceTypesByIndex;
  
  		int resCells;
  
  		public event Action<CPos, string> CellChanged;
  
  		public ResourceLayer(Actor self, ResourceLayerInfo info)
  		{
  			this.info = info;
  			world = self.World;
  			Map = world.Map;
  			BuildingInfluence = self.Trait<BuildingInfluence>();
  			Content = new CellLayer<ResourceLayerContents>(Map);
  			ResourceTypesByIndex = info.ResourceTypes.ToDictionary(
  				kv => kv.Value.ResourceIndex,
  				kv => kv.Key);
  		}
  
  		protected virtual void WorldLoaded(World w, WorldRenderer wr)
  		{
  			foreach (var cell in w.Map.AllCells)
  			{
  				var resource = world.Map.Resources[cell];
  				if (!ResourceTypesByIndex.TryGetValue(resource.Type, out var resourceType))
  					continue;
  
  				if (!AllowResourceAt(resourceType, cell))
  					continue;
  
  				Content[cell] = CreateResourceCell(resourceType, cell, resource.Index);
  			}
  
  			if (!info.RecalculateResourceDensity)
  				return;
  
  			// Set initial density based on the number of neighboring resources
  			foreach (var cell in w.Map.AllCells)
  			{
  				var resource = Content[cell];
  				if (resource.Type == null || !info.ResourceTypes.TryGetValue(resource.Type, out var resourceInfo))
  					continue;
  
  				var adjacent = 0;
  				var directions = CVec.Directions;
  				for (var i = 0; i < directions.Length; i++)
  				{
  					var c = cell + directions[i];
  					if (Content.Contains(c) && Content[c].Type == resource.Type)
  						++adjacent;
  				}
  
  				// Adjacent includes the current cell, so is always >= 1
  				var density = Math.Max(int2.Lerp(0, resourceInfo.MaxDensity, adjacent, 9), 1);
  				Content[cell] = new ResourceLayerContents(resource.Type, density);
  			}
  		}
  
  		void IWorldLoaded.WorldLoaded(World w, WorldRenderer wr) { WorldLoaded(w, wr); }
  
  		protected virtual bool AllowResourceAt(string resourceType, CPos cell)
  		{
  			if (!Map.Contains(cell) || Map.Ramp[cell] != 0)
  				return false;
  
  			if (resourceType == null || !info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo))
  				return false;
  
  			if (!resourceInfo.AllowedTerrainTypes.Contains(Map.GetTerrainInfo(cell).Type))
  				return false;
  
  			return !BuildingInfluence.AnyBuildingAt(cell);
  		}
  
  		ResourceLayerContents CreateResourceCell(string resourceType, CPos cell, int density)
  		{
  			if (!info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo))
  			{
  				world.Map.CustomTerrain[cell] = byte.MaxValue;
  				return ResourceLayerContents.Empty;
  			}
  
  			world.Map.CustomTerrain[cell] = world.Map.Rules.TerrainInfo.GetTerrainIndex(resourceInfo.TerrainType);
  			++resCells;
  
  			return new ResourceLayerContents(resourceType, density.Clamp(1, resourceInfo.MaxDensity));
  		}
  
  		bool CanAddResource(string resourceType, CPos cell, int amount = 1)
  		{
  			if (!world.Map.Contains(cell))
  				return false;
  
  			if (resourceType == null || !info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo))
  				return false;
  
  			var content = Content[cell];
  			if (content.Type == null)
  				return amount <= resourceInfo.MaxDensity && AllowResourceAt(resourceType, cell);
  
  			if (content.Type != resourceType)
  				return false;
  
  			return content.Density + amount <= resourceInfo.MaxDensity;
  		}
  
  		int AddResource(string resourceType, CPos cell, int amount = 1)
  		{
  			if (!Content.Contains(cell))
  				return 0;
  
  			if (resourceType == null || !info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo))
  				return 0;
  
  			var content = Content[cell];
  			if (content.Type == null)
  				content = CreateResourceCell(resourceType, cell, 0);
  
  			if (content.Type != resourceType)
  				return 0;
  
  			var oldDensity = content.Density;
  			var density = Math.Min(resourceInfo.MaxDensity, oldDensity + amount);
  			Content[cell] = new ResourceLayerContents(content.Type, density);
  
  			CellChanged?.Invoke(cell, content.Type);
  
  			return density - oldDensity;
  		}
  
  		int RemoveResource(string resourceType, CPos cell, int amount = 1)
  		{
  			if (!Content.Contains(cell))
  				return 0;
  
  			var content = Content[cell];
  			if (content.Type == null || content.Type != resourceType)
  				return 0;
  
  			var oldDensity = content.Density;
  			var density = Math.Max(0, oldDensity - amount);
  
  			if (density == 0)
  			{
  				Content[cell] = ResourceLayerContents.Empty;
  				Map.CustomTerrain[cell] = byte.MaxValue;
  				--resCells;
  
  				CellChanged?.Invoke(cell, null);
  			}
  			else
  			{
  				Content[cell] = new ResourceLayerContents(content.Type, density);
  				CellChanged?.Invoke(cell, content.Type);
  			}
  
  			return oldDensity - density;
  		}
  
  		void ClearResources(CPos cell)
  		{
  			if (!Content.Contains(cell))
  				return;
  
  			// Don't break other users of CustomTerrain if there are no resources
  
gt: 			var content = Content[cell];