diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index 1d3b8b551f..9c955565a2 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -87,10 +87,16 @@ namespace OpenRA public const int Timestep = 40; public const int TimestepJankThreshold = 250; // Don't catch up for delays larger than 250ms + public static event Action OnRemoteDirectConnect = (a, b) => { }; public static event Action ConnectionStateChanged = _ => { }; static ConnectionState lastConnectionState = ConnectionState.PreConnecting; public static int LocalClientId { get { return orderManager.Connection.LocalClientId; } } + public static void RemoteDirectConnect(string host, int port) + { + OnRemoteDirectConnect(host, port); + } + // Hacky workaround for orderManager visibility public static Widget OpenWindow(World world, string widget) { @@ -264,6 +270,9 @@ namespace OpenRA LobbyInfoChanged = () => { }; ConnectionStateChanged = om => { }; BeforeGameStart = () => { }; + OnRemoteDirectConnect = (a, b) => { }; + delayedActions = new ActionQueue(); + Ui.ResetAll(); if (worldRenderer != null) @@ -356,45 +365,7 @@ namespace OpenRA Environment.Exit(0); } else - { - var window = args != null ? args.GetValue("Launch.Window", null) : null; - if (!string.IsNullOrEmpty(window)) - { - var installData = modData.Manifest.ContentInstaller; - if (installData.InstallerBackgroundWidget != null) - Ui.LoadWidget(installData.InstallerBackgroundWidget, Ui.Root, new WidgetArgs()); - - Widgets.Ui.OpenWindow(window, new WidgetArgs()); - } - else - { - modData.LoadScreen.StartGame(); - Settings.Save(); - var replay = args != null ? args.GetValue("Launch.Replay", null) : null; - if (!string.IsNullOrEmpty(replay)) - Game.JoinReplay(replay); - } - } - } - - public static void TestAndContinue() - { - Ui.ResetAll(); - var installData = modData.Manifest.ContentInstaller; - if (!installData.TestFiles.All(f => GlobalFileSystem.Exists(f))) - { - var args = new WidgetArgs() - { - { "continueLoading", () => InitializeMod(Game.Settings.Game.Mod, null) }, - }; - - if (installData.InstallerBackgroundWidget != null) - Ui.LoadWidget(installData.InstallerBackgroundWidget, Ui.Root, args); - - Ui.OpenWindow(installData.InstallerMenuWidget, args); - } - else - LoadShellMap(); + modData.LoadScreen.StartGame(args); } public static void LoadShellMap() diff --git a/OpenRA.Game/InstallUtils.cs b/OpenRA.Game/InstallUtils.cs index b3a2449f7f..95f89b0127 100644 --- a/OpenRA.Game/InstallUtils.cs +++ b/OpenRA.Game/InstallUtils.cs @@ -20,8 +20,9 @@ namespace OpenRA { public class InstallData { - public readonly string InstallerMenuWidget = null; - public readonly string InstallerBackgroundWidget = null; + public readonly string MenuWidget = null; + public readonly string MusicMenuWidget = null; + public readonly string BackgroundWidget = null; public readonly string[] TestFiles = {}; public readonly string[] DiskTestFiles = {}; public readonly string PackageToExtractFromCD = null; diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index d2e114f517..55ab4b1e12 100644 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -180,6 +180,6 @@ namespace OpenRA { void Init(Manifest m, Dictionary info); void Display(); - void StartGame(); + void StartGame(Arguments args); } } diff --git a/OpenRA.Game/Network/OrderManager.cs b/OpenRA.Game/Network/OrderManager.cs index 56f221af6e..b4dd17b7f1 100644 --- a/OpenRA.Game/Network/OrderManager.cs +++ b/OpenRA.Game/Network/OrderManager.cs @@ -51,6 +51,8 @@ namespace OpenRA.Network public readonly ReadOnlyList ChatCache; + bool disposed; + static void OutOfSync(int frame) { throw new InvalidOperationException("Out of sync in frame {0}.\n Compare syncreport.log with other players.".F(frame)); @@ -122,8 +124,16 @@ namespace OpenRA.Network }); foreach (var p in immediatePackets) + { foreach (var o in p.Second.ToOrderList(World)) + { UnitOrders.ProcessOrder(this, World, p.First, o); + + // A mod switch or other event has pulled the ground from beneath us + if (disposed) + return; + } + } } Dictionary syncForFrame = new Dictionary(); @@ -213,6 +223,7 @@ namespace OpenRA.Network public void Dispose() { + disposed = true; if (Connection != null) Connection.Dispose(); } diff --git a/OpenRA.Game/Network/ReplayConnection.cs b/OpenRA.Game/Network/ReplayConnection.cs index 32afbfad6a..5924ff7b6b 100755 --- a/OpenRA.Game/Network/ReplayConnection.cs +++ b/OpenRA.Game/Network/ReplayConnection.cs @@ -33,9 +33,12 @@ namespace OpenRA.Network public readonly int TickCount; public readonly bool IsValid; public readonly Session LobbyInfo; + public readonly string Filename; public ReplayConnection(string replayFilename) { + Filename = replayFilename; + // Parse replay data into a struct that can be fed to the game in chunks // to avoid issues with all immediate orders being resolved on the first tick. using (var rs = File.OpenRead(replayFilename)) diff --git a/OpenRA.Game/Network/UnitOrders.cs b/OpenRA.Game/Network/UnitOrders.cs index a7ff1e32a3..4a1d1d2818 100644 --- a/OpenRA.Game/Network/UnitOrders.cs +++ b/OpenRA.Game/Network/UnitOrders.cs @@ -119,10 +119,27 @@ namespace OpenRA.Network case "HandshakeRequest": { - // TODO: Switch to the server's mod if we have it - // Otherwise send the handshake with our current settings and let the server reject us + // Switch to the server's mod if we need and are able to var mod = Game.modData.Manifest.Mod; + var request = HandshakeRequest.Deserialize(order.TargetString); + ModMetadata serverMod; + if (request.Mod != mod.Id && + ModMetadata.AllMods.TryGetValue(request.Mod, out serverMod) && + serverMod.Version == request.Version) + { + var replay = orderManager.Connection as ReplayConnection; + var launchCommand = replay != null ? + "Launch.Replay=" + replay.Filename : + "Launch.Connect=" + orderManager.Host + ":" + orderManager.Port; + + Game.modData.LoadScreen.Display(); + Game.InitializeMod(request.Mod, new Arguments(launchCommand)); + + break; + } + + // Otherwise send the handshake with our current settings and let the server reject us var info = new Session.Client() { Name = Game.Settings.Player.Name, diff --git a/OpenRA.Mods.Cnc/CncLoadScreen.cs b/OpenRA.Mods.Cnc/CncLoadScreen.cs index 1f3d424737..d81ba334c9 100644 --- a/OpenRA.Mods.Cnc/CncLoadScreen.cs +++ b/OpenRA.Mods.Cnc/CncLoadScreen.cs @@ -12,12 +12,15 @@ using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using OpenRA.Graphics; +using OpenRA.Mods.Common.LoadScreens; using OpenRA.Widgets; namespace OpenRA.Mods.Cnc { - public sealed class CncLoadScreen : ILoadScreen + public sealed class CncLoadScreen : BlankLoadScreen { + readonly NullInputHandler nih = new NullInputHandler(); + Dictionary loadInfo; Stopwatch loadTimer = Stopwatch.StartNew(); Sheet sheet; @@ -27,9 +30,8 @@ namespace OpenRA.Mods.Cnc Sprite nodLogo, gdiLogo, evaLogo, brightBlock, dimBlock; Rectangle bounds; Renderer r; - readonly NullInputHandler nih = new NullInputHandler(); - public void Init(Manifest m, Dictionary info) + public override void Init(Manifest m, Dictionary info) { loadInfo = info; @@ -72,7 +74,7 @@ namespace OpenRA.Mods.Cnc string loadingText, versionText; float2 loadingPos, versionPos; - public void Display() + public override void Display() { if (r == null || loadTimer.Elapsed.TotalSeconds < 0.25) return; @@ -118,12 +120,7 @@ namespace OpenRA.Mods.Cnc r.EndFrame(nih); } - public void StartGame() - { - Game.TestAndContinue(); - } - - public void Dispose() + public override void Dispose() { if (sheet != null) sheet.Dispose(); diff --git a/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs b/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs new file mode 100644 index 0000000000..a8fcaa3e1a --- /dev/null +++ b/OpenRA.Mods.Common/LoadScreens/BlankLoadScreen.cs @@ -0,0 +1,91 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 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.Collections.Generic; +using System.Linq; +using OpenRA.FileSystem; +using OpenRA.Widgets; +using OpenRA.Mods.Common.Widgets.Logic; + +namespace OpenRA.Mods.Common.LoadScreens +{ + public class BlankLoadScreen : ILoadScreen + { + public virtual void Init(Manifest m, Dictionary info) { } + + public virtual void Display() + { + if (Game.Renderer == null) + return; + + // Draw a black screen + Game.Renderer.BeginFrame(int2.Zero, 1f); + Game.Renderer.EndFrame(new NullInputHandler()); + } + + public void StartGame(Arguments args) + { + Ui.ResetAll(); + Game.Settings.Save(); + + // Check whether the mod content is installed + // TODO: The installation code has finally been beaten into shape, so we can + // finally move it all into the planned "Manage Content" panel in the modchooser mod. + var installData = Game.modData.Manifest.ContentInstaller; + var installModContent = !installData.TestFiles.All(f => GlobalFileSystem.Exists(f)); + var installModMusic = args != null && args.Contains("Install.Music"); + + if (installModContent || installModMusic) + { + var widgetArgs = new WidgetArgs() + { + { "continueLoading", () => Game.InitializeMod(Game.Settings.Game.Mod, args) }, + }; + + if (installData.BackgroundWidget != null) + Ui.LoadWidget(installData.BackgroundWidget, Ui.Root, widgetArgs); + + var menu = installModContent ? installData.MenuWidget : installData.MusicMenuWidget; + Ui.OpenWindow(menu, widgetArgs); + + return; + } + + // Join a server directly + var connect = args != null ? args.GetValue("Launch.Connect", null) : null; + if (!string.IsNullOrEmpty(connect)) + { + var parts = connect.Split(':'); + + if (parts.Length == 2) + { + var host = parts[0]; + var port = Exts.ParseIntegerInvariant(parts[1]); + Game.LoadShellMap(); + Game.RemoteDirectConnect(host, port); + return; + } + } + + // Load a replay directly + var replay = args != null ? args.GetValue("Launch.Replay", null) : null; + if (!string.IsNullOrEmpty(replay)) + { + Game.JoinReplay(replay); + return; + } + + Game.LoadShellMap(); + Game.Settings.Save(); + } + + public virtual void Dispose() { } + } +} \ No newline at end of file diff --git a/OpenRA.Mods.Common/LoadScreens/DefaultLoadScreen.cs b/OpenRA.Mods.Common/LoadScreens/LogoStripeLoadScreen.cs similarity index 89% rename from OpenRA.Mods.Common/LoadScreens/DefaultLoadScreen.cs rename to OpenRA.Mods.Common/LoadScreens/LogoStripeLoadScreen.cs index c04fa6738d..ab18d9b026 100644 --- a/OpenRA.Mods.Common/LoadScreens/DefaultLoadScreen.cs +++ b/OpenRA.Mods.Common/LoadScreens/LogoStripeLoadScreen.cs @@ -16,7 +16,7 @@ using OpenRA.Widgets; namespace OpenRA.Mods.Common.LoadScreens { - public sealed class DefaultLoadScreen : ILoadScreen + public sealed class LogoStripeLoadScreen : BlankLoadScreen { Stopwatch lastUpdate = Stopwatch.StartNew(); Renderer r; @@ -27,7 +27,7 @@ namespace OpenRA.Mods.Common.LoadScreens Sprite stripe, logo; string[] messages; - public void Init(Manifest m, Dictionary info) + public override void Init(Manifest m, Dictionary info) { // Avoid standard loading mechanisms so we // can display the loadscreen as early as possible @@ -43,7 +43,7 @@ namespace OpenRA.Mods.Common.LoadScreens logoPos = new float2(r.Resolution.Width / 2 - 128, r.Resolution.Height / 2 - 128); } - public void Display() + public override void Display() { if (r == null) return; @@ -66,12 +66,7 @@ namespace OpenRA.Mods.Common.LoadScreens r.EndFrame(new NullInputHandler()); } - public void StartGame() - { - Game.TestAndContinue(); - } - - public void Dispose() + public override void Dispose() { if (sheet != null) sheet.Dispose(); diff --git a/OpenRA.Mods.Common/LoadScreens/ModChooserLoadScreen.cs b/OpenRA.Mods.Common/LoadScreens/ModChooserLoadScreen.cs index ccc18e204e..bfea1e2aee 100644 --- a/OpenRA.Mods.Common/LoadScreens/ModChooserLoadScreen.cs +++ b/OpenRA.Mods.Common/LoadScreens/ModChooserLoadScreen.cs @@ -39,7 +39,7 @@ namespace OpenRA.Mods.Common.LoadScreens r.EndFrame(new NullInputHandler()); } - public void StartGame() + public void StartGame(Arguments args) { Ui.LoadWidget("MODCHOOSER", Ui.Root, new WidgetArgs()); } diff --git a/OpenRA.Mods.Common/LoadScreens/NullLoadScreen.cs b/OpenRA.Mods.Common/LoadScreens/NullLoadScreen.cs deleted file mode 100644 index ce0c7b149c..0000000000 --- a/OpenRA.Mods.Common/LoadScreens/NullLoadScreen.cs +++ /dev/null @@ -1,39 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2014 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.Collections.Generic; -using OpenRA.Widgets; - -namespace OpenRA.Mods.Common.LoadScreens -{ - public sealed class NullLoadScreen : ILoadScreen - { - public void Init(Manifest m, Dictionary info) { } - - public void Display() - { - if (Game.Renderer == null) - return; - - // Draw a black screen - Game.Renderer.BeginFrame(int2.Zero, 1f); - Game.Renderer.EndFrame(new NullInputHandler()); - } - - public void StartGame() - { - Ui.ResetAll(); - } - - public void Dispose() - { - } - } -} diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index b0486fade2..9526687c40 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -80,9 +80,7 @@ - - @@ -243,6 +241,8 @@ + + diff --git a/OpenRA.Mods.RA/Widgets/Logic/MainMenuLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/MainMenuLogic.cs index 6624f049b8..bfc19c4a26 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/MainMenuLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/MainMenuLogic.cs @@ -48,7 +48,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic Ui.OpenWindow("SERVERBROWSER_PANEL", new WidgetArgs { { "onStart", RemoveShellmapUI }, - { "onExit", () => menuType = MenuType.Main } + { "onExit", () => menuType = MenuType.Main }, + { "directConnectHost", null }, + { "directConnectPort", 0 }, }); }; @@ -173,6 +175,18 @@ namespace OpenRA.Mods.RA.Widgets.Logic newsButton.IsHighlighted = () => newsHighlighted && Game.LocalTick % 50 < 25; } + + Game.OnRemoteDirectConnect += (host, port) => + { + menuType = MenuType.None; + Ui.OpenWindow("SERVERBROWSER_PANEL", new WidgetArgs + { + { "onStart", RemoveShellmapUI }, + { "onExit", () => menuType = MenuType.Main }, + { "directConnectHost", host }, + { "directConnectPort", port }, + }); + }; } void SetNewsStatus(string message) diff --git a/OpenRA.Mods.RA/Widgets/Logic/MusicPlayerLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/MusicPlayerLogic.cs index c3d1252729..692b1ede52 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/MusicPlayerLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/MusicPlayerLogic.cs @@ -85,7 +85,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (installButton != null) { installButton.IsDisabled = () => world == null || !world.IsShellmap; - var args = new string[] { "Launch.Window=INSTALL_MUSIC_PANEL" }; + var args = new string[] { "Install.Music=true" }; installButton.OnClick = () => { Game.modData.LoadScreen.Display(); // HACK: prevent a flicker when transitioning to the installation dialog diff --git a/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs index 114f823c0e..96bf17574f 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs @@ -51,7 +51,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic } [ObjectCreator.UseCtor] - public ServerBrowserLogic(Widget widget, Action onStart, Action onExit) + public ServerBrowserLogic(Widget widget, Action onStart, Action onExit, string directConnectHost, int directConnectPort) { panel = widget; this.onStart = onStart; @@ -116,6 +116,18 @@ namespace OpenRA.Mods.RA.Widgets.Logic } RefreshServerList(); + + if (directConnectHost != null) + { + // The connection window must be opened at the end of the tick for the widget hierarchy to + // work out, but we also want to prevent the server browser from flashing visible for one tick. + widget.Visible = false; + Game.RunAfterTick(() => + { + ConnectionLogic.Connect(directConnectHost, directConnectPort, "", OpenLobby, DoNothing); + widget.Visible = true; + }); + } } void RefreshServerList() diff --git a/mods/cnc/mod.yaml b/mods/cnc/mod.yaml index 7cdb8baf1d..d9f10c2634 100644 --- a/mods/cnc/mod.yaml +++ b/mods/cnc/mod.yaml @@ -138,8 +138,9 @@ LoadScreen: CncLoadScreen ContentInstaller: TestFiles: conquer.mix, desert.mix, sounds.mix, speech.mix, temperat.mix, tempicnh.mix, winter.mix - InstallerBackgroundWidget: INSTALL_BACKGROUND - InstallerMenuWidget: INSTALL_PANEL + BackgroundWidget: INSTALL_BACKGROUND + MenuWidget: INSTALL_PANEL + MusicMenuWidget: INSTALL_MUSIC_PANEL FilesToCopy: CONQUER.MIX, DESERT.MIX, SCORES.MIX, SOUNDS.MIX, TEMPERAT.MIX, WINTER.MIX FilesToExtract: speech.mix, tempicnh.mix, transit.mix PackageMirrorList: http://www.openra.net/packages/cnc-mirrors.txt diff --git a/mods/d2k/mod.yaml b/mods/d2k/mod.yaml index 6d3018f755..fa729a95e3 100644 --- a/mods/d2k/mod.yaml +++ b/mods/d2k/mod.yaml @@ -117,12 +117,13 @@ Movies: Translations: ./mods/d2k/languages/english.yaml -LoadScreen: DefaultLoadScreen +LoadScreen: LogoStripeLoadScreen Image: ./mods/d2k/uibits/loadscreen.png Text: Filling Crates..., Breeding Sandworms... ContentInstaller: - InstallerMenuWidget: INSTALL_PANEL + MenuWidget: INSTALL_PANEL + MusicMenuWidget: INSTALL_MUSIC_PANEL # TODO: check if DATA.R8 is at 1.03 patch level with 4840 frames TestFiles: BLOXBASE.R8, BLOXBAT.R8, BLOXBGBS.R8, BLOXICE.R8, BLOXTREE.R8, BLOXWAST.R8, DATA.R8, SOUND.RS PackageMirrorList: http://www.openra.net/packages/d2k-103-mirrors.txt diff --git a/mods/ra/mod.yaml b/mods/ra/mod.yaml index 57294f8cc5..7a888b3089 100644 --- a/mods/ra/mod.yaml +++ b/mods/ra/mod.yaml @@ -132,12 +132,13 @@ Movies: Translations: ./mods/ra/languages/english.yaml -LoadScreen: DefaultLoadScreen +LoadScreen: LogoStripeLoadScreen Image: ./mods/ra/uibits/loadscreen.png Text: Filling Crates..., Charging Capacitors..., Reticulating Splines..., Planting Trees..., Building Bridges..., Aging Empires..., Compiling EVA..., Constructing Pylons..., Activating Skynet..., Splitting Atoms... ContentInstaller: - InstallerMenuWidget: INSTALL_PANEL + MenuWidget: INSTALL_PANEL + MusicMenuWidget: INSTALL_MUSIC_PANEL TestFiles: allies.mix, conquer.mix, interior.mix, redalert.mix, russian.mix, snow.mix, sounds.mix, temperat.mix PackageMirrorList: http://www.openra.net/packages/ra-mirrors.txt DiskTestFiles: MAIN.MIX, INSTALL/REDALERT.MIX diff --git a/mods/ts/mod.yaml b/mods/ts/mod.yaml index b0d26fefb1..78f54919b5 100644 --- a/mods/ts/mod.yaml +++ b/mods/ts/mod.yaml @@ -162,12 +162,13 @@ Movies: Translations: ./mods/ts/languages/english.yaml -LoadScreen: DefaultLoadScreen +LoadScreen: LogoStripeLoadScreen Image: ./mods/ts/uibits/loadscreen.png Text: Updating EVA installation..., Changing perspective... ContentInstaller: - InstallerMenuWidget: INSTALL_PANEL + MenuWidget: INSTALL_PANEL + MusicMenuWidget: INSTALL_MUSIC_PANEL TestFiles: cache.mix, conquer.mix, isosnow.mix, isotemp.mix, local.mix, sidec01.mix, sidec02.mix, sno.mix, snow.mix, sounds.mix, speech01.mix, tem.mix, temperat.mix PackageMirrorList: http://www.openra.net/packages/ts-mirrors.txt DiskTestFiles: MULTI.MIX, INSTALL/TIBSUN.MIX