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