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.Mods.Common.Pathfinder;
  using OpenRA.Primitives;
  using OpenRA.Support;
  using OpenRA.Traits;
  
  namespace OpenRA.Mods.Common.Traits
  {
  	[Flags]
  	public enum CellFlag : byte
  	{
  		HasFreeSpace = 0,
  		HasMovingActor = 1,
  		HasStationaryActor = 2,
  		HasMovableActor = 4,
  		HasCrushableActor = 8,
  		HasTemporaryBlocker = 16,
  		HasTransitOnlyActor = 32,
  	}
  
  	public static class LocomoterExts
  	{
  		public static bool HasCellFlag(this CellFlag c, CellFlag cellFlag)
  		{
  			// PERF: Enum.HasFlag is slower and requires allocations.
  			return (c & cellFlag) == cellFlag;
  		}
  
  		public static bool HasMovementType(this MovementType m, MovementType movementType)
  		{
  			// PERF: Enum.HasFlag is slower and requires allocations.
  			return (m & movementType) == movementType;
  		}
  	}
  
  	public static class CustomMovementLayerType
  	{
  		public const byte Tunnel = 1;
  		public const byte Subterranean = 2;
  		public const byte Jumpjet = 3;
  		public const byte ElevatedBridge = 4;
  	}
  
  	[TraitLocation(SystemActors.World | SystemActors.EditorWorld)]
  	[Desc("Used by Mobile. Attach these to the world actor. You can have multiple variants by adding @suffixes.")]
  	public class LocomotorInfo : TraitInfo
  	{
  		[Desc("Locomotor ID.")]
  		public readonly string Name = "default";
  
  		public readonly int WaitAverage = 40;
  
  		public readonly int WaitSpread = 10;
  
  		[Desc("Allow multiple (infantry) units in one cell.")]
  		public readonly bool SharesCell = false;
  
  		[Desc("Can the actor be ordered to move in to shroud?")]
  		public readonly bool MoveIntoShroud = true;
  
  		[Desc("e.g. crate, wall, infantry")]
  		public readonly BitSet<CrushClass> Crushes = default(BitSet<CrushClass>);
  
  		[Desc("Types of damage that are caused while crushing. Leave empty for no damage types.")]
  		public readonly BitSet<DamageType> CrushDamageTypes = default(BitSet<DamageType>);
  
  		[FieldLoader.LoadUsing(nameof(LoadSpeeds), true)]
  		[Desc("Lower the value on rough terrain. Leave out entries for impassable terrain.")]
  		public readonly Dictionary<string, TerrainInfo> TerrainSpeeds;
  
  		protected static object LoadSpeeds(MiniYaml y)
  		{
  			var ret = new Dictionary<string, TerrainInfo>();
  			foreach (var t in y.ToDictionary()["TerrainSpeeds"].Nodes)
  			{
  				var speed = FieldLoader.GetValue<int>("speed", t.Value.Value);
  				if (speed > 0)
  				{
  					var nodesDict = t.Value.ToDictionary();
  					var cost = (nodesDict.ContainsKey("PathingCost")
  						? FieldLoader.GetValue<short>("cost", nodesDict["PathingCost"].Value)
  						: 10000 / speed);
  					ret.Add(t.Key, new TerrainInfo(speed, (short)cost));
  				}
  			}
  
  			return ret;
  		}
  
  		public class TerrainInfo
  		{
  			public static readonly TerrainInfo Impassable = new TerrainInfo();
  
  			public readonly short Cost;
  			public readonly int Speed;
  
  			public TerrainInfo()
  			{
  				Cost = PathGraph.MovementCostForUnreachableCell;
  				Speed = 0;
  			}
  
  			public TerrainInfo(int speed, short cost)
  			{
  				Speed = speed;
  				Cost = cost;
  			}
  		}
  
  		public virtual bool DisableDomainPassabilityCheck => false;
  
  		public override object Create(ActorInitializer init) { return new Locomotor(init.Self, this); }
  	}
  
  	public class Locomotor : IWorldLoaded
  	{
  		readonly struct CellCache
  		{
  			public readonly LongBitSet<PlayerBitMask> Immovable;
  			public readonly LongBitSet<PlayerBitMask> Crushable;
  			public readonly CellFlag CellFlag;
  
  			public CellCache(LongBitSet<PlayerBitMask> immovable, CellFlag cellFlag, LongBitSet<PlayerBitMask> crushable = default(LongBitSet<PlayerBitMask>))
  			{
  				Immovable = immovable;
  				Crushable = crushable;
  				CellFlag = cellFlag;
  			}
  		}
  
  		public readonly LocomotorInfo Info;
  		public readonly uint MovementClass;
  
  		readonly LocomotorInfo.TerrainInfo[] terrainInfos;
  		readonly World world;
  		readonly HashSet<CPos> dirtyCells = new HashSet<CPos>();
  		readonly bool sharesCell;
  
  		CellLayer<short>[] cellsCost;
  		CellLayer<CellCache>[] blockingCache;
  
  		IActorMap actorMap;
  
  		public Locomotor(Actor self, LocomotorInfo info)
  		{
  			Info = info;
  			sharesCell = info.SharesCell;
  			world = self.World;
  
  			var terrainInfo = world.Map.Rules.TerrainInfo;
  			terrainInfos = new LocomotorInfo.TerrainInfo[terrainInfo.TerrainTypes.Length];
  			for (var i = 0; i < terrainInfos.Length; i++)
  				if (!info.TerrainSpeeds.TryGetValue(terrainInfo.TerrainTypes[i].Type, out terrainInfos[i]))
  					terrainInfos[i] = LocomotorInfo.TerrainInfo.Impassable;
  
  			MovementClass = (uint)terrainInfos.Select(ti => ti.Cost != PathGraph.MovementCostForUnreachableCell).ToBits();
  		}
  
  		public short MovementCostForCell(CPos cell)
  		{
  			return MovementCostForCell(cell, null);
  		}
  
  		short MovementCostForCell(CPos cell, CPos? fromCell)
  		{
  			if (!world.Map.Contains(cell))
  				return PathGraph.MovementCostForUnreachableCell;
  
  			// Prevent units from jumping over height discontinuities.
  			if (fromCell != null && cell.Layer == 0 && fromCell.Value.Layer == 0 && world.Map.Grid.MaximumTerrainHeight > 0)
  			{
  				var heightLayer = world.Map.Height;
  				if (Math.Abs(heightLayer[cell] - heightLayer[fromCell.Value]) > 1)
  					return PathGraph.MovementCostForUnreachableCell;
  			}
  
  			return cellsCost[cell.Layer][cell];
  		}
  
  		public int MovementSpeedForCell(CPos cell)
  		{
  			var index = cell.Layer == 0 ? world.Map.GetTerrainIndex(cell) :
  				world.GetCustomMovementLayers()[cell.Layer].GetTerrainIndex(cell);
  
  			return terrainInfos[index].Speed;
  		}
  
  		public short MovementCostToEnterCell(Actor actor, CPos srcNode, CPos destNode, BlockedByActor check, Actor ignoreActor)
  		{
  			var cellCost = MovementCostForCell(destNode, srcNode);
  
  			if (cellCost == PathGraph.MovementCostForUnreachableCell ||
  				!CanMoveFreelyInto(actor, destNode, check, ignoreActor))
  				return PathGraph.MovementCostForUnreachableCell;
  
  			return cellCost;
  		}
  
  		// Determines whether the actor is blocked by other Actors
  		public bool CanMoveFreelyInto(Actor actor, CPos cell, BlockedByActor check, Actor ignoreActor)
  		{
  			return CanMoveFreelyInto(actor, cell, SubCell.FullCell, check, ignoreActor);
  		}
  
  		public bool CanMoveFreelyInto(Actor actor, CPos cell, SubCell subCell, BlockedByActor check, Actor ignoreActor)
  		{
  			// If the check allows: We are not blocked by transient actors.
  			if (check == BlockedByActor.None)
  				return true;
  
  			var cellCache = GetCache(cell);
  			var cellFlag = cellCache.CellFlag;
  
  			// No actor in the cell or free SubCell.
  			if (cellFlag == CellFlag.HasFreeSpace)
  				return true;
  
  			// If actor is null we're just checking what would happen theoretically.
  			// In such a scenario - we'll just assume any other actor in the cell will block us by default.
  			// If we have a real actor, we can then perform the extra checks that allow us to avoid being blocked.
  
gt: 			if (actor == null)