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