1 #region Copyright & License Information
2 /*
3  * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
4  * This file is part of OpenRA, which is free software. It is made
5  * available to you under the terms of the GNU General Public License
6  * as published by the Free Software Foundation, either version 3 of
7  * the License, or (at your option) any later version. For more
8  * information, see COPYING.
9  */
10 #endregion
11 
12 using System;
13 using System.Collections.Generic;
14 using System.Linq;
15 using Eluant;
16 using Eluant.ObjectBinding;
17 using OpenRA.Activities;
18 using OpenRA.Graphics;
19 using OpenRA.Primitives;
20 using OpenRA.Scripting;
21 using OpenRA.Traits;
22 
23 namespace OpenRA
24 {
25 	public sealed class Actor : IScriptBindable, IScriptNotifyBind, ILuaTableBinding, ILuaEqualityBinding, ILuaToStringBinding, IEquatable<Actor>, IDisposable
26 	{
27 		internal struct SyncHash
28 		{
29 			public readonly ISync Trait;
30 			readonly Func<object, int> hashFunction;
SyncHashOpenRA.Actor.SyncHash31 			public SyncHash(ISync trait) { Trait = trait; hashFunction = Sync.GetHashFunction(trait); }
HashOpenRA.Actor.SyncHash32 			public int Hash() { return hashFunction(Trait); }
33 		}
34 
35 		public readonly ActorInfo Info;
36 
37 		public readonly World World;
38 
39 		public readonly uint ActorID;
40 
41 		public Player Owner { get; internal set; }
42 
43 		public bool IsInWorld { get; internal set; }
44 		public bool WillDispose { get; private set; }
45 		public bool Disposed { get; private set; }
46 
47 		Activity currentActivity;
48 		public Activity CurrentActivity
49 		{
50 			get { return Activity.SkipDoneActivities(currentActivity); }
51 			private set { currentActivity = value; }
52 		}
53 
54 		public int Generation;
55 		public Actor ReplacedByActor;
56 
57 		public IEffectiveOwner EffectiveOwner { get; private set; }
58 		public IOccupySpace OccupiesSpace { get; private set; }
59 		public ITargetable[] Targetables { get; private set; }
60 
61 		public bool IsIdle { get { return CurrentActivity == null; } }
62 		public bool IsDead { get { return Disposed || (health != null && health.IsDead); } }
63 
64 		public CPos Location { get { return OccupiesSpace.TopLeft; } }
65 		public WPos CenterPosition { get { return OccupiesSpace.CenterPosition; } }
66 
67 		public WRot Orientation
68 		{
69 			get
70 			{
71 				// TODO: Support non-zero pitch/roll in IFacing (IOrientation?)
72 				var facingValue = facing != null ? facing.Facing : 0;
73 				return new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(facingValue));
74 			}
75 		}
76 
77 		internal SyncHash[] SyncHashes { get; private set; }
78 
79 		readonly IFacing facing;
80 		readonly IHealth health;
81 		readonly IRenderModifier[] renderModifiers;
82 		readonly IRender[] renders;
83 		readonly IMouseBounds[] mouseBounds;
84 		readonly IVisibilityModifier[] visibilityModifiers;
85 		readonly IDefaultVisibility defaultVisibility;
86 		readonly INotifyBecomingIdle[] becomingIdles;
87 		readonly INotifyIdle[] tickIdles;
88 		readonly ITargetablePositions[] targetablePositions;
89 		WPos[] staticTargetablePositions;
90 		bool created;
91 
Actor(World world, string name, TypeDictionary initDict)92 		internal Actor(World world, string name, TypeDictionary initDict)
93 		{
94 			var init = new ActorInitializer(this, initDict);
95 
96 			World = world;
97 			ActorID = world.NextAID();
98 			if (initDict.Contains<OwnerInit>())
99 				Owner = init.Get<OwnerInit, Player>();
100 
101 			if (name != null)
102 			{
103 				name = name.ToLowerInvariant();
104 
105 				if (!world.Map.Rules.Actors.ContainsKey(name))
106 					throw new NotImplementedException("No rules definition for unit " + name);
107 
108 				Info = world.Map.Rules.Actors[name];
109 				foreach (var trait in Info.TraitsInConstructOrder())
110 				{
111 					AddTrait(trait.Create(init));
112 
113 					// Some traits rely on properties provided by IOccupySpace in their initialization,
114 					// so we must ready it now, we cannot wait until all traits have finished construction.
115 					if (trait is IOccupySpaceInfo)
116 						OccupiesSpace = Trait<IOccupySpace>();
117 				}
118 			}
119 
120 			// PERF: Cache all these traits as soon as the actor is created. This is a fairly cheap one-off cost per
121 			// actor that allows us to provide some fast implementations of commonly used methods that are relied on by
122 			// performance-sensitive parts of the core game engine, such as pathfinding, visibility and rendering.
123 			EffectiveOwner = TraitOrDefault<IEffectiveOwner>();
124 			facing = TraitOrDefault<IFacing>();
125 			health = TraitOrDefault<IHealth>();
126 			renderModifiers = TraitsImplementing<IRenderModifier>().ToArray();
127 			renders = TraitsImplementing<IRender>().ToArray();
128 			mouseBounds = TraitsImplementing<IMouseBounds>().ToArray();
129 			visibilityModifiers = TraitsImplementing<IVisibilityModifier>().ToArray();
130 			defaultVisibility = Trait<IDefaultVisibility>();
131 			becomingIdles = TraitsImplementing<INotifyBecomingIdle>().ToArray();
132 			tickIdles = TraitsImplementing<INotifyIdle>().ToArray();
133 			Targetables = TraitsImplementing<ITargetable>().ToArray();
134 			targetablePositions = TraitsImplementing<ITargetablePositions>().ToArray();
135 			world.AddFrameEndTask(w =>
136 			{
137 				// Caching this in a AddFrameEndTask, because trait construction order might cause problems if done directly at creation time.
138 				// All actors that can move or teleport should have IPositionable, if not it's pretty safe to assume the actor is completely immobile and
139 				// all targetable positions can be cached if all ITargetablePositions have no conditional requirements.
140 				if (!Info.HasTraitInfo<IPositionableInfo>() && targetablePositions.Any() && targetablePositions.All(tp => tp.AlwaysEnabled))
141 					staticTargetablePositions = targetablePositions.SelectMany(tp => tp.TargetablePositions(this)).ToArray();
142 			});
143 
144 			SyncHashes = TraitsImplementing<ISync>().Select(sync => new SyncHash(sync)).ToArray();
145 		}
146 
Created()147 		internal void Created()
148 		{
149 			created = true;
150 
151 			foreach (var t in TraitsImplementing<INotifyCreated>())
152 				t.Created(this);
153 
154 			// The initial activity should run before any activities queued by INotifyCreated.Created
155 			// However, we need to know which traits are enabled (via conditions), so wait for after the calls and insert the activity as the first
156 			ICreationActivity creationActivity = null;
157 			foreach (var ica in TraitsImplementing<ICreationActivity>())
158 			{
159 				if (!ica.IsTraitEnabled())
160 					continue;
161 
162 				if (creationActivity != null)
163 					throw new InvalidOperationException("More than one enabled ICreationActivity trait: {0} and {1}".F(creationActivity.GetType().Name, ica.GetType().Name));
164 
165 				var activity = ica.GetCreationActivity();
166 				if (activity == null)
167 					continue;
168 
169 				creationActivity = ica;
170 
171 				activity.Queue(CurrentActivity);
172 				CurrentActivity = activity;
173 			}
174 		}
175 
Tick()176 		public void Tick()
177 		{
178 			var wasIdle = IsIdle;
179 			CurrentActivity = ActivityUtils.RunActivity(this, CurrentActivity);
180 
181 			if (!wasIdle && IsIdle)
182 			{
183 				foreach (var n in becomingIdles)
184 					n.OnBecomingIdle(this);
185 
186 				// If IsIdle is true, it means the last CurrentActivity.Tick returned null.
187 				// If a next activity has been queued via OnBecomingIdle, we need to start running it now,
188 				// to avoid an 'empty' null tick where the actor will (visibly, if moving) do nothing.
189 				CurrentActivity = ActivityUtils.RunActivity(this, CurrentActivity);
190 			}
191 			else if (wasIdle)
192 				foreach (var tickIdle in tickIdles)
193 					tickIdle.TickIdle(this);
194 		}
195 
Render(WorldRenderer wr)196 		public IEnumerable<IRenderable> Render(WorldRenderer wr)
197 		{
198 			// PERF: Avoid LINQ.
199 			var renderables = Renderables(wr);
200 			foreach (var modifier in renderModifiers)
201 				renderables = modifier.ModifyRender(this, wr, renderables);
202 			return renderables;
203 		}
204 
Renderables(WorldRenderer wr)205 		IEnumerable<IRenderable> Renderables(WorldRenderer wr)
206 		{
207 			// PERF: Avoid LINQ.
208 			// Implementations of Render are permitted to return both an eagerly materialized collection or a lazily
209 			// generated sequence.
210 			// For large amounts of renderables, a lazily generated sequence (e.g. as returned by LINQ, or by using
211 			// `yield`) will avoid the need to allocate a large collection.
212 			// For small amounts of renderables, allocating a small collection can often be faster and require less
213 			// memory than creating the objects needed to represent a sequence.
214 			foreach (var render in renders)
215 				foreach (var renderable in render.Render(this, wr))
216 					yield return renderable;
217 		}
218 
ScreenBounds(WorldRenderer wr)219 		public IEnumerable<Rectangle> ScreenBounds(WorldRenderer wr)
220 		{
221 			var bounds = Bounds(wr);
222 			foreach (var modifier in renderModifiers)
223 				bounds = modifier.ModifyScreenBounds(this, wr, bounds);
224 			return bounds;
225 		}
226 
Bounds(WorldRenderer wr)227 		IEnumerable<Rectangle> Bounds(WorldRenderer wr)
228 		{
229 			// PERF: Avoid LINQ. See comments for Renderables
230 			foreach (var render in renders)
231 				foreach (var r in render.ScreenBounds(this, wr))
232 					if (!r.IsEmpty)
233 						yield return r;
234 		}
235 
MouseBounds(WorldRenderer wr)236 		public Rectangle MouseBounds(WorldRenderer wr)
237 		{
238 			foreach (var mb in mouseBounds)
239 			{
240 				var bounds = mb.MouseoverBounds(this, wr);
241 				if (!bounds.IsEmpty)
242 					return bounds;
243 			}
244 
245 			return Rectangle.Empty;
246 		}
247 
QueueActivity(bool queued, Activity nextActivity)248 		public void QueueActivity(bool queued, Activity nextActivity)
249 		{
250 			if (!queued)
251 				CancelActivity();
252 
253 			QueueActivity(nextActivity);
254 		}
255 
QueueActivity(Activity nextActivity)256 		public void QueueActivity(Activity nextActivity)
257 		{
258 			if (!created)
259 				throw new InvalidOperationException("An activity was queued before the actor was created. Queue it inside the INotifyCreated.Created callback instead.");
260 
261 			if (CurrentActivity == null)
262 				CurrentActivity = nextActivity;
263 			else
264 				CurrentActivity.Queue(nextActivity);
265 		}
266 
CancelActivity()267 		public void CancelActivity()
268 		{
269 			if (CurrentActivity != null)
270 				CurrentActivity.Cancel(this);
271 		}
272 
GetHashCode()273 		public override int GetHashCode()
274 		{
275 			return (int)ActorID;
276 		}
277 
Equals(object obj)278 		public override bool Equals(object obj)
279 		{
280 			var o = obj as Actor;
281 			return o != null && Equals(o);
282 		}
283 
Equals(Actor other)284 		public bool Equals(Actor other)
285 		{
286 			return ActorID == other.ActorID;
287 		}
288 
ToString()289 		public override string ToString()
290 		{
291 			// PERF: Avoid format strings.
292 			var name = Info.Name + " " + ActorID;
293 			if (!IsInWorld)
294 				name += " (not in world)";
295 			return name;
296 		}
297 
Trait()298 		public T Trait<T>()
299 		{
300 			return World.TraitDict.Get<T>(this);
301 		}
302 
TraitOrDefault()303 		public T TraitOrDefault<T>()
304 		{
305 			return World.TraitDict.GetOrDefault<T>(this);
306 		}
307 
TraitsImplementing()308 		public IEnumerable<T> TraitsImplementing<T>()
309 		{
310 			return World.TraitDict.WithInterface<T>(this);
311 		}
312 
AddTrait(object trait)313 		public void AddTrait(object trait)
314 		{
315 			World.TraitDict.AddTrait(this, trait);
316 		}
317 
Dispose()318 		public void Dispose()
319 		{
320 			// If CurrentActivity isn't null, run OnActorDisposeOuter in case some cleanups are needed.
321 			// This should be done before the FrameEndTask to avoid dependency issues.
322 			if (CurrentActivity != null)
323 				CurrentActivity.OnActorDisposeOuter(this);
324 
325 			// Allow traits/activities to prevent a race condition when they depend on disposing the actor (e.g. Transforms)
326 			WillDispose = true;
327 
328 			World.AddFrameEndTask(w =>
329 			{
330 				if (Disposed)
331 					return;
332 
333 				if (IsInWorld)
334 					World.Remove(this);
335 
336 				foreach (var t in TraitsImplementing<INotifyActorDisposing>())
337 					t.Disposing(this);
338 
339 				World.TraitDict.RemoveActor(this);
340 				Disposed = true;
341 
342 				if (luaInterface != null)
343 					luaInterface.Value.OnActorDestroyed();
344 			});
345 		}
346 
347 		// TODO: move elsewhere.
ChangeOwner(Player newOwner)348 		public void ChangeOwner(Player newOwner)
349 		{
350 			World.AddFrameEndTask(_ => ChangeOwnerSync(newOwner));
351 		}
352 
353 		/// <summary>
354 		/// Change the actors owner without queuing a FrameEndTask.
355 		/// This must only be called from inside an existing FrameEndTask.
356 		/// </summary>
ChangeOwnerSync(Player newOwner)357 		public void ChangeOwnerSync(Player newOwner)
358 		{
359 			if (Disposed)
360 				return;
361 
362 			var oldOwner = Owner;
363 			var wasInWorld = IsInWorld;
364 
365 			// momentarily remove from world so the ownership queries don't get confused
366 			if (wasInWorld)
367 				World.Remove(this);
368 
369 			Owner = newOwner;
370 			Generation++;
371 
372 			foreach (var t in TraitsImplementing<INotifyOwnerChanged>())
373 				t.OnOwnerChanged(this, oldOwner, newOwner);
374 
375 			foreach (var t in World.WorldActor.TraitsImplementing<INotifyOwnerChanged>())
376 				t.OnOwnerChanged(this, oldOwner, newOwner);
377 
378 			if (wasInWorld)
379 				World.Add(this);
380 		}
381 
GetDamageState()382 		public DamageState GetDamageState()
383 		{
384 			if (Disposed)
385 				return DamageState.Dead;
386 
387 			return (health == null) ? DamageState.Undamaged : health.DamageState;
388 		}
389 
InflictDamage(Actor attacker, Damage damage)390 		public void InflictDamage(Actor attacker, Damage damage)
391 		{
392 			if (Disposed || health == null)
393 				return;
394 
395 			health.InflictDamage(this, attacker, damage, false);
396 		}
397 
Kill(Actor attacker, BitSet<DamageType> damageTypes = default(BitSet<DamageType>))398 		public void Kill(Actor attacker, BitSet<DamageType> damageTypes = default(BitSet<DamageType>))
399 		{
400 			if (Disposed || health == null)
401 				return;
402 
403 			health.Kill(this, attacker, damageTypes);
404 		}
405 
CanBeViewedByPlayer(Player player)406 		public bool CanBeViewedByPlayer(Player player)
407 		{
408 			// PERF: Avoid LINQ.
409 			foreach (var visibilityModifier in visibilityModifiers)
410 				if (!visibilityModifier.IsVisible(this, player))
411 					return false;
412 
413 			return defaultVisibility.IsVisible(this, player);
414 		}
415 
GetAllTargetTypes()416 		public BitSet<TargetableType> GetAllTargetTypes()
417 		{
418 			// PERF: Avoid LINQ.
419 			var targetTypes = default(BitSet<TargetableType>);
420 			foreach (var targetable in Targetables)
421 				targetTypes = targetTypes.Union(targetable.TargetTypes);
422 			return targetTypes;
423 		}
424 
GetEnabledTargetTypes()425 		public BitSet<TargetableType> GetEnabledTargetTypes()
426 		{
427 			// PERF: Avoid LINQ.
428 			var targetTypes = default(BitSet<TargetableType>);
429 			foreach (var targetable in Targetables)
430 				if (targetable.IsTraitEnabled())
431 					targetTypes = targetTypes.Union(targetable.TargetTypes);
432 			return targetTypes;
433 		}
434 
IsTargetableBy(Actor byActor)435 		public bool IsTargetableBy(Actor byActor)
436 		{
437 			// PERF: Avoid LINQ.
438 			foreach (var targetable in Targetables)
439 				if (targetable.IsTraitEnabled() && targetable.TargetableBy(this, byActor))
440 					return true;
441 
442 			return false;
443 		}
444 
GetTargetablePositions()445 		public IEnumerable<WPos> GetTargetablePositions()
446 		{
447 			if (staticTargetablePositions != null)
448 				return staticTargetablePositions;
449 
450 			var enabledTargetablePositionTraits = targetablePositions.Where(Exts.IsTraitEnabled);
451 			if (enabledTargetablePositionTraits.Any())
452 				return enabledTargetablePositionTraits.SelectMany(tp => tp.TargetablePositions(this));
453 
454 			return new[] { CenterPosition };
455 		}
456 
457 		#region Scripting interface
458 
459 		Lazy<ScriptActorInterface> luaInterface;
OnScriptBind(ScriptContext context)460 		public void OnScriptBind(ScriptContext context)
461 		{
462 			if (luaInterface == null)
463 				luaInterface = Exts.Lazy(() => new ScriptActorInterface(context, this));
464 		}
465 
466 		public LuaValue this[LuaRuntime runtime, LuaValue keyValue]
467 		{
468 			get { return luaInterface.Value[runtime, keyValue]; }
469 			set { luaInterface.Value[runtime, keyValue] = value; }
470 		}
471 
Equals(LuaRuntime runtime, LuaValue left, LuaValue right)472 		public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
473 		{
474 			Actor a, b;
475 			if (!left.TryGetClrValue(out a) || !right.TryGetClrValue(out b))
476 				return false;
477 
478 			return a == b;
479 		}
480 
ToString(LuaRuntime runtime)481 		public LuaValue ToString(LuaRuntime runtime)
482 		{
483 			return "Actor ({0})".F(this);
484 		}
485 
HasScriptProperty(string name)486 		public bool HasScriptProperty(string name)
487 		{
488 			return luaInterface.Value.ContainsKey(name);
489 		}
490 
491 		#endregion
492 	}
493 }
494