From 199f80c8ed8b3e3372110d123c0d8a9c74e9fd24 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 13 Dec 2014 10:34:54 +1300 Subject: [PATCH 1/4] Cache GameServer joinable/compatible state across render frames. --- OpenRA.Game/Network/GameServer.cs | 50 +++++++------------ .../Widgets/Logic/ServerBrowserLogic.cs | 27 +++------- 2 files changed, 27 insertions(+), 50 deletions(-) diff --git a/OpenRA.Game/Network/GameServer.cs b/OpenRA.Game/Network/GameServer.cs index 4e6d565ea7..d5b2cb42ec 100644 --- a/OpenRA.Game/Network/GameServer.cs +++ b/OpenRA.Game/Network/GameServer.cs @@ -26,42 +26,30 @@ namespace OpenRA.Network public readonly bool Protected = false; public readonly string Started = null; - public bool CanJoin() + public readonly bool IsCompatible = false; + public readonly bool IsJoinable = false; + + public readonly string ModLabel = ""; + public readonly string ModVersion = ""; + + public GameServer(MiniYaml yaml) { - // "waiting for players" - if (State != 1) - return false; + FieldLoader.Load(this, yaml); - if (!CompatibleVersion()) - return false; - - // Don't have the map locally - // TODO: We allow joining, then drop on game start if the map isn't available - if (Game.modData.MapCache[Map].Status != MapStatus.Available && !Game.Settings.Game.AllowDownloading) - return false; - - return true; - } - - public bool CompatibleVersion() - { - // Invalid game listing - we require one entry of id@version + ModMetadata mod; var modVersion = Mods.Split('@'); - if (modVersion.Length != 2) - return false; + if (modVersion.Length == 2 && ModMetadata.AllMods.TryGetValue(modVersion[0], out mod)) + { + ModLabel = "{0} ({1})".F(mod.Title, modVersion[1]); + ModVersion = modVersion[1]; - var mod = Game.modData.Manifest.Mod; + IsCompatible = Game.Settings.Debug.IgnoreVersionMismatch || (mod.Id == Game.modData.Manifest.Mod.Id && ModVersion == Game.modData.Manifest.Mod.Version); + } + else + ModLabel = "Unknown mod: {0}".F(Mods); - // Different mod - // TODO: Allow mod switch when joining server - if (modVersion[0] != mod.Id) - return false; - - // Same mod, but different version - if (modVersion[1] != mod.Version && !Game.Settings.Debug.IgnoreVersionMismatch) - return false; - - return true; + var mapAvailable = Game.Settings.Game.AllowDownloading || Game.modData.MapCache[Map].Status == MapStatus.Available; + IsJoinable = IsCompatible && State == 1 && mapAvailable; } } } diff --git a/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs index 96bf17574f..9bbf0a2de8 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs @@ -69,7 +69,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic panel.Get("CREATE_BUTTON").OnClick = OpenCreateServerPanel; var join = panel.Get("JOIN_BUTTON"); - join.IsDisabled = () => currentServer == null || !currentServer.CanJoin(); + join.IsDisabled = () => currentServer == null || !currentServer.IsJoinable; join.OnClick = () => Join(currentServer); panel.Get("BACK_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); }; @@ -151,7 +151,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic var data = Encoding.UTF8.GetString(i.Result); var yaml = MiniYaml.FromString(data); - var games = yaml.Select(a => FieldLoader.Load(a.Value)) + var games = yaml.Select(a => new GameServer(a.Value)) .Where(gs => gs.Address != null); RefreshServerListInner(games); @@ -168,14 +168,14 @@ namespace OpenRA.Mods.RA.Widgets.Logic var rows = new List(); - foreach (var loop in games.OrderByDescending(g => g.CanJoin()).ThenByDescending(g => g.Players)) + foreach (var loop in games.OrderByDescending(g => g.IsJoinable).ThenByDescending(g => g.Players)) { var game = loop; if (game == null) continue; - var canJoin = game.CanJoin(); - var compatible = game.CompatibleVersion(); + var canJoin = game.IsJoinable; + var compatible = game.IsCompatible; var item = ScrollItemWidget.Setup(serverTemplate, () => currentServer == game, () => currentServer = game, () => Join(game)); @@ -223,7 +223,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic var version = item.GetOrNull("VERSION"); if (version != null) { - version.GetText = () => GenerateModLabel(game); + version.GetText = () => game.ModLabel; version.IsVisible = () => !compatible; version.GetColor = () => !compatible ? Color.DarkGray : !canJoin ? Color.LightGray : version.TextColor; } @@ -300,7 +300,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic void Join(GameServer server) { - if (server == null || !server.CanJoin()) + if (server == null || !server.IsJoinable) return; var host = server.Address.Split(':')[0]; @@ -353,17 +353,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic return label.TextColor; } - public static string GenerateModLabel(GameServer s) - { - ModMetadata mod; - var modVersion = s.Mods.Split('@'); - - if (modVersion.Length == 2 && ModMetadata.AllMods.TryGetValue(modVersion[0], out mod)) - return "{0} ({1})".F(mod.Title, modVersion[1]); - - return "Unknown mod: {0}".F(s.Mods); - } - bool Filtered(GameServer game) { if ((game.State == (int)ServerState.GameStarted) && !showStarted) @@ -375,7 +364,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic if ((game.Players == 0) && !showEmpty) return true; - if (!game.CompatibleVersion() && !showIncompatible) + if (!game.IsCompatible && !showIncompatible) return true; if (game.Protected && !showProtected) From c45d472f9da25f0a8312c815a16b1983fc90b475 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 13 Dec 2014 11:10:17 +1300 Subject: [PATCH 2/4] Hide started games from the server list to reduce noise. --- OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs index 9bbf0a2de8..fade2cff2a 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs @@ -36,7 +36,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic bool showWaiting = true; bool showEmpty = true; - bool showStarted = true; + bool showStarted = false; bool showProtected = true; bool showIncompatible = false; From 568a23e395db774f57234c3fab8af7b5151d30c0 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sat, 13 Dec 2014 11:30:55 +1300 Subject: [PATCH 3/4] Show all mods in the server browser. --- OpenRA.Game/Network/GameServer.cs | 7 +- .../Widgets/Logic/ServerBrowserLogic.cs | 138 ++++++++++-------- mods/cnc/chrome/serverbrowser.yaml | 44 +++--- mods/ra/chrome/serverbrowser.yaml | 37 +++-- 4 files changed, 130 insertions(+), 96 deletions(-) diff --git a/OpenRA.Game/Network/GameServer.cs b/OpenRA.Game/Network/GameServer.cs index d5b2cb42ec..60fc8247ca 100644 --- a/OpenRA.Game/Network/GameServer.cs +++ b/OpenRA.Game/Network/GameServer.cs @@ -30,6 +30,7 @@ namespace OpenRA.Network public readonly bool IsJoinable = false; public readonly string ModLabel = ""; + public readonly string ModId = ""; public readonly string ModVersion = ""; public GameServer(MiniYaml yaml) @@ -40,10 +41,10 @@ namespace OpenRA.Network var modVersion = Mods.Split('@'); if (modVersion.Length == 2 && ModMetadata.AllMods.TryGetValue(modVersion[0], out mod)) { - ModLabel = "{0} ({1})".F(mod.Title, modVersion[1]); + ModId = modVersion[0]; ModVersion = modVersion[1]; - - IsCompatible = Game.Settings.Debug.IgnoreVersionMismatch || (mod.Id == Game.modData.Manifest.Mod.Id && ModVersion == Game.modData.Manifest.Mod.Version); + ModLabel = "{0} ({1})".F(mod.Title, modVersion[1]); + IsCompatible = Game.Settings.Debug.IgnoreVersionMismatch || ModVersion == mod.Version; } else ModLabel = "Unknown mod: {0}".F(Mods); diff --git a/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs index fade2cff2a..15e3a0e314 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/ServerBrowserLogic.cs @@ -26,6 +26,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic GameServer currentServer; ScrollItemWidget serverTemplate; + ScrollItemWidget headerTemplate; Action onStart; @@ -57,6 +58,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic this.onStart = onStart; serverList = panel.Get("SERVER_LIST"); + headerTemplate = serverList.Get("HEADER_TEMPLATE"); serverTemplate = serverList.Get("SERVER_TEMPLATE"); // Menu buttons @@ -154,91 +156,109 @@ namespace OpenRA.Mods.RA.Widgets.Logic var games = yaml.Select(a => new GameServer(a.Value)) .Where(gs => gs.Address != null); - RefreshServerListInner(games); Game.RunAfterTick(() => RefreshServerListInner(games)); }; currentQuery = new Download(Game.Settings.Server.MasterServer + "games", _ => {}, onComplete); } + int GroupSortOrder(GameServer testEntry) + { + // Games that we can't join are sorted last + if (!testEntry.IsCompatible) + return 0; + + // Games for the current mod+version are sorted first + if (testEntry.ModId == Game.modData.Manifest.Mod.Id) + return 2; + + // Followed by games for different mods that are joinable + return 1; + } + void RefreshServerListInner(IEnumerable games) { if (games == null) return; - var rows = new List(); + var mods = games.GroupBy(g => g.Mods) + .OrderByDescending(g => GroupSortOrder(g.First())) + .ThenByDescending(g => g.Count()); - foreach (var loop in games.OrderByDescending(g => g.IsJoinable).ThenByDescending(g => g.Players)) + var rows = new List(); + foreach (var modGames in mods) { - var game = loop; - if (game == null) + if (modGames.All(Filtered)) continue; - var canJoin = game.IsJoinable; - var compatible = game.IsCompatible; + var header = ScrollItemWidget.Setup(headerTemplate, () => true, () => {}); - var item = ScrollItemWidget.Setup(serverTemplate, () => currentServer == game, () => currentServer = game, () => Join(game)); + var headerTitle = modGames.First().ModLabel; + header.Get("LABEL").GetText = () => headerTitle; + rows.Add(header); - var map = Game.modData.MapCache[game.Map]; - var preview = item.GetOrNull("MAP_PREVIEW"); - if (preview != null) - preview.Preview = () => map; - - var title = item.GetOrNull("TITLE"); - if (title != null) + foreach (var loop in modGames.OrderByDescending(g => g.IsJoinable).ThenByDescending(g => g.Players)) { - title.GetText = () => game.Name; - title.GetColor = () => !compatible ? Color.DarkGray : !canJoin ? Color.LightGray : title.TextColor; - } + var game = loop; + if (game == null || Filtered(game)) + continue; - var maptitle = item.GetOrNull("MAP"); - if (title != null) - { - maptitle.GetText = () => map.Title; - maptitle.GetColor = () => !compatible ? Color.DarkGray : !canJoin ? Color.LightGray : maptitle.TextColor; - } + var canJoin = game.IsJoinable; + var compatible = game.IsCompatible; - var players = item.GetOrNull("PLAYERS"); - if (players != null) - { - players.GetText = () => "{0} / {1}".F(game.Players, game.MaxPlayers) - + (game.Spectators > 0 ? " ({0} Spectator{1})".F(game.Spectators, game.Spectators > 1 ? "s" : "") : ""); - players.GetColor = () => !compatible ? Color.DarkGray : !canJoin ? Color.LightGray : players.TextColor; - } + var item = ScrollItemWidget.Setup(serverTemplate, () => currentServer == game, () => currentServer = game, () => Join(game)); - var state = item.GetOrNull("STATE"); - if (state != null) - { - state.GetText = () => GetStateLabel(game); - state.GetColor = () => GetStateColor(game, state, !compatible || !canJoin); - } + var map = Game.modData.MapCache[game.Map]; + var preview = item.GetOrNull("MAP_PREVIEW"); + if (preview != null) + preview.Preview = () => map; - var ip = item.GetOrNull("IP"); - if (ip != null) - { - ip.GetText = () => game.Address; - ip.GetColor = () => !compatible ? Color.DarkGray : !canJoin ? Color.LightGray : ip.TextColor; - } + var title = item.GetOrNull("TITLE"); + if (title != null) + { + title.GetText = () => game.Name; + title.GetColor = () => !compatible ? Color.DarkGray : !canJoin ? Color.LightGray : title.TextColor; + } - var version = item.GetOrNull("VERSION"); - if (version != null) - { - version.GetText = () => game.ModLabel; - version.IsVisible = () => !compatible; - version.GetColor = () => !compatible ? Color.DarkGray : !canJoin ? Color.LightGray : version.TextColor; - } + var maptitle = item.GetOrNull("MAP"); + if (title != null) + { + maptitle.GetText = () => map.Title; + maptitle.GetColor = () => !compatible ? Color.DarkGray : !canJoin ? Color.LightGray : maptitle.TextColor; + } - var location = item.GetOrNull("LOCATION"); - if (location != null) - { - var cachedServerLocation = LobbyUtils.LookupCountry(game.Address.Split(':')[0]); - location.GetText = () => cachedServerLocation; - location.IsVisible = () => compatible; - location.GetColor = () => !compatible ? Color.DarkGray : !canJoin ? Color.LightGray : location.TextColor; - } + var players = item.GetOrNull("PLAYERS"); + if (players != null) + { + players.GetText = () => "{0} / {1}".F(game.Players, game.MaxPlayers) + + (game.Spectators > 0 ? " ({0} Spectator{1})".F(game.Spectators, game.Spectators > 1 ? "s" : "") : ""); + players.GetColor = () => !compatible ? Color.DarkGray : !canJoin ? Color.LightGray : players.TextColor; + } + + var state = item.GetOrNull("STATE"); + if (state != null) + { + state.GetText = () => GetStateLabel(game); + state.GetColor = () => GetStateColor(game, state, !compatible || !canJoin); + } + + var ip = item.GetOrNull("IP"); + if (ip != null) + { + ip.GetText = () => game.Address; + ip.GetColor = () => !compatible ? Color.DarkGray : !canJoin ? Color.LightGray : ip.TextColor; + } + + var location = item.GetOrNull("LOCATION"); + if (location != null) + { + var cachedServerLocation = LobbyUtils.LookupCountry(game.Address.Split(':')[0]); + location.GetText = () => cachedServerLocation; + location.GetColor = () => !compatible ? Color.DarkGray : !canJoin ? Color.LightGray : location.TextColor; + } - if (!Filtered(game)) rows.Add(item); + } } Game.RunAfterTick(() => diff --git a/mods/cnc/chrome/serverbrowser.yaml b/mods/cnc/chrome/serverbrowser.yaml index 6b8683ccdc..2e93f9f0f2 100644 --- a/mods/cnc/chrome/serverbrowser.yaml +++ b/mods/cnc/chrome/serverbrowser.yaml @@ -26,40 +26,40 @@ Container@SERVERBROWSER_PANEL: Font: Bold Checkbox@WAITING_FOR_PLAYERS: X: 80 - Y: 468 + Y: 467 Width: 100 Height: 20 Text: Waiting TextColor: 50,205,50 Checkbox@EMPTY: X: 180 - Y: 468 + Y: 467 Width: 100 Height: 20 Text: Empty - Checkbox@ALREADY_STARTED: - X: 270 - Y: 468 - Width: 100 - Height: 20 - Text: Started - TextColor: 255,165,0 Checkbox@PASSWORD_PROTECTED: - X: 370 - Y: 468 + X: 270 + Y: 467 Width: 100 Height: 20 Text: Protected TextColor: 255,0,0 + Checkbox@ALREADY_STARTED: + X: 385 + Y: 467 + Width: 100 + Height: 20 + Text: Started + TextColor: 255,165,0 Checkbox@INCOMPATIBLE_VERSION: X: 480 - Y: 468 + Y: 467 Width: 100 Height: 20 Text: Incompatible TextColor: 190,190,190 Button@REFRESH_BUTTON: - X: PARENT_RIGHT - WIDTH - 20 + X: PARENT_RIGHT - WIDTH - 15 Y: 465 Width: 100 Height: 25 @@ -70,6 +70,18 @@ Container@SERVERBROWSER_PANEL: Width: 700 Height: 440 Children: + ScrollItem@HEADER_TEMPLATE: + Width: PARENT_RIGHT-27 + Height: 25 + X: 2 + Visible: false + Children: + Label@LABEL: + Y: 0-1 + Font: Bold + Width: PARENT_RIGHT + Height: 25 + Align: Center ScrollItem@SERVER_TEMPLATE: Width: PARENT_RIGHT-27 Height: 68 @@ -109,12 +121,6 @@ Container@SERVERBROWSER_PANEL: Y: 20 Align: Right Height: 25 - Label@VERSION: - Width: 140 - X: PARENT_RIGHT-150 - Y: 40 - Align: Right - Height: 25 Label@LOCATION: Width: 140 X: PARENT_RIGHT-150 diff --git a/mods/ra/chrome/serverbrowser.yaml b/mods/ra/chrome/serverbrowser.yaml index 34846bafd4..cdcae58345 100644 --- a/mods/ra/chrome/serverbrowser.yaml +++ b/mods/ra/chrome/serverbrowser.yaml @@ -15,7 +15,7 @@ Background@SERVERBROWSER_PANEL: Font: Bold Label@SHOW_LABEL_TITLE: X: 20 - Y: 45 + Y: 48 Width: 20 Height: 25 Text: Show: @@ -33,20 +33,20 @@ Background@SERVERBROWSER_PANEL: Width: 100 Height: 20 Text: Empty - Checkbox@ALREADY_STARTED: - X: 280 - Y: 50 - Width: 100 - Height: 20 - Text: Started - TextColor: 255,165,0 Checkbox@PASSWORD_PROTECTED: - X: 380 + X: 270 Y: 50 Width: 100 Height: 20 Text: Protected TextColor: 255,0,0 + Checkbox@ALREADY_STARTED: + X: 385 + Y: 50 + Width: 100 + Height: 20 + Text: Started + TextColor: 255,165,0 Checkbox@INCOMPATIBLE_VERSION: X: 480 Y: 50 @@ -60,6 +60,19 @@ Background@SERVERBROWSER_PANEL: Width: 700 Height: 360 Children: + ScrollItem@HEADER_TEMPLATE: + BaseName: scrollheader + Width: PARENT_RIGHT-27 + Height: 25 + X: 2 + Visible: false + Children: + Label@LABEL: + Y: 0-1 + Font: Bold + Width: PARENT_RIGHT + Height: 25 + Align: Center ScrollItem@SERVER_TEMPLATE: Width: PARENT_RIGHT-27 Height: 68 @@ -99,12 +112,6 @@ Background@SERVERBROWSER_PANEL: Y: 20 Align: Right Height: 25 - Label@VERSION: - Width: 140 - X: PARENT_RIGHT-150 - Y: 40 - Align: Right - Height: 25 Label@LOCATION: Width: 140 X: PARENT_RIGHT-150 From 2cdcd0f590db1110b93cde9e30073bbb5bd8c37c Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Thu, 25 Dec 2014 17:05:01 +1300 Subject: [PATCH 4/4] =?UTF-8?q?Don=E2=80=99t=20crash=20if=20a=20replay=20c?= =?UTF-8?q?an=E2=80=99t=20be=20loaded.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenRA.Game/Network/UnitOrders.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/OpenRA.Game/Network/UnitOrders.cs b/OpenRA.Game/Network/UnitOrders.cs index 4a1d1d2818..7a88bd5c18 100644 --- a/OpenRA.Game/Network/UnitOrders.cs +++ b/OpenRA.Game/Network/UnitOrders.cs @@ -93,6 +93,15 @@ namespace OpenRA.Network case "StartGame": { + if (Game.modData.MapCache[orderManager.LobbyInfo.GlobalSettings.Map].Status != MapStatus.Available) + { + Game.Disconnect(); + Game.LoadShellMap(); + + // TODO: After adding a startup error dialog, notify the replay load failure. + break; + } + Game.AddChatLine(Color.White, "Server", "The game has started."); Game.StartGame(orderManager.LobbyInfo.GlobalSettings.Map, false); break;