Files
OpenRA/OpenRA.Game/Game.cs

411 lines
12 KiB
C#
Executable File

#region Copyright & License Information
/*
* Copyright 2007-2011 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 COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using OpenRA.FileFormats;
using OpenRA.GameRules;
using OpenRA.Graphics;
using OpenRA.Network;
using OpenRA.Support;
using OpenRA.Widgets;
using XRandom = OpenRA.Thirdparty.Random;
namespace OpenRA
{
public static class Game
{
public static int CellSize { get { return modData.Manifest.TileSize; } }
public static ModData modData;
static WorldRenderer worldRenderer;
public static Viewport viewport;
public static Settings Settings;
internal static OrderManager orderManager;
static Server.Server server;
public static XRandom CosmeticRandom = new XRandom(); // not synced
public static Renderer Renderer;
public static bool HasInputFocus = false;
public static void MoveViewport(float2 loc)
{
viewport.Center(loc);
}
public static void JoinServer(string host, int port)
{
var replayFilename = ChooseReplayFilename();
string path = Path.Combine( Platform.SupportDir, "Replays" );
if( !Directory.Exists( path ) ) Directory.CreateDirectory( path );
var replayFile = File.Create( Path.Combine( path, replayFilename ) );
JoinInner(new OrderManager(host, port,
new ReplayRecorderConnection(new NetworkConnection(host, port), replayFile)));
}
static string ChooseReplayFilename()
{
return DateTime.UtcNow.ToString("OpenRA-yyyy-MM-ddTHHmmssZ.rep");
}
static void JoinInner(OrderManager om)
{
if (orderManager != null) orderManager.Dispose();
orderManager = om;
lastConnectionState = ConnectionState.PreConnecting;
ConnectionStateChanged(orderManager);
}
public static void JoinReplay(string replayFile)
{
JoinInner(new OrderManager("<no server>", -1, new ReplayConnection(replayFile)));
}
static void JoinLocal()
{
JoinInner(new OrderManager("<no server>", -1, new EchoConnection()));
}
public static int RenderFrame = 0;
public static int NetFrameNumber { get { return orderManager.NetFrameNumber; } }
public static int LocalTick { get { return orderManager.LocalFrameNumber; } }
const int NetTickScale = 3; // 120ms net tick for 40ms local tick
public static event Action<OrderManager> ConnectionStateChanged = _ => { };
static ConnectionState lastConnectionState = ConnectionState.PreConnecting;
public static int LocalClientId { get { return orderManager.Connection.LocalClientId; } }
// Hacky workaround for orderManager visibility
public static Widget OpenWindow(World world, string widget)
{
return Widget.OpenWindow(widget, new WidgetArgs() {{ "world", world }, { "orderManager", orderManager }, { "worldRenderer", worldRenderer }});
}
// Who came up with the great idea of making these things
// impossible for the things that want them to access them directly?
public static Widget OpenWindow(string widget, WidgetArgs args)
{
return Widget.OpenWindow(widget, new WidgetArgs(args)
{
{ "world", worldRenderer.world },
{ "orderManager", orderManager },
{ "worldRenderer", worldRenderer },
});
}
// Load a widget with world, orderManager, worldRenderer args, without adding it to the widget tree
public static Widget LoadWidget(World world, string id, Widget parent, WidgetArgs args)
{
return Game.modData.WidgetLoader.LoadWidget(new WidgetArgs(args)
{
{ "world", world },
{ "orderManager", orderManager },
{ "worldRenderer", worldRenderer },
}, parent, id);
}
static ActionQueue delayedActions = new ActionQueue();
public static void RunAfterTick(Action a) { delayedActions.Add(a); }
public static void RunAfterDelay(int delay, Action a) { delayedActions.Add(a, delay); }
static void Tick( OrderManager orderManager, Viewport viewPort )
{
if (orderManager.Connection.ConnectionState != lastConnectionState)
{
lastConnectionState = orderManager.Connection.ConnectionState;
ConnectionStateChanged( orderManager );
}
Tick( orderManager );
if( worldRenderer != null && orderManager.world != worldRenderer.world )
Tick( worldRenderer.world.orderManager );
using (new PerfSample("render"))
{
++RenderFrame;
viewport.DrawRegions(worldRenderer, new DefaultInputHandler( orderManager.world ));
Sound.SetListenerPosition(viewport.CenterLocation);
}
PerfHistory.items["render"].Tick();
PerfHistory.items["batches"].Tick();
PerfHistory.items["render_widgets"].Tick();
PerfHistory.items["render_flip"].Tick();
delayedActions.PerformActions();
}
static void Tick( OrderManager orderManager )
{
int t = Environment.TickCount;
int dt = t - orderManager.LastTickTime;
if (dt >= Settings.Game.Timestep)
using( new PerfSample( "tick_time" ) )
{
orderManager.LastTickTime += Settings.Game.Timestep;
Widget.DoTick();
var world = orderManager.world;
if (orderManager.GameStarted)
++Viewport.TicksSinceLastMove;
Sound.Tick();
Sync.CheckSyncUnchanged( world, () => { orderManager.TickImmediate(); } );
if (world != null)
{
var isNetTick = LocalTick % NetTickScale == 0;
if (!isNetTick || orderManager.IsReadyForNextFrame)
{
++orderManager.LocalFrameNumber;
Log.Write("debug", "--Tick: {0} ({1})", LocalTick, isNetTick ? "net" : "local");
if (isNetTick) orderManager.Tick();
Sync.CheckSyncUnchanged(world, () =>
{
world.OrderGenerator.Tick(world);
world.Selection.Tick(world);
});
world.Tick();
PerfHistory.Tick();
}
else
if (orderManager.NetFrameNumber == 0)
orderManager.LastTickTime = Environment.TickCount;
}
}
}
public static event Action LobbyInfoChanged = () => { };
internal static void SyncLobbyInfo()
{
LobbyInfoChanged();
}
public static event Action BeforeGameStart = () => {};
internal static void StartGame(string mapUID, bool isShellmap)
{
BeforeGameStart();
var map = modData.PrepareMap(mapUID);
viewport = new Viewport(new int2(Renderer.Resolution), map.Bounds, Renderer);
orderManager.world = new World(modData.Manifest, map, orderManager) { IsShellmap = isShellmap };
worldRenderer = new WorldRenderer(orderManager.world);
if (orderManager.GameStarted) return;
Widget.SelectedWidget = null;
orderManager.LocalFrameNumber = 0;
orderManager.LastTickTime = Environment.TickCount;
orderManager.StartGame();
worldRenderer.RefreshPalette();
}
public static bool IsHost
{
get { return orderManager.Connection.LocalClientId == 0; }
}
public static Dictionary<String, Mod> CurrentMods
{
get { return Mod.AllMods.Where( k => modData.Manifest.Mods.Contains( k.Key )).ToDictionary( k => k.Key, k => k.Value ); }
}
static Modifiers modifiers;
public static Modifiers GetModifierKeys() { return modifiers; }
internal static void HandleModifierKeys(Modifiers mods) { modifiers = mods; }
internal static void Initialize(Arguments args)
{
Console.WriteLine("Platform is {0}", Platform.CurrentPlatform);
AppDomain.CurrentDomain.AssemblyResolve += FileSystem.ResolveAssembly;
Settings = new Settings(Platform.SupportDir + "settings.yaml", args);
Settings.Save();
Log.LogPath = Platform.SupportDir + "Logs" + Path.DirectorySeparatorChar;
Log.AddChannel("perf", "perf.log");
Log.AddChannel("debug", "debug.log");
Log.AddChannel("sync", "syncreport.log");
FileSystem.Mount("."); // Needed to access shaders
Renderer.Initialize( Game.Settings.Graphics.Mode );
Renderer = new Renderer();
Console.WriteLine("Available mods:");
foreach(var mod in Mod.AllMods)
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Title, mod.Value.Version);
Sound.Create();
InitializeWithMods(Settings.Game.Mods);
}
public static void InitializeWithMods(string[] mods)
{
// Clear static state if we have switched mods
LobbyInfoChanged = () => {};
AddChatLine = (a,b,c) => {};
ConnectionStateChanged = om => {};
BeforeGameStart = () => {};
Widget.ResetAll();
worldRenderer = null;
if (server != null)
server.Shutdown();
if (orderManager != null)
orderManager.Dispose();
// Discard any invalid mods
var mm = mods.Where( m => Mod.AllMods.ContainsKey( m ) ).ToArray();
Console.WriteLine("Loading mods: {0}", mm.JoinWith(","));
Settings.Game.Mods = mm;
Settings.Save();
Sound.StopMusic();
Sound.StopVideo();
Sound.Initialize();
modData = new ModData( mm );
Renderer.InitializeFonts(modData.Manifest);
modData.LoadInitialAssets();
PerfHistory.items["render"].hasNormalTick = false;
PerfHistory.items["batches"].hasNormalTick = false;
PerfHistory.items["render_widgets"].hasNormalTick = false;
PerfHistory.items["render_flip"].hasNormalTick = false;
JoinLocal();
viewport = new Viewport(new int2(Renderer.Resolution), Rectangle.Empty, Renderer);
modData.LoadScreen.StartGame();
}
public static void LoadShellMap()
{
StartGame(ChooseShellmap(), true);
}
static string ChooseShellmap()
{
var shellmaps = modData.AvailableMaps
.Where(m => m.Value.UseAsShellmap);
if (shellmaps.Count() == 0)
throw new InvalidDataException("No valid shellmaps available");
return shellmaps.Random(CosmeticRandom).Key;
}
static bool quit;
public static event Action OnQuit = () => {};
internal static void Run()
{
while (!quit)
Tick( orderManager, viewport );
OnQuit();
}
public static void Exit() { quit = true; }
public static Action<Color,string,string> AddChatLine = (c,n,s) => {};
public static void Debug(string s, params object[] args)
{
AddChatLine(Color.White, "Debug", String.Format(s,args));
}
public static void Disconnect()
{
if (orderManager.world != null)
orderManager.world.traitDict.PrintReport();
orderManager.Dispose();
CloseServer();
JoinLocal();
}
public static void CloseServer()
{
if (server != null)
server.Shutdown();
}
public static T CreateObject<T>( string name )
{
return modData.ObjectCreator.CreateObject<T>( name );
}
public static void CreateServer(ServerSettings settings)
{
server = new Server.Server(new IPEndPoint(IPAddress.Any, settings.ListenPort),
Game.Settings.Game.Mods, settings, modData);
}
public static int CreateLocalServer(string map)
{
var settings = new ServerSettings()
{
Name = "Skirmish Game",
Map = map
};
// Work around a miscompile in mono 2.6.7:
// booleans that default to true cannot be set false by an initializer
settings.AdvertiseOnline = false;
server = new Server.Server(new IPEndPoint(IPAddress.Loopback, 0),
Game.Settings.Game.Mods, settings, modData);
return server.Port;
}
public static bool IsCurrentWorld(World world)
{
return orderManager != null && orderManager.world == world;
}
public static void JoinExternalGame()
{
var addressParts = Game.Settings.Game.ConnectTo.Split(
new [] { ':' }, StringSplitOptions.RemoveEmptyEntries);
if (addressParts.Length < 1 || addressParts.Length > 2)
return;
var host = addressParts[0];
var port = Exts.WithDefault(1234, () => int.Parse(addressParts[1]));
Game.Settings.Game.ConnectTo = "";
Game.Settings.Save();
Game.JoinServer(host, port);
}
}
}