public AnonymousProfileTooltipLogic(Widget widget, OrderManager orderManager, Session.Client client) { var address = LobbyUtils.GetExternalIP(client, orderManager); var cachedDescriptiveIP = address ?? "Unknown IP"; var nameLabel = widget.Get <LabelWidget>("NAME"); var nameFont = Game.Renderer.Fonts[nameLabel.Font]; widget.Bounds.Width = nameFont.Measure(nameLabel.Text).X + 2 * nameLabel.Bounds.Left; var ipLabel = widget.Get <LabelWidget>("IP"); ipLabel.GetText = () => cachedDescriptiveIP; var locationLabel = widget.Get <LabelWidget>("LOCATION"); var cachedCountryLookup = GeoIP.LookupCountry(address); locationLabel.GetText = () => cachedCountryLookup; if (client.IsAdmin) { var adminLabel = widget.Get("GAME_ADMIN"); adminLabel.IsVisible = () => client.IsAdmin; widget.Bounds.Height += adminLabel.Bounds.Height; } }
void ValidateClient(Connection newConn, string data) { try { if (State == ServerState.GameStarted) { Log.Write("server", "Rejected connection from {0}; game is already started.", newConn.Socket.RemoteEndPoint); SendOrderTo(newConn, "ServerError", "The game has already started"); DropClient(newConn); return; } var handshake = HandshakeResponse.Deserialize(data); if (!string.IsNullOrEmpty(Settings.Password) && handshake.Password != Settings.Password) { var message = string.IsNullOrEmpty(handshake.Password) ? "Server requires a password" : "Incorrect password"; SendOrderTo(newConn, "AuthenticationError", message); DropClient(newConn); return; } var ipAddress = ((IPEndPoint)newConn.Socket.RemoteEndPoint).Address; var client = new Session.Client { Name = OpenRA.Settings.SanitizedPlayerName(handshake.Client.Name), IPAddress = ipAddress.ToString(), AnonymizedIPAddress = Type != ServerType.Local && Settings.ShareAnonymizedIPs ? Session.AnonymizeIP(ipAddress) : null, Location = GeoIP.LookupCountry(ipAddress), Index = newConn.PlayerIndex, PreferredColor = handshake.Client.PreferredColor, Color = handshake.Client.Color, Faction = "Random", SpawnPoint = 0, Team = 0, State = Session.ClientState.Invalid, }; if (ModData.Manifest.Id != handshake.Mod) { Log.Write("server", "Rejected connection from {0}; mods do not match.", newConn.Socket.RemoteEndPoint); SendOrderTo(newConn, "ServerError", "Server is running an incompatible mod"); DropClient(newConn); return; } if (ModData.Manifest.Metadata.Version != handshake.Version) { Log.Write("server", "Rejected connection from {0}; Not running the same version.", newConn.Socket.RemoteEndPoint); SendOrderTo(newConn, "ServerError", "Server is running an incompatible version"); DropClient(newConn); return; } if (handshake.OrdersProtocol != ProtocolVersion.Orders) { Log.Write("server", "Rejected connection from {0}; incompatible Orders protocol version {1}.", newConn.Socket.RemoteEndPoint, handshake.OrdersProtocol); SendOrderTo(newConn, "ServerError", "Server is running an incompatible protocol"); DropClient(newConn); return; } // Check if IP is banned var bans = Settings.Ban.Union(TempBans); if (bans.Contains(client.IPAddress)) { Log.Write("server", "Rejected connection from {0}; Banned.", newConn.Socket.RemoteEndPoint); SendOrderTo(newConn, "ServerError", "You have been {0} from the server".F(Settings.Ban.Contains(client.IPAddress) ? "banned" : "temporarily banned")); DropClient(newConn); return; } Action completeConnection = () => { lock (LobbyInfo) { client.Slot = LobbyInfo.FirstEmptySlot(); client.IsAdmin = !LobbyInfo.Clients.Any(c1 => c1.IsAdmin); if (client.IsObserver && !LobbyInfo.GlobalSettings.AllowSpectators) { SendOrderTo(newConn, "ServerError", "The game is full"); DropClient(newConn); return; } if (client.Slot != null) { SyncClientToPlayerReference(client, Map.Players.Players[client.Slot]); } else { client.Color = Color.White; } // Promote connection to a valid client PreConns.Remove(newConn); Conns.Add(newConn); LobbyInfo.Clients.Add(client); newConn.Validated = true; var clientPing = new Session.ClientPing { Index = client.Index }; LobbyInfo.ClientPings.Add(clientPing); Log.Write("server", "Client {0}: Accepted connection from {1}.", newConn.PlayerIndex, newConn.Socket.RemoteEndPoint); if (client.Fingerprint != null) { Log.Write("server", "Client {0}: Player fingerprint is {1}.", newConn.PlayerIndex, client.Fingerprint); } foreach (var t in serverTraits.WithInterface <IClientJoined>()) { t.ClientJoined(this, newConn); } SyncLobbyInfo(); Log.Write("server", "{0} ({1}) has joined the game.", client.Name, newConn.Socket.RemoteEndPoint); // Report to all other players SendMessage("{0} has joined the game.".F(client.Name), newConn); // Send initial ping SendOrderTo(newConn, "Ping", Game.RunTime.ToString(CultureInfo.InvariantCulture)); if (Type == ServerType.Dedicated) { var motdFile = Platform.ResolvePath(Platform.SupportDirPrefix, "motd.txt"); if (!File.Exists(motdFile)) { File.WriteAllText(motdFile, "Welcome, have fun and good luck!"); } var motd = File.ReadAllText(motdFile); if (!string.IsNullOrEmpty(motd)) { SendOrderTo(newConn, "Message", motd); } } if (Map.DefinesUnsafeCustomRules) { SendOrderTo(newConn, "Message", "This map contains custom rules. Game experience may change."); } if (!LobbyInfo.GlobalSettings.EnableSingleplayer) { SendOrderTo(newConn, "Message", TwoHumansRequiredText); } else if (Map.Players.Players.Where(p => p.Value.Playable).All(p => !p.Value.AllowBots)) { SendOrderTo(newConn, "Message", "Bots have been disabled on this map."); } } }; if (Type == ServerType.Local) { // Local servers can only be joined by the local client, so we can trust their identity without validation client.Fingerprint = handshake.Fingerprint; completeConnection(); } else if (!string.IsNullOrEmpty(handshake.Fingerprint) && !string.IsNullOrEmpty(handshake.AuthSignature)) { waitingForAuthenticationCallback++; Action <DownloadDataCompletedEventArgs> onQueryComplete = i => { PlayerProfile profile = null; if (i.Error == null) { try { var yaml = MiniYaml.FromString(Encoding.UTF8.GetString(i.Result)).First(); if (yaml.Key == "Player") { profile = FieldLoader.Load <PlayerProfile>(yaml.Value); var publicKey = Encoding.ASCII.GetString(Convert.FromBase64String(profile.PublicKey)); var parameters = CryptoUtil.DecodePEMPublicKey(publicKey); if (!profile.KeyRevoked && CryptoUtil.VerifySignature(parameters, newConn.AuthToken, handshake.AuthSignature)) { client.Fingerprint = handshake.Fingerprint; Log.Write("server", "{0} authenticated as {1} (UID {2})", newConn.Socket.RemoteEndPoint, profile.ProfileName, profile.ProfileID); } else if (profile.KeyRevoked) { profile = null; Log.Write("server", "{0} failed to authenticate as {1} (key revoked)", newConn.Socket.RemoteEndPoint, handshake.Fingerprint); } else { profile = null; Log.Write("server", "{0} failed to authenticate as {1} (signature verification failed)", newConn.Socket.RemoteEndPoint, handshake.Fingerprint); } } else { Log.Write("server", "{0} failed to authenticate as {1} (invalid server response: `{2}` is not `Player`)", newConn.Socket.RemoteEndPoint, handshake.Fingerprint, yaml.Key); } } catch (Exception ex) { Log.Write("server", "{0} failed to authenticate as {1} (exception occurred)", newConn.Socket.RemoteEndPoint, handshake.Fingerprint); Log.Write("server", ex.ToString()); } } else { Log.Write("server", "{0} failed to authenticate as {1} (server error: `{2}`)", newConn.Socket.RemoteEndPoint, handshake.Fingerprint, i.Error); } delayedActions.Add(() => { var notAuthenticated = Type == ServerType.Dedicated && profile == null && (Settings.RequireAuthentication || Settings.ProfileIDWhitelist.Any()); var blacklisted = Type == ServerType.Dedicated && profile != null && Settings.ProfileIDBlacklist.Contains(profile.ProfileID); var notWhitelisted = Type == ServerType.Dedicated && Settings.ProfileIDWhitelist.Any() && (profile == null || !Settings.ProfileIDWhitelist.Contains(profile.ProfileID)); if (notAuthenticated) { Log.Write("server", "Rejected connection from {0}; Not authenticated.", newConn.Socket.RemoteEndPoint); SendOrderTo(newConn, "ServerError", "Server requires players to have an OpenRA forum account"); DropClient(newConn); } else if (blacklisted || notWhitelisted) { if (blacklisted) { Log.Write("server", "Rejected connection from {0}; In server blacklist.", newConn.Socket.RemoteEndPoint); } else { Log.Write("server", "Rejected connection from {0}; Not in server whitelist.", newConn.Socket.RemoteEndPoint); } SendOrderTo(newConn, "ServerError", "You do not have permission to join this server"); DropClient(newConn); } else { completeConnection(); } waitingForAuthenticationCallback--; }, 0); }; new Download(playerDatabase.Profile + handshake.Fingerprint, _ => { }, onQueryComplete); } else { if (Type == ServerType.Dedicated && (Settings.RequireAuthentication || Settings.ProfileIDWhitelist.Any())) { Log.Write("server", "Rejected connection from {0}; Not authenticated.", newConn.Socket.RemoteEndPoint); SendOrderTo(newConn, "ServerError", "Server requires players to have an OpenRA forum account"); DropClient(newConn); } else { completeConnection(); } } } catch (Exception ex) { Log.Write("server", "Dropping connection {0} because an error occurred:", newConn.Socket.RemoteEndPoint); Log.Write("server", ex.ToString()); DropClient(newConn); } }
List <Widget> LoadGameRows(List <GameServer> games, out ScrollItemWidget nextServerRow) { nextServerRow = null; var rows = new List <Widget>(); var mods = games.GroupBy(g => g.ModLabel) .OrderByDescending(g => GroupSortOrder(g.First())) .ThenByDescending(g => g.Count()); foreach (var modGames in mods) { if (modGames.All(Filtered)) { continue; } var header = ScrollItemWidget.Setup(headerTemplate, () => true, () => { }); var headerTitle = modGames.First().ModLabel; header.Get <LabelWidget>("LABEL").GetText = () => headerTitle; rows.Add(header); Func <GameServer, int> listOrder = g => { // Servers waiting for players are always first if (g.State == (int)ServerState.WaitingPlayers && g.Players > 0) { return(0); } // Then servers with spectators if (g.State == (int)ServerState.WaitingPlayers && g.Spectators > 0) { return(1); } // Then active games if (g.State >= (int)ServerState.GameStarted) { return(2); } // Empty servers are shown at the end because a flood of empty servers // at the top of the game list make the community look dead return(3); }; foreach (var modGamesByState in modGames.GroupBy(listOrder).OrderBy(g => g.Key)) { // Sort 'Playing' games by Started, others by number of players foreach (var game in modGamesByState.Key == 2 ? modGamesByState.OrderByDescending(g => g.Started) : modGamesByState.OrderByDescending(g => g.Players)) { if (Filtered(game)) { continue; } var canJoin = game.IsJoinable; var item = ScrollItemWidget.Setup(serverTemplate, () => currentServer == game, () => SelectServer(game), () => onJoin(game)); var title = item.GetOrNull <LabelWidget>("TITLE"); if (title != null) { var font = Game.Renderer.Fonts[title.Font]; var label = WidgetUtils.TruncateText(game.Name, title.Bounds.Width, font); title.GetText = () => label; title.GetColor = () => canJoin ? title.TextColor : incompatibleGameColor; } var password = item.GetOrNull <ImageWidget>("PASSWORD_PROTECTED"); if (password != null) { password.IsVisible = () => game.Protected; password.GetImageName = () => canJoin ? "protected" : "protected-disabled"; } var players = item.GetOrNull <LabelWithTooltipWidget>("PLAYERS"); if (players != null) { var label = "{0} / {1}".F(game.Players + game.Bots, game.MaxPlayers + game.Bots) + (game.Spectators > 0 ? " + {0}".F(game.Spectators) : ""); var color = canJoin ? players.TextColor : incompatibleGameColor; players.GetText = () => label; players.GetColor = () => color; if (game.Clients.Any()) { var displayClients = game.Clients.Select(c => c.Name); if (game.Clients.Length > 10) { displayClients = displayClients .Take(9) .Append("+ {0} other players".F(game.Clients.Length - 9)); } var tooltip = displayClients.JoinWith("\n"); players.GetTooltipText = () => tooltip; } else { players.GetTooltipText = null; } } var state = item.GetOrNull <LabelWidget>("STATUS"); if (state != null) { var label = game.State >= (int)ServerState.GameStarted ? "Playing" : "Waiting"; state.GetText = () => label; var color = GetStateColor(game, state, !canJoin); state.GetColor = () => color; } var location = item.GetOrNull <LabelWidget>("LOCATION"); if (location != null) { var font = Game.Renderer.Fonts[location.Font]; var cachedServerLocation = game.Id != -1 ? GeoIP.LookupCountry(game.Address.Split(':')[0]) : "Local Network"; var label = WidgetUtils.TruncateText(cachedServerLocation, location.Bounds.Width, font); location.GetText = () => label; location.GetColor = () => canJoin ? location.TextColor : incompatibleGameColor; } if (currentServer != null && game.Address == currentServer.Address) { nextServerRow = item; } rows.Add(item); } } } return(rows); }
public ClientTooltipLogic(Widget widget, TooltipContainerWidget tooltipContainer, OrderManager orderManager, int clientIndex) { var admin = widget.Get <LabelWidget>("ADMIN"); var adminFont = Game.Renderer.Fonts[admin.Font]; var latency = widget.GetOrNull <LabelWidget>("LATENCY"); if (latency != null) { latencyFont = Game.Renderer.Fonts[latency.Font]; } var latencyPrefix = widget.GetOrNull <LabelWidget>("LATENCY_PREFIX"); if (latencyPrefix != null) { latencyPrefixFont = Game.Renderer.Fonts[latencyPrefix.Font]; } var ip = widget.Get <LabelWidget>("IP"); var addressFont = Game.Renderer.Fonts[ip.Font]; var location = widget.Get <LabelWidget>("LOCATION"); var locationFont = Game.Renderer.Fonts[location.Font]; var locationOffset = location.Bounds.Y; var addressOffset = ip.Bounds.Y; var latencyOffset = latency == null ? 0 : latency.Bounds.Y; var tooltipHeight = widget.Bounds.Height; var margin = widget.Bounds.Width; widget.IsVisible = () => (orderManager.LobbyInfo.ClientWithIndex(clientIndex) != null); tooltipContainer.BeforeRender = () => { if (!widget.IsVisible()) { return; } var latencyPrefixSize = latencyPrefix == null ? 0 : latencyPrefix.Bounds.X + latencyPrefixFont.Measure(latencyPrefix.GetText() + " ").X; var locationWidth = locationFont.Measure(location.GetText()).X; var adminWidth = adminFont.Measure(admin.GetText()).X; var addressWidth = addressFont.Measure(ip.GetText()).X; var latencyWidth = latencyFont == null ? 0 : latencyPrefixSize + latencyFont.Measure(latency.GetText()).X; var width = Math.Max(locationWidth, Math.Max(adminWidth, Math.Max(addressWidth, latencyWidth))); widget.Bounds.Width = width + 2 * margin; if (latency != null) { latency.Bounds.Width = widget.Bounds.Width; } ip.Bounds.Width = widget.Bounds.Width; admin.Bounds.Width = widget.Bounds.Width; location.Bounds.Width = widget.Bounds.Width; ip.Bounds.Y = addressOffset; if (latency != null) { latency.Bounds.Y = latencyOffset; } location.Bounds.Y = locationOffset; widget.Bounds.Height = tooltipHeight; if (admin.IsVisible()) { ip.Bounds.Y += admin.Bounds.Height; if (latency != null) { latency.Bounds.Y += admin.Bounds.Height; } location.Bounds.Y += admin.Bounds.Height; widget.Bounds.Height += admin.Bounds.Height; } if (latencyPrefix != null) { latencyPrefix.Bounds.Y = latency.Bounds.Y; } if (latency != null) { latency.Bounds.X = latencyPrefixSize; } }; admin.IsVisible = () => orderManager.LobbyInfo.ClientWithIndex(clientIndex).IsAdmin; var client = orderManager.LobbyInfo.ClientWithIndex(clientIndex); var ping = orderManager.LobbyInfo.PingFromClient(client); if (latency != null) { latency.GetText = () => LobbyUtils.LatencyDescription(ping); latency.GetColor = () => LobbyUtils.LatencyColor(ping); } var address = LobbyUtils.GetExternalIP(clientIndex, orderManager); var cachedDescriptiveIP = LobbyUtils.DescriptiveIpAddress(address); ip.GetText = () => cachedDescriptiveIP; var cachedCountryLookup = GeoIP.LookupCountry(address); location.GetText = () => cachedCountryLookup; }
void RefreshServerListInner(List <GameServer> games) { if (games == null) { return; } var mods = games.GroupBy(g => g.Mods) .OrderByDescending(g => GroupSortOrder(g.First())) .ThenByDescending(g => g.Count()); ScrollItemWidget nextServerRow = null; var rows = new List <Widget>(); foreach (var modGames in mods) { if (modGames.All(Filtered)) { continue; } var header = ScrollItemWidget.Setup(headerTemplate, () => true, () => { }); var headerTitle = modGames.First().ModLabel; header.Get <LabelWidget>("LABEL").GetText = () => headerTitle; rows.Add(header); Func <GameServer, int> listOrder = g => { // Servers waiting for players are always first if (g.State == (int)ServerState.WaitingPlayers && g.Players > 0) { return(0); } // Then servers with spectators if (g.State == (int)ServerState.WaitingPlayers && g.Spectators > 0) { return(1); } // Then active games if (g.State >= (int)ServerState.GameStarted) { return(2); } // Empty servers are shown at the end because a flood of empty servers // at the top of the game list make the community look dead return(3); }; foreach (var modGamesByState in modGames.GroupBy(listOrder).OrderBy(g => g.Key)) { // Sort 'Playing' games by Started, others by number of players foreach (var game in modGamesByState.Key == 2 ? modGamesByState.OrderByDescending(g => g.Started) : modGamesByState.OrderByDescending(g => g.Players)) { if (Filtered(game)) { continue; } var canJoin = game.IsJoinable; var item = ScrollItemWidget.Setup(serverTemplate, () => currentServer == game, () => SelectServer(game), () => Join(game)); var title = item.GetOrNull <LabelWidget>("TITLE"); if (title != null) { var font = Game.Renderer.Fonts[title.Font]; var label = WidgetUtils.TruncateText(game.Name, title.Bounds.Width, font); title.GetText = () => label; title.GetColor = () => canJoin ? title.TextColor : incompatibleGameColor; } var password = item.GetOrNull <ImageWidget>("PASSWORD_PROTECTED"); if (password != null) { password.IsVisible = () => game.Protected; password.GetImageName = () => canJoin ? "protected" : "protected-disabled"; } var players = item.GetOrNull <LabelWidget>("PLAYERS"); if (players != null) { players.GetText = () => "{0} / {1}".F(game.Players, game.MaxPlayers) + (game.Spectators > 0 ? " + {0}".F(game.Spectators) : ""); players.GetColor = () => canJoin ? players.TextColor : incompatibleGameColor; } var state = item.GetOrNull <LabelWidget>("STATUS"); if (state != null) { var label = game.State >= (int)ServerState.GameStarted ? "Playing" : "Waiting"; state.GetText = () => label; var color = GetStateColor(game, state, !canJoin); state.GetColor = () => color; } var location = item.GetOrNull <LabelWidget>("LOCATION"); if (location != null) { var font = Game.Renderer.Fonts[location.Font]; var cachedServerLocation = game.Id != -1 ? GeoIP.LookupCountry(game.Address.Split(':')[0]) : "Local Network"; var label = WidgetUtils.TruncateText(cachedServerLocation, location.Bounds.Width, font); location.GetText = () => label; location.GetColor = () => canJoin ? location.TextColor : incompatibleGameColor; } if (currentServer != null && game.Address == currentServer.Address) { nextServerRow = item; } rows.Add(item); } } } Game.RunAfterTick(() => { serverList.RemoveChildren(); SelectServer(null); if (games == null) { searchStatus = SearchStatus.Failed; return; } if (!rows.Any()) { searchStatus = SearchStatus.NoGames; return; } searchStatus = SearchStatus.Hidden; // Search for any unknown maps if (Game.Settings.Game.AllowDownloading) { modData.MapCache.QueryRemoteMapDetails(services.MapRepository, games.Where(g => !Filtered(g)).Select(g => g.Map)); } foreach (var row in rows) { serverList.AddChild(row); } if (nextServerRow != null) { nextServerRow.OnClick(); } }); }
void RefreshServerListInner(IEnumerable <GameServer> games) { if (games == null) { return; } var mods = games.GroupBy(g => g.Mods) .OrderByDescending(g => GroupSortOrder(g.First())) .ThenByDescending(g => g.Count()); ScrollItemWidget nextServerRow = null; var rows = new List <Widget>(); foreach (var modGames in mods) { if (modGames.All(Filtered)) { continue; } var header = ScrollItemWidget.Setup(headerTemplate, () => true, () => { }); var headerTitle = modGames.First().ModLabel; header.Get <LabelWidget>("LABEL").GetText = () => headerTitle; rows.Add(header); foreach (var loop in modGames.OrderByDescending(g => g.IsJoinable).ThenByDescending(g => g.Players)) { var game = loop; if (game == null || Filtered(game)) { continue; } var canJoin = game.IsJoinable; var item = ScrollItemWidget.Setup(serverTemplate, () => currentServer == game, () => SelectServer(game), () => Join(game)); var title = item.GetOrNull <LabelWidget>("TITLE"); if (title != null) { var font = Game.Renderer.Fonts[title.Font]; var label = WidgetUtils.TruncateText(game.Name, title.Bounds.Width, font); title.GetText = () => label; title.GetColor = () => canJoin ? title.TextColor : incompatibleGameColor; } var password = item.GetOrNull <ImageWidget>("PASSWORD_PROTECTED"); if (password != null) { password.IsVisible = () => game.Protected; password.GetImageName = () => canJoin ? "protected" : "protected-disabled"; } var players = item.GetOrNull <LabelWidget>("PLAYERS"); if (players != null) { players.GetText = () => "{0} / {1}".F(game.Players, game.MaxPlayers) + (game.Spectators > 0 ? " + {0}".F(game.Spectators) : ""); players.GetColor = () => canJoin ? players.TextColor : incompatibleGameColor; } var state = item.GetOrNull <LabelWidget>("STATUS"); if (state != null) { var label = game.State >= (int)ServerState.GameStarted ? "Playing" : "Waiting"; state.GetText = () => label; var color = GetStateColor(game, state, !canJoin); state.GetColor = () => color; } var location = item.GetOrNull <LabelWidget>("LOCATION"); if (location != null) { var cachedServerLocation = GeoIP.LookupCountry(game.Address.Split(':')[0]); location.GetText = () => cachedServerLocation; location.GetColor = () => canJoin ? location.TextColor : incompatibleGameColor; } if (currentServer != null && game.Address == currentServer.Address) { nextServerRow = item; } rows.Add(item); } } Game.RunAfterTick(() => { serverList.RemoveChildren(); SelectServer(null); if (games == null) { searchStatus = SearchStatus.Failed; return; } if (!rows.Any()) { searchStatus = SearchStatus.NoGames; return; } searchStatus = SearchStatus.Hidden; // Search for any unknown maps if (Game.Settings.Game.AllowDownloading) { Game.ModData.MapCache.QueryRemoteMapDetails(games.Where(g => !Filtered(g)).Select(g => g.Map)); } foreach (var row in rows) { serverList.AddChild(row); } if (nextServerRow != null) { nextServerRow.OnClick(); } }); }
void RefreshServerListInner(IEnumerable <GameServer> games) { if (games == null) { return; } var mods = games.GroupBy(g => g.Mods) .OrderByDescending(g => GroupSortOrder(g.First())) .ThenByDescending(g => g.Count()); var rows = new List <Widget>(); foreach (var modGames in mods) { if (modGames.All(Filtered)) { continue; } var header = ScrollItemWidget.Setup(headerTemplate, () => true, () => { }); var headerTitle = modGames.First().ModLabel; header.Get <LabelWidget>("LABEL").GetText = () => headerTitle; rows.Add(header); foreach (var loop in modGames.OrderByDescending(g => g.IsJoinable).ThenByDescending(g => g.Players)) { var game = loop; if (game == null || Filtered(game)) { continue; } var canJoin = game.IsJoinable; var compatible = game.IsCompatible; var item = ScrollItemWidget.Setup(serverTemplate, () => currentServer == game, () => currentServer = game, () => Join(game)); var map = Game.ModData.MapCache[game.Map]; var preview = item.GetOrNull <MapPreviewWidget>("MAP_PREVIEW"); if (preview != null) { preview.Preview = () => map; } var title = item.GetOrNull <LabelWidget>("TITLE"); if (title != null) { title.GetText = () => game.Name; title.GetColor = () => !compatible ? incompatibleGameColor : !canJoin ? cantJoinGameColor : title.TextColor; } var maptitle = item.GetOrNull <LabelWidget>("MAP"); if (maptitle != null) { maptitle.GetText = () => map.Title; maptitle.GetColor = () => !compatible ? Color.DarkGray : !canJoin ? Color.LightGray : maptitle.TextColor; } var players = item.GetOrNull <LabelWidget>("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 ? incompatibleGameColor : !canJoin ? cantJoinGameColor : players.TextColor; } var state = item.GetOrNull <LabelWidget>("STATE"); if (state != null) { state.GetText = () => GetStateLabel(game); state.GetColor = () => GetStateColor(game, state, !compatible || !canJoin); } var ip = item.GetOrNull <LabelWidget>("IP"); if (ip != null) { ip.GetText = () => game.Address; ip.GetColor = () => !compatible ? incompatibleGameColor : !canJoin ? cantJoinGameColor : ip.TextColor; } var location = item.GetOrNull <LabelWidget>("LOCATION"); if (location != null) { var cachedServerLocation = GeoIP.LookupCountry(game.Address.Split(':')[0]); location.GetText = () => cachedServerLocation; location.GetColor = () => !compatible ? incompatibleGameColor : !canJoin ? cantJoinGameColor : location.TextColor; } rows.Add(item); } } Game.RunAfterTick(() => { serverList.RemoveChildren(); currentServer = null; if (games == null) { searchStatus = SearchStatus.Failed; return; } if (!games.Any()) { searchStatus = SearchStatus.NoGames; return; } currentServer = games.FirstOrDefault(); searchStatus = SearchStatus.Hidden; // Search for any unknown maps if (Game.Settings.Game.AllowDownloading) { Game.ModData.MapCache.QueryRemoteMapDetails(games.Where(g => !Filtered(g)).Select(g => g.Map)); } foreach (var row in rows) { serverList.AddChild(row); } }); }