#region Copyright & License Information /* * Copyright 2007-2010 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. For more information, * see LICENSE. */ #endregion using System; using System.Collections.Generic; using OpenRA.Collections; using OpenRA.Effects; using OpenRA.FileFormats; using OpenRA.Network; using OpenRA.Orders; using OpenRA.Traits; using OpenRA.Widgets; using XRandom = OpenRA.Thirdparty.Random; namespace OpenRA { public class World { internal TraitDictionary traitDict = new TraitDictionary(); Set actors = new Set(); List effects = new List(); Queue> frameEndActions = new Queue>(); public int FrameNumber { get { return orderManager.LocalFrameNumber; } } internal readonly OrderManager orderManager; public Session LobbyInfo { get { return orderManager.LobbyInfo; } } public XRandom SharedRandom; public readonly Dictionary players = new Dictionary(); public void AddPlayer(Player p) { players[p.Index] = p; } int localPlayerIndex = -999; public Player LocalPlayer { get { return players.ContainsKey(localPlayerIndex) ? players[localPlayerIndex] : null; } } public readonly Shroud LocalShroud; public void SetLocalPlayer(int index) { if (!(orderManager.Connection is ReplayConnection)) localPlayerIndex = index; } public readonly Actor WorldActor; public readonly Map Map; public readonly TileSet TileSet; public void IssueOrder( Order o ) { orderManager.IssueOrder( o ); } /* avoid exposing the OM to mod code */ IOrderGenerator orderGenerator_; public IOrderGenerator OrderGenerator { get { return orderGenerator_; } set { Sync.AssertUnsynced( "The current order generator may not be changed from synced code" ); orderGenerator_ = value; } } public Selection Selection = new Selection(); public void CancelInputMode() { OrderGenerator = new UnitOrderGenerator(); } public bool ToggleInputMode() where T : IOrderGenerator, new() { if (OrderGenerator is T) { CancelInputMode(); return false; } else { OrderGenerator = new T(); return true; } } internal World(Manifest manifest, Map map, OrderManager orderManager) { this.orderManager = orderManager; orderGenerator_ = new UnitOrderGenerator(); Map = map; TileSet = Rules.TileSets[Map.Tileset]; TileSet.LoadTiles(); SharedRandom = new XRandom(orderManager.LobbyInfo.GlobalSettings.RandomSeed); WorldActor = CreateActor( "World", new TypeDictionary() ); LocalShroud = WorldActor.Trait(); Queries = new AllQueries(this); // Add players foreach (var cmp in WorldActor.TraitsImplementing()) cmp.CreatePlayers(this); // Set defaults for any unset stances foreach (var p in players.Values) foreach (var q in players.Values) if (!p.Stances.ContainsKey(q)) p.Stances[q] = Stance.Neutral; Sound.SoundVolumeModifier = 1.0f; foreach (var wlh in WorldActor.TraitsImplementing()) wlh.WorldLoaded(this); } // Hacky workaround for orderManager visibility public Widget OpenWindow(string widget) { return Widget.OpenWindow(widget, new Dictionary{{"world", this}, { "orderManager", orderManager }}); } public Actor CreateActor( string name, TypeDictionary initDict ) { return CreateActor( true, name, initDict ); } public Actor CreateActor( bool addToWorld, string name, TypeDictionary initDict ) { var a = new Actor( this, name, initDict ); if( addToWorld ) Add( a ); return a; } public void Add(Actor a) { a.IsInWorld = true; actors.Add(a); ActorAdded(a); } public void Remove(Actor a) { a.IsInWorld = false; actors.Remove(a); ActorRemoved(a); } public void Add(IEffect b) { effects.Add(b); } public void Remove(IEffect b) { effects.Remove(b); } public void AddFrameEndTask( Action a ) { frameEndActions.Enqueue( a ); } public event Action ActorAdded = _ => { }; public event Action ActorRemoved = _ => { }; // Will do bad things in multiplayer games public bool DisableTick = false; public void Tick() { // Todo: Expose this as an order so it can be synced if (!DisableTick) { actors.Do( x => x.Tick() ); Queries.WithTrait().DoTimed( x => { x.Trait.Tick( x.Actor ); }, "[{2}] Trait: {0} ({1:0.000} ms)", Game.Settings.Debug.LongTickThreshold ); effects.DoTimed( e => e.Tick( this ), "[{2}] Effect: {0} ({1:0.000} ms)", Game.Settings.Debug.LongTickThreshold ); } while (frameEndActions.Count != 0) frameEndActions.Dequeue()(this); Game.viewport.Tick(); } public IEnumerable Actors { get { return actors; } } public IEnumerable Effects { get { return effects; } } uint nextAID = 0; internal uint NextAID() { return nextAID++; } public int SyncHash() { //using (new PerfSample("synchash")) { int n = 0; int ret = 0; // hash all the actors foreach (var a in Actors) ret += n++ * (int)(1+a.ActorID) * Sync.CalculateSyncHash(a); // hash all the traits that tick foreach (var x in traitDict.ActorsWithTraitMultiple(this)) ret += n++*(int) (1+x.Actor.ActorID)*Sync.CalculateSyncHash(x.Trait); // Hash the shared rng ret += SharedRandom.Last; return ret; } } public class AllQueries { readonly World world; public readonly Cache OwnedBy; public AllQueries( World world ) { this.world = world; OwnedBy = new Cache(p => new OwnedByCachedView(world, world.actors, x => x.Owner == p)); } public IEnumerable> WithTrait() { return world.traitDict.ActorsWithTraitMultiple( world ); } static CachedView> WithTraitInner( Set set, TypeDictionary hasTrait ) { var ret = hasTrait.GetOrDefault>>(); if( ret != null ) return ret; ret = new CachedView>( set, x => x.HasTrait(), x => new TraitPair { Actor = x, Trait = x.Trait() } ); hasTrait.Add( ret ); return ret; } public class OwnedByCachedView : CachedView { readonly TypeDictionary hasTrait = new TypeDictionary(); public OwnedByCachedView( World world, Set set, Func include ) : base( set, include, a => a ) { } public CachedView> WithTrait() { return WithTraitInner( this, hasTrait ); } } } public AllQueries Queries; } public struct TraitPair { public Actor Actor; public T Trait; public override string ToString() { return "{0}->{1}".F( Actor.Info.Name, Trait.GetType().Name ); } } }