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 OpenRA.Effects;
16 using OpenRA.FileFormats;
17 using OpenRA.Graphics;
18 using OpenRA.Network;
19 using OpenRA.Orders;
20 using OpenRA.Primitives;
21 using OpenRA.Support;
22 using OpenRA.Traits;
23 
24 namespace OpenRA
25 {
26 	public enum WorldType { Regular, Shellmap, Editor }
27 
28 	public sealed class World : IDisposable
29 	{
30 		internal readonly TraitDictionary TraitDict = new TraitDictionary();
31 		readonly SortedDictionary<uint, Actor> actors = new SortedDictionary<uint, Actor>();
32 		readonly List<IEffect> effects = new List<IEffect>();
33 		readonly List<IEffect> unpartitionedEffects = new List<IEffect>();
34 		readonly List<ISync> syncedEffects = new List<ISync>();
35 
36 		readonly Queue<Action<World>> frameEndActions = new Queue<Action<World>>();
37 
38 		public int Timestep;
39 
40 		internal readonly OrderManager OrderManager;
41 		public Session LobbyInfo { get { return OrderManager.LobbyInfo; } }
42 
43 		public readonly MersenneTwister SharedRandom;
44 		public readonly MersenneTwister LocalRandom;
45 		public readonly IModelCache ModelCache;
46 		public LongBitSet<PlayerBitMask> AllPlayersMask = default(LongBitSet<PlayerBitMask>);
47 		public readonly LongBitSet<PlayerBitMask> NoPlayersMask = default(LongBitSet<PlayerBitMask>);
48 
49 		public Player[] Players = new Player[0];
50 
51 		public event Action<Player> RenderPlayerChanged;
52 
SetPlayers(IEnumerable<Player> players, Player localPlayer)53 		public void SetPlayers(IEnumerable<Player> players, Player localPlayer)
54 		{
55 			if (Players.Length > 0)
56 				throw new InvalidOperationException("Players are fixed once they have been set.");
57 			Players = players.ToArray();
58 			SetLocalPlayer(localPlayer);
59 		}
60 
61 		public Player LocalPlayer { get; private set; }
62 
63 		public event Action GameOver = () => { };
64 		public bool IsGameOver { get; private set; }
EndGame()65 		public void EndGame()
66 		{
67 			if (!IsGameOver)
68 			{
69 				IsGameOver = true;
70 
71 				foreach (var t in WorldActor.TraitsImplementing<IGameOver>())
72 					t.GameOver(this);
73 				gameInfo.FinalGameTick = WorldTick;
74 				GameOver();
75 			}
76 		}
77 
78 		Player renderPlayer;
79 		public Player RenderPlayer
80 		{
81 			get
82 			{
83 				return renderPlayer;
84 			}
85 
86 			set
87 			{
88 				if (LocalPlayer == null || LocalPlayer.UnlockedRenderPlayer)
89 				{
90 					renderPlayer = value;
91 
92 					if (RenderPlayerChanged != null)
93 						RenderPlayerChanged(value);
94 				}
95 			}
96 		}
97 
FogObscures(Actor a)98 		public bool FogObscures(Actor a) { return RenderPlayer != null && !a.CanBeViewedByPlayer(RenderPlayer); }
FogObscures(CPos p)99 		public bool FogObscures(CPos p) { return RenderPlayer != null && !RenderPlayer.Shroud.IsVisible(p); }
FogObscures(WPos pos)100 		public bool FogObscures(WPos pos) { return RenderPlayer != null && !RenderPlayer.Shroud.IsVisible(pos); }
ShroudObscures(CPos p)101 		public bool ShroudObscures(CPos p) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(p); }
ShroudObscures(MPos uv)102 		public bool ShroudObscures(MPos uv) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(uv); }
ShroudObscures(WPos pos)103 		public bool ShroudObscures(WPos pos) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(pos); }
ShroudObscures(PPos uv)104 		public bool ShroudObscures(PPos uv) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(uv); }
105 
106 		public bool IsReplay
107 		{
108 			get { return OrderManager.Connection is ReplayConnection; }
109 		}
110 
111 		public bool IsLoadingGameSave
112 		{
113 			get { return OrderManager.NetFrameNumber <= OrderManager.GameSaveLastFrame; }
114 		}
115 
116 		public int GameSaveLoadingPercentage
117 		{
118 			get { return OrderManager.NetFrameNumber * 100 / OrderManager.GameSaveLastFrame; }
119 		}
120 
SetLocalPlayer(Player localPlayer)121 		void SetLocalPlayer(Player localPlayer)
122 		{
123 			if (localPlayer == null)
124 				return;
125 
126 			if (!Players.Contains(localPlayer))
127 				throw new ArgumentException("The local player must be one of the players in the world.", "localPlayer");
128 
129 			if (IsReplay)
130 				return;
131 
132 			LocalPlayer = localPlayer;
133 
134 			// Set the property backing field directly
135 			renderPlayer = LocalPlayer;
136 		}
137 
138 		public readonly Actor WorldActor;
139 
140 		public readonly Map Map;
141 
142 		public readonly IActorMap ActorMap;
143 		public readonly ScreenMap ScreenMap;
144 		public readonly WorldType Type;
145 
146 		readonly GameInformation gameInfo;
147 
148 		// Hide the OrderManager from mod code
IssueOrder(Order o)149 		public void IssueOrder(Order o) { OrderManager.IssueOrder(o); }
150 
151 		IOrderGenerator orderGenerator;
152 		public IOrderGenerator OrderGenerator
153 		{
154 			get
155 			{
156 				return orderGenerator;
157 			}
158 
159 			set
160 			{
161 				Sync.AssertUnsynced("The current order generator may not be changed from synced code");
162 				if (orderGenerator != null)
163 					orderGenerator.Deactivate();
164 
165 				orderGenerator = value;
166 			}
167 		}
168 
169 		public readonly ISelection Selection;
170 
CancelInputMode()171 		public void CancelInputMode() { OrderGenerator = new UnitOrderGenerator(); }
172 
173 		public bool ToggleInputMode<T>() where T : IOrderGenerator, new()
174 		{
175 			if (OrderGenerator is T)
176 			{
177 				CancelInputMode();
178 				return false;
179 			}
180 			else
181 			{
182 				OrderGenerator = new T();
183 				return true;
184 			}
185 		}
186 
187 		public bool RulesContainTemporaryBlocker { get; private set; }
188 
189 		bool wasLoadingGameSave;
190 
World(ModData modData, Map map, OrderManager orderManager, WorldType type)191 		internal World(ModData modData, Map map, OrderManager orderManager, WorldType type)
192 		{
193 			Type = type;
194 			OrderManager = orderManager;
195 			orderGenerator = new UnitOrderGenerator();
196 			Map = map;
197 			Timestep = orderManager.LobbyInfo.GlobalSettings.Timestep;
198 			SharedRandom = new MersenneTwister(orderManager.LobbyInfo.GlobalSettings.RandomSeed);
199 			LocalRandom = new MersenneTwister();
200 
201 			ModelCache = modData.ModelSequenceLoader.CacheModels(map, modData, map.Rules.ModelSequences);
202 
203 			var worldActorType = type == WorldType.Editor ? "EditorWorld" : "World";
204 			WorldActor = CreateActor(worldActorType, new TypeDictionary());
205 			ActorMap = WorldActor.Trait<IActorMap>();
206 			ScreenMap = WorldActor.Trait<ScreenMap>();
207 			Selection = WorldActor.Trait<ISelection>();
208 
209 			// Reset mask
210 			LongBitSet<PlayerBitMask>.Reset();
211 
212 			// Add players
213 			foreach (var cmp in WorldActor.TraitsImplementing<ICreatePlayers>())
214 				cmp.CreatePlayers(this);
215 
216 			// Set defaults for any unset stances
217 			foreach (var p in Players)
218 			{
219 				if (!p.Spectating)
220 					AllPlayersMask = AllPlayersMask.Union(p.PlayerMask);
221 
222 				foreach (var q in Players)
223 				{
224 					SetUpPlayerMask(p, q);
225 
226 					if (!p.Stances.ContainsKey(q))
227 						p.Stances[q] = Stance.Neutral;
228 				}
229 			}
230 
231 			Game.Sound.SoundVolumeModifier = 1.0f;
232 
233 			gameInfo = new GameInformation
234 			{
235 				Mod = Game.ModData.Manifest.Id,
236 				Version = Game.ModData.Manifest.Metadata.Version,
237 
238 				MapUid = Map.Uid,
239 				MapTitle = Map.Title
240 			};
241 
242 			RulesContainTemporaryBlocker = map.Rules.Actors.Any(a => a.Value.HasTraitInfo<ITemporaryBlockerInfo>());
243 		}
244 
SetUpPlayerMask(Player p, Player q)245 		void SetUpPlayerMask(Player p, Player q)
246 		{
247 			if (q.Spectating)
248 				return;
249 
250 			var bitSet = q.PlayerMask;
251 
252 			switch (p.Stances[q])
253 			{
254 				case Stance.Enemy:
255 				case Stance.Neutral:
256 					p.EnemyPlayersMask = p.EnemyPlayersMask.Union(bitSet);
257 					break;
258 				case Stance.Ally:
259 					p.AlliedPlayersMask = p.AlliedPlayersMask.Union(bitSet);
260 					break;
261 			}
262 		}
263 
AddToMaps(Actor self, IOccupySpace ios)264 		public void AddToMaps(Actor self, IOccupySpace ios)
265 		{
266 			ActorMap.AddInfluence(self, ios);
267 			ActorMap.AddPosition(self, ios);
268 			ScreenMap.AddOrUpdate(self);
269 		}
270 
UpdateMaps(Actor self, IOccupySpace ios)271 		public void UpdateMaps(Actor self, IOccupySpace ios)
272 		{
273 			if (!self.IsInWorld)
274 				return;
275 
276 			ScreenMap.AddOrUpdate(self);
277 			ActorMap.UpdatePosition(self, ios);
278 		}
279 
RemoveFromMaps(Actor self, IOccupySpace ios)280 		public void RemoveFromMaps(Actor self, IOccupySpace ios)
281 		{
282 			ActorMap.RemoveInfluence(self, ios);
283 			ActorMap.RemovePosition(self, ios);
284 			ScreenMap.Remove(self);
285 		}
286 
LoadComplete(WorldRenderer wr)287 		public void LoadComplete(WorldRenderer wr)
288 		{
289 			if (IsLoadingGameSave)
290 			{
291 				wasLoadingGameSave = true;
292 				Game.Sound.DisableAllSounds = true;
293 				foreach (var nsr in WorldActor.TraitsImplementing<INotifyGameLoading>())
294 					nsr.GameLoading(this);
295 			}
296 
297 			// ScreenMap must be initialized before anything else
298 			using (new PerfTimer("ScreenMap.WorldLoaded"))
299 				ScreenMap.WorldLoaded(this, wr);
300 
301 			foreach (var iwl in WorldActor.TraitsImplementing<IWorldLoaded>())
302 			{
303 				// These have already been initialized
304 				if (iwl == ScreenMap)
305 					continue;
306 
307 				using (new PerfTimer(iwl.GetType().Name + ".WorldLoaded"))
308 					iwl.WorldLoaded(this, wr);
309 			}
310 
311 			foreach (var p in Players)
312 				foreach (var iwl in p.PlayerActor.TraitsImplementing<IWorldLoaded>())
313 					using (new PerfTimer(iwl.GetType().Name + ".WorldLoaded"))
314 						iwl.WorldLoaded(this, wr);
315 
316 			gameInfo.StartTimeUtc = DateTime.UtcNow;
317 			foreach (var player in Players)
318 				gameInfo.AddPlayer(player, OrderManager.LobbyInfo);
319 
320 			var echo = OrderManager.Connection as EchoConnection;
321 			var rc = echo != null ? echo.Recorder : null;
322 
323 			if (rc != null)
324 				rc.Metadata = new ReplayMetadata(gameInfo);
325 		}
326 
SetWorldOwner(Player p)327 		public void SetWorldOwner(Player p)
328 		{
329 			WorldActor.Owner = p;
330 		}
331 
CreateActor(string name, TypeDictionary initDict)332 		public Actor CreateActor(string name, TypeDictionary initDict)
333 		{
334 			return CreateActor(true, name, initDict);
335 		}
336 
CreateActor(bool addToWorld, string name, TypeDictionary initDict)337 		public Actor CreateActor(bool addToWorld, string name, TypeDictionary initDict)
338 		{
339 			var a = new Actor(this, name, initDict);
340 			a.Created();
341 			if (addToWorld)
342 				Add(a);
343 
344 			return a;
345 		}
346 
Add(Actor a)347 		public void Add(Actor a)
348 		{
349 			a.IsInWorld = true;
350 			actors.Add(a.ActorID, a);
351 			ActorAdded(a);
352 
353 			foreach (var t in a.TraitsImplementing<INotifyAddedToWorld>())
354 				t.AddedToWorld(a);
355 		}
356 
Remove(Actor a)357 		public void Remove(Actor a)
358 		{
359 			a.IsInWorld = false;
360 			actors.Remove(a.ActorID);
361 			ActorRemoved(a);
362 
363 			foreach (var t in a.TraitsImplementing<INotifyRemovedFromWorld>())
364 				t.RemovedFromWorld(a);
365 		}
366 
Add(IEffect e)367 		public void Add(IEffect e)
368 		{
369 			effects.Add(e);
370 
371 			var sp = e as ISpatiallyPartitionable;
372 			if (sp == null)
373 				unpartitionedEffects.Add(e);
374 
375 			var se = e as ISync;
376 			if (se != null)
377 				syncedEffects.Add(se);
378 		}
379 
Remove(IEffect e)380 		public void Remove(IEffect e)
381 		{
382 			effects.Remove(e);
383 
384 			var sp = e as ISpatiallyPartitionable;
385 			if (sp == null)
386 				unpartitionedEffects.Remove(e);
387 
388 			var se = e as ISync;
389 			if (se != null)
390 				syncedEffects.Remove(se);
391 		}
392 
RemoveAll(Predicate<IEffect> predicate)393 		public void RemoveAll(Predicate<IEffect> predicate)
394 		{
395 			effects.RemoveAll(predicate);
396 			unpartitionedEffects.RemoveAll(e => predicate((IEffect)e));
397 			syncedEffects.RemoveAll(e => predicate((IEffect)e));
398 		}
399 
AddFrameEndTask(Action<World> a)400 		public void AddFrameEndTask(Action<World> a) { frameEndActions.Enqueue(a); }
401 
402 		public event Action<Actor> ActorAdded = _ => { };
403 		public event Action<Actor> ActorRemoved = _ => { };
404 
405 		public bool Paused { get; internal set; }
406 		public bool PredictedPaused { get; internal set; }
407 		public bool PauseStateLocked { get; set; }
408 
409 		public int WorldTick { get; private set; }
410 
411 		Dictionary<int, MiniYaml> gameSaveTraitData = new Dictionary<int, MiniYaml>();
AddGameSaveTraitData(int traitIndex, MiniYaml yaml)412 		internal void AddGameSaveTraitData(int traitIndex, MiniYaml yaml)
413 		{
414 			gameSaveTraitData[traitIndex] = yaml;
415 		}
416 
SetPauseState(bool paused)417 		public void SetPauseState(bool paused)
418 		{
419 			if (PauseStateLocked)
420 				return;
421 
422 			IssueOrder(Order.FromTargetString("PauseGame", paused ? "Pause" : "UnPause", false));
423 			PredictedPaused = paused;
424 		}
425 
SetLocalPauseState(bool paused)426 		public void SetLocalPauseState(bool paused)
427 		{
428 			Paused = PredictedPaused = paused;
429 		}
430 
Tick()431 		public void Tick()
432 		{
433 			if (wasLoadingGameSave && !IsLoadingGameSave)
434 			{
435 				foreach (var kv in gameSaveTraitData)
436 				{
437 					var tp = TraitDict.ActorsWithTrait<IGameSaveTraitData>()
438 						.Skip(kv.Key)
439 						.FirstOrDefault();
440 
441 					if (tp.Actor == null)
442 						break;
443 
444 					tp.Trait.ResolveTraitData(tp.Actor, kv.Value.Nodes);
445 				}
446 
447 				gameSaveTraitData.Clear();
448 
449 				Game.Sound.DisableAllSounds = false;
450 				foreach (var nsr in WorldActor.TraitsImplementing<INotifyGameLoaded>())
451 					nsr.GameLoaded(this);
452 
453 				wasLoadingGameSave = false;
454 			}
455 
456 			if (!Paused)
457 			{
458 				WorldTick++;
459 
460 				using (new PerfSample("tick_actors"))
461 					foreach (var a in actors.Values)
462 						a.Tick();
463 
464 				ActorsWithTrait<ITick>().DoTimed(x => x.Trait.Tick(x.Actor), "Trait");
465 
466 				effects.DoTimed(e => e.Tick(this), "Effect");
467 			}
468 
469 			while (frameEndActions.Count != 0)
470 				frameEndActions.Dequeue()(this);
471 		}
472 
473 		// For things that want to update their render state once per tick, ignoring pause state
TickRender(WorldRenderer wr)474 		public void TickRender(WorldRenderer wr)
475 		{
476 			ActorsWithTrait<ITickRender>().DoTimed(x => x.Trait.TickRender(wr, x.Actor), "Render");
477 			ScreenMap.TickRender();
478 		}
479 
480 		public IEnumerable<Actor> Actors { get { return actors.Values; } }
481 		public IEnumerable<IEffect> Effects { get { return effects; } }
482 		public IEnumerable<IEffect> UnpartitionedEffects { get { return unpartitionedEffects; } }
483 		public IEnumerable<ISync> SyncedEffects { get { return syncedEffects; } }
484 
GetActorById(uint actorId)485 		public Actor GetActorById(uint actorId)
486 		{
487 			Actor a;
488 			if (actors.TryGetValue(actorId, out a))
489 				return a;
490 			return null;
491 		}
492 
493 		uint nextAID = 0;
NextAID()494 		internal uint NextAID()
495 		{
496 			return nextAID++;
497 		}
498 
SyncHash()499 		public int SyncHash()
500 		{
501 			// using (new PerfSample("synchash"))
502 			{
503 				var n = 0;
504 				var ret = 0;
505 
506 				// Hash all the actors.
507 				foreach (var a in Actors)
508 					ret += n++ * (int)(1 + a.ActorID) * Sync.HashActor(a);
509 
510 				// Hash fields marked with the ISync interface.
511 				foreach (var actor in ActorsHavingTrait<ISync>())
512 					foreach (var syncHash in actor.SyncHashes)
513 						ret += n++ * (int)(1 + actor.ActorID) * syncHash.Hash();
514 
515 				// Hash game state relevant effects such as projectiles.
516 				foreach (var sync in SyncedEffects)
517 					ret += n++ * Sync.Hash(sync);
518 
519 				// Hash the shared random number generator.
520 				ret += SharedRandom.Last;
521 
522 				// Hash player RenderPlayer status
523 				foreach (var p in Players)
524 					if (p.UnlockedRenderPlayer)
525 						ret += Sync.HashPlayer(p);
526 
527 				return ret;
528 			}
529 		}
530 
ActorsWithTrait()531 		public IEnumerable<TraitPair<T>> ActorsWithTrait<T>()
532 		{
533 			return TraitDict.ActorsWithTrait<T>();
534 		}
535 
ActorsHavingTrait()536 		public IEnumerable<Actor> ActorsHavingTrait<T>()
537 		{
538 			return TraitDict.ActorsHavingTrait<T>();
539 		}
540 
ActorsHavingTrait(Func<T, bool> predicate)541 		public IEnumerable<Actor> ActorsHavingTrait<T>(Func<T, bool> predicate)
542 		{
543 			return TraitDict.ActorsHavingTrait(predicate);
544 		}
545 
OnPlayerWinStateChanged(Player player)546 		public void OnPlayerWinStateChanged(Player player)
547 		{
548 			var pi = gameInfo.GetPlayer(player);
549 			if (pi != null)
550 			{
551 				pi.Outcome = player.WinState;
552 				pi.OutcomeTimestampUtc = DateTime.UtcNow;
553 			}
554 		}
555 
RequestGameSave(string filename)556 		public void RequestGameSave(string filename)
557 		{
558 			// Allow traits to save arbitrary data that will be passed back via IGameSaveTraitData.ResolveTraitData
559 			// at the end of the save restoration
560 			// TODO: This will need to be generalized to a request / response pair for multiplayer game saves
561 			var i = 0;
562 			foreach (var tp in TraitDict.ActorsWithTrait<IGameSaveTraitData>())
563 			{
564 				var data = tp.Trait.IssueTraitData(tp.Actor);
565 				if (data != null)
566 				{
567 					var yaml = new List<MiniYamlNode>() { new MiniYamlNode(i.ToString(), new MiniYaml("", data)) };
568 					IssueOrder(Order.FromTargetString("GameSaveTraitData", yaml.WriteToString(), true));
569 				}
570 
571 				i++;
572 			}
573 
574 			IssueOrder(Order.FromTargetString("CreateGameSave", filename, true));
575 		}
576 
577 		public bool Disposing;
578 
Dispose()579 		public void Dispose()
580 		{
581 			Disposing = true;
582 
583 			if (OrderGenerator != null)
584 				OrderGenerator.Deactivate();
585 
586 			frameEndActions.Clear();
587 
588 			Game.Sound.StopAudio();
589 			Game.Sound.StopVideo();
590 			if (IsLoadingGameSave)
591 				Game.Sound.DisableAllSounds = false;
592 
593 			ModelCache.Dispose();
594 
595 			// Dispose newer actors first, and the world actor last
596 			foreach (var a in actors.Values.Reverse())
597 				a.Dispose();
598 
599 			// Actor disposals are done in a FrameEndTask
600 			while (frameEndActions.Count != 0)
601 				frameEndActions.Dequeue()(this);
602 
603 			Game.FinishBenchmark();
604 		}
605 	}
606 
607 	public struct TraitPair<T> : IEquatable<TraitPair<T>>
608 	{
609 		public readonly Actor Actor;
610 		public readonly T Trait;
611 
TraitPairOpenRA.TraitPair612 		public TraitPair(Actor actor, T trait) { Actor = actor; Trait = trait; }
613 
operator ==OpenRA.TraitPair614 		public static bool operator ==(TraitPair<T> me, TraitPair<T> other) { return me.Actor == other.Actor && Equals(me.Trait, other.Trait); }
operator !=OpenRA.TraitPair615 		public static bool operator !=(TraitPair<T> me, TraitPair<T> other) { return !(me == other); }
616 
GetHashCodeOpenRA.TraitPair617 		public override int GetHashCode() { return Actor.GetHashCode() ^ Trait.GetHashCode(); }
618 
EqualsOpenRA.TraitPair619 		public bool Equals(TraitPair<T> other) { return this == other; }
EqualsOpenRA.TraitPair620 		public override bool Equals(object obj) { return obj is TraitPair<T> && Equals((TraitPair<T>)obj); }
621 
ToStringOpenRA.TraitPair622 		public override string ToString() { return Actor.Info.Name + "->" + Trait.GetType().Name; }
623 	}
624 }
625