context: #region Copyright & License Information
  /*
   * Copyright 2007-2019 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 Eluant;
  using Eluant.ObjectBinding;
  using OpenRA.Activities;
  using OpenRA.Graphics;
  using OpenRA.Primitives;
  using OpenRA.Scripting;
  using OpenRA.Traits;
  
  namespace OpenRA
  {
  	public sealed class Actor : IScriptBindable, IScriptNotifyBind, ILuaTableBinding, ILuaEqualityBinding, ILuaToStringBinding, IEquatable<Actor>, IDisposable
  	{
  		internal struct SyncHash
  		{
  			public readonly ISync Trait;
  			readonly Func<object, int> hashFunction;
  			public SyncHash(ISync trait) { Trait = trait; hashFunction = Sync.GetHashFunction(trait); }
  			public int Hash() { return hashFunction(Trait); }
  		}
  
  		public readonly ActorInfo Info;
  
  		public readonly World World;
  
  		public readonly uint ActorID;
  
  		public Player Owner { get; internal set; }
  
  		public bool IsInWorld { get; internal set; }
  		public bool WillDispose { get; private set; }
  		public bool Disposed { get; private set; }
  
  		public Activity CurrentActivity { get; private set; }
  
  		public int Generation;
  		public Actor ReplacedByActor;
  
  		public IEffectiveOwner EffectiveOwner { get; private set; }
  		public IOccupySpace OccupiesSpace { get; private set; }
  		public ITargetable[] Targetables { get; private set; }
  
  		public bool IsIdle { get { return CurrentActivity == null; } }
  		public bool IsDead { get { return Disposed || (health != null && health.IsDead); } }
  
  		public CPos Location { get { return OccupiesSpace.TopLeft; } }
  		public WPos CenterPosition { get { return OccupiesSpace.CenterPosition; } }
  
  		public WRot Orientation
  		{
  			get
  			{
  				// TODO: Support non-zero pitch/roll in IFacing (IOrientation?)
  				var facingValue = facing != null ? facing.Facing : 0;
  				return new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(facingValue));
  			}
  		}
  
  		internal SyncHash[] SyncHashes { get; private set; }
  
  		readonly IFacing facing;
  		readonly IHealth health;
  		readonly IRenderModifier[] renderModifiers;
  		readonly IRender[] renders;
  		readonly IMouseBounds[] mouseBounds;
  		readonly IVisibilityModifier[] visibilityModifiers;
  		readonly IDefaultVisibility defaultVisibility;
  		readonly INotifyBecomingIdle[] becomingIdles;
  		readonly INotifyIdle[] tickIdles;
  		readonly ITargetablePositions[] targetablePositions;
  		WPos[] staticTargetablePositions;
  
  		internal Actor(World world, string name, TypeDictionary initDict)
  		{
  			var init = new ActorInitializer(this, initDict);
  
  			World = world;
  			ActorID = world.NextAID();
  			if (initDict.Contains<OwnerInit>())
  				Owner = init.Get<OwnerInit, Player>();
  
  			if (name != null)
  			{
  				name = name.ToLowerInvariant();
  
  				if (!world.Map.Rules.Actors.ContainsKey(name))
  					throw new NotImplementedException("No rules definition for unit " + name);
  
  				Info = world.Map.Rules.Actors[name];
  				foreach (var trait in Info.TraitsInConstructOrder())
  				{
  					AddTrait(trait.Create(init));
  
  					// Some traits rely on properties provided by IOccupySpace in their initialization,
  					// so we must ready it now, we cannot wait until all traits have finished construction.
  					if (trait is IOccupySpaceInfo)
  						OccupiesSpace = Trait<IOccupySpace>();
  				}
  			}
  
  			// PERF: Cache all these traits as soon as the actor is created. This is a fairly cheap one-off cost per
  			// actor that allows us to provide some fast implementations of commonly used methods that are relied on by
  			// performance-sensitive parts of the core game engine, such as pathfinding, visibility and rendering.
  			EffectiveOwner = TraitOrDefault<IEffectiveOwner>();
  			facing = TraitOrDefault<IFacing>();
  			health = TraitOrDefault<IHealth>();
  			renderModifiers = TraitsImplementing<IRenderModifier>().ToArray();
  			renders = TraitsImplementing<IRender>().ToArray();
  			mouseBounds = TraitsImplementing<IMouseBounds>().ToArray();
  			visibilityModifiers = TraitsImplementing<IVisibilityModifier>().ToArray();
  			defaultVisibility = Trait<IDefaultVisibility>();
  			becomingIdles = TraitsImplementing<INotifyBecomingIdle>().ToArray();
  			tickIdles = TraitsImplementing<INotifyIdle>().ToArray();
  			Targetables = TraitsImplementing<ITargetable>().ToArray();
  			targetablePositions = TraitsImplementing<ITargetablePositions>().ToArray();
  			world.AddFrameEndTask(w =>
  			{
  				// Caching this in a AddFrameEndTask, because trait construction order might cause problems if done directly at creation time.
  				// All actors that can move or teleport should have IPositionable, if not it's pretty safe to assume the actor is completely immobile and
  				// all targetable positions can be cached if all ITargetablePositions have no conditional requirements.
  				if (!Info.HasTraitInfo<IPositionableInfo>() && targetablePositions.Any() && targetablePositions.All(tp => tp.AlwaysEnabled))
  					staticTargetablePositions = targetablePositions.SelectMany(tp => tp.TargetablePositions(this)).ToArray();
  			});
  
  			SyncHashes = TraitsImplementing<ISync>().Select(sync => new SyncHash(sync)).ToArray();
  		}
  
  		public void Tick()
  		{
  			var wasIdle = IsIdle;
  			CurrentActivity = ActivityUtils.RunActivity(this, CurrentActivity);
  
  			if (!wasIdle && IsIdle)
  			{
  				foreach (var n in becomingIdles)
  					n.OnBecomingIdle(this);
  
  				// If IsIdle is true, it means the last CurrentActivity.Tick returned null.
  				// If a next activity has been queued via OnBecomingIdle, we need to start running it now,
  				// to avoid an 'empty' null tick where the actor will (visibly, if moving) do nothing.
  				CurrentActivity = ActivityUtils.RunActivity(this, CurrentActivity);
  			}
  			else if (wasIdle)
  				foreach (var tickIdle in tickIdles)
  					tickIdle.TickIdle(this);
  		}
  
  		public IEnumerable<IRenderable> Render(WorldRenderer wr)
  		{
  			// PERF: Avoid LINQ.
  			var renderables = Renderables(wr);
  			foreach (var modifier in renderModifiers)
  				renderables = modifier.ModifyRender(this, wr, renderables);
  			return renderables;
  		}
  
  		IEnumerable<IRenderable> Renderables(WorldRenderer wr)
  		{
  			// PERF: Avoid LINQ.
  			// Implementations of Render are permitted to return both an eagerly materialized collection or a lazily
  			// generated sequence.
  			// For large amounts of renderables, a lazily generated sequence (e.g. as returned by LINQ, or by using
  			// `yield`) will avoid the need to allocate a large collection.
  			// For small amounts of renderables, allocating a small collection can often be faster and require less
  			// memory than creating the objects needed to represent a sequence.
  			foreach (var render in renders)
  				foreach (var renderable in render.Render(this, wr))
  					yield return renderable;
  		}
  
  		public IEnumerable<Rectangle> ScreenBounds(WorldRenderer wr)
  		{
  			var bounds = Bounds(wr);
  			foreach (var modifier in renderModifiers)
  				bounds = modifier.ModifyScreenBounds(this, wr, bounds);
  			return bounds;
  		}
  
  		IEnumerable<Rectangle> Bounds(WorldRenderer wr)
  		{
  			// PERF: Avoid LINQ. See comments for Renderables
  			foreach (var render in renders)
  				foreach (var r in render.ScreenBounds(this, wr))
  					if (!r.IsEmpty)
  						yield return r;
  		}
  
  		public Rectangle MouseBounds(WorldRenderer wr)
  		{
  			foreach (var mb in mouseBounds)
  			{
  				var bounds = mb.MouseoverBounds(this, wr);
  				if (!bounds.IsEmpty)
  					return bounds;
  			}
  
  			return Rectangle.Empty;
  		}
  
  		public void QueueActivity(bool queued, Activity nextActivity)
  		{
  			if (!queued)
  				CancelActivity();
  			QueueActivity(nextActivity);
  		}
  
  		public void QueueActivity(Activity nextActivity)
  		{
  
gt: 			if (CurrentActivity == null)