using Barotrauma.Extensions; using Barotrauma.Networking; using Barotrauma.Steam; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using RestSharp; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Threading; namespace Barotrauma { class ServerListScreen : Screen { //how often the client is allowed to refresh servers private TimeSpan AllowedRefreshInterval = new TimeSpan(0, 0, 3); private readonly GUIFrame menu; private readonly GUIListBox serverList; private readonly GUIFrame serverPreview; private readonly GUIButton joinButton; private readonly GUITextBox clientNameBox, ipBox; private bool masterServerResponded; private IRestResponse masterServerResponse; private readonly float[] columnRelativeWidth = new float[] { 0.1f, 0.1f, 0.7f, 0.12f, 0.08f, 0.08f }; private readonly string[] columnLabel = new string[] { "ServerListCompatible", "ServerListHasPassword", "ServerListName", "ServerListRoundStarted", "ServerListPlayers", "ServerListPing" }; private readonly GUILayoutGroup labelHolder; private readonly List labelTexts = new List(); //filters private readonly GUITextBox searchBox; private readonly GUITickBox filterPassword; private readonly GUITickBox filterIncompatible; private readonly GUITickBox filterFull; private readonly GUITickBox filterEmpty; private string sortedBy; private readonly GUIButton serverPreviewToggleButton; //a timer for preventing the client from spamming the refresh button faster than AllowedRefreshInterval private DateTime refreshDisableTimer; private bool waitingForRefresh; public ServerListScreen() { GameMain.Instance.OnResolutionChanged += OnResolutionChanged; menu = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.85f), GUI.Canvas, Anchor.Center) { MinSize = new Point(GameMain.GraphicsHeight, 0) }); var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.97f, 0.95f), menu.RectTransform, Anchor.Center)) { RelativeSpacing = 0.02f, Stretch = true }; //------------------------------------------------------------------------------------- //Top row //------------------------------------------------------------------------------------- var topRow = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), paddedFrame.RectTransform)) { Stretch = true }; var title = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.33f), topRow.RectTransform), TextManager.Get("JoinServer"), font: GUI.LargeFont) { Padding = Vector4.Zero, ForceUpperCase = true, AutoScale = true }; var infoHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.33f), topRow.RectTransform), isHorizontal: true) { RelativeSpacing = 0.05f, Stretch = true }; var clientNameHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), infoHolder.RectTransform)) { RelativeSpacing = 0.05f }; new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), clientNameHolder.RectTransform), TextManager.Get("YourName")); clientNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.5f), clientNameHolder.RectTransform), "") { Text = GameMain.Config.DefaultPlayerName, MaxTextLength = Client.MaxNameLength, OverflowClip = true }; if (string.IsNullOrEmpty(clientNameBox.Text)) { clientNameBox.Text = SteamManager.GetUsername(); } var ipBoxHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), infoHolder.RectTransform)) { RelativeSpacing = 0.05f }; new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), ipBoxHolder.RectTransform), TextManager.Get("ServerIP")); ipBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.5f), ipBoxHolder.RectTransform), ""); ipBox.OnTextChanged += (textBox, text) => { joinButton.Enabled = !string.IsNullOrEmpty(text); return true; }; ipBox.OnSelected += (sender, key) => { if (sender.UserData is ServerInfo) { sender.Text = ""; sender.UserData = null; } }; //------------------------------------------------------------------------------------- // Bottom row //------------------------------------------------------------------------------------- var bottomRow = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f - topRow.RectTransform.RelativeSize.Y), paddedFrame.RectTransform, Anchor.CenterRight)) { Stretch = true }; var serverListHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), bottomRow.RectTransform), isHorizontal: true) { Stretch = true }; // filters ------------------------------------------- var filters = new GUIFrame(new RectTransform(new Vector2(0.25f, 1.0f), serverListHolder.RectTransform, Anchor.Center), style: null) { Color = new Color(12, 14, 15, 255) * 0.5f, OutlineColor = Color.Black }; var filterToggle = new GUIButton(new RectTransform(new Vector2(0.02f, 1.0f), serverListHolder.RectTransform, Anchor.CenterRight) { MinSize = new Point(20, 0) }, style: "UIToggleButton") { OnClicked = (btn, userdata) => { filters.RectTransform.RelativeSize = new Vector2(0.25f, 1.0f); filters.Visible = !filters.Visible; filters.IgnoreLayoutGroups = !filters.Visible; serverListHolder.Recalculate(); btn.Children.ForEach(c => c.SpriteEffects = !filters.Visible ? SpriteEffects.None : SpriteEffects.FlipHorizontally); return true; } }; filterToggle.Children.ForEach(c => c.SpriteEffects = SpriteEffects.FlipHorizontally); var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.99f), filters.RectTransform, Anchor.Center)) { Stretch = true, RelativeSpacing = 0.015f }; var filterTitle = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), filterContainer.RectTransform), TextManager.Get("FilterServers"), font: GUI.LargeFont) { Padding = Vector4.Zero, AutoScale = true }; float elementHeight = 0.05f; var searchHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, elementHeight), filterContainer.RectTransform), isHorizontal: true) { Stretch = true }; var searchTitle = new GUITextBlock(new RectTransform(new Vector2(0.001f, 1.0f), searchHolder.RectTransform), TextManager.Get("Search") + "..."); searchBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 1.0f), searchHolder.RectTransform), ""); searchBox.OnSelected += (sender, userdata) => { searchTitle.Visible = false; }; searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = true; }; searchBox.OnTextChanged += (txtBox, txt) => { FilterServers(); return true; }; var filterHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), filterContainer.RectTransform)) { RelativeSpacing = 0.005f }; List filterTextList = new List(); filterPassword = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filterHolder.RectTransform), TextManager.Get("FilterPassword")) { ToolTip = TextManager.Get("FilterPassword"), OnSelected = (tickBox) => { FilterServers(); return true; } }; filterTextList.Add(filterPassword.TextBlock); filterIncompatible = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filterHolder.RectTransform), TextManager.Get("FilterIncompatibleServers")) { ToolTip = TextManager.Get("FilterIncompatibleServers"), OnSelected = (tickBox) => { FilterServers(); return true; } }; filterTextList.Add(filterIncompatible.TextBlock); filterFull = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filterHolder.RectTransform), TextManager.Get("FilterFullServers")) { ToolTip = TextManager.Get("FilterFullServers"), OnSelected = (tickBox) => { FilterServers(); return true; } }; filterTextList.Add(filterFull.TextBlock); filterEmpty = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filterHolder.RectTransform), TextManager.Get("FilterEmptyServers")) { ToolTip = TextManager.Get("FilterEmptyServers"), OnSelected = (tickBox) => { FilterServers(); return true; } }; filterTextList.Add(filterEmpty.TextBlock); filterContainer.RectTransform.SizeChanged += () => { filterContainer.RectTransform.RecalculateChildren(true, true); filterTextList.ForEach(t => t.Text = t.ToolTip); GUITextBlock.AutoScaleAndNormalize(filterTextList); if (filterTextList[0].TextScale < 0.8f) { filterTextList.ForEach(t => t.TextScale = 1.0f); filterTextList.ForEach(t => t.Text = ToolBox.LimitString(t.Text, t.Font, (int)(filterContainer.Rect.Width * 0.8f))); } }; // server list --------------------------------------------------------------------- var serverListContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), serverListHolder.RectTransform)) { Stretch = true }; labelHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.99f, 0.05f), serverListContainer.RectTransform) { MinSize = new Point(0, 15) }, isHorizontal: true) { Stretch = true }; for (int i = 0; i < columnRelativeWidth.Length; i++) { var btn = new GUIButton(new RectTransform(new Vector2(columnRelativeWidth[i], 1.0f), labelHolder.RectTransform), text: TextManager.Get(columnLabel[i]), textAlignment: Alignment.Center, style: null) { Color = new Color(12, 14, 15, 255) * 0.5f, HoverColor = new Color(12, 14, 15, 255) * 2.5f, SelectedColor = Color.Gray * 0.7f, PressedColor = Color.Gray * 0.7f, OutlineColor = Color.Black, ToolTip = TextManager.Get(columnLabel[i]), ForceUpperCase = true, UserData = columnLabel[i], OnClicked = SortList }; labelTexts.Add(btn.TextBlock); new GUIImage(new RectTransform(new Vector2(0.5f, 0.3f), btn.RectTransform, Anchor.BottomCenter, scaleBasis: ScaleBasis.BothHeight), style: "GUIButtonVerticalArrow", scaleToFit: true) { CanBeFocused = false, UserData = "arrowup", Visible = false }; new GUIImage(new RectTransform(new Vector2(0.5f, 0.3f), btn.RectTransform, Anchor.BottomCenter, scaleBasis: ScaleBasis.BothHeight), style: "GUIButtonVerticalArrow", scaleToFit: true) { CanBeFocused = false, UserData = "arrowdown", SpriteEffects = SpriteEffects.FlipVertically, Visible = false }; } serverList = new GUIListBox(new RectTransform(new Vector2(1.0f, 1.0f), serverListContainer.RectTransform, Anchor.Center)) { ScrollBarVisible = true, OnSelected = (btn, obj) => { if (obj is ServerInfo serverInfo) { joinButton.Enabled = true; ipBox.UserData = serverInfo; ipBox.Text = serverInfo.ServerName; if (!serverPreview.Visible) { serverPreview.RectTransform.RelativeSize = new Vector2(0.3f, 1.0f); serverPreviewToggleButton.Visible = true; serverPreviewToggleButton.IgnoreLayoutGroups = false; serverPreview.Visible = true; serverPreview.IgnoreLayoutGroups = false; serverListHolder.Recalculate(); } serverInfo.CreatePreviewWindow(serverPreview); btn.Children.ForEach(c => c.SpriteEffects = serverPreview.Visible ? SpriteEffects.None : SpriteEffects.FlipHorizontally); } return true; } }; //server preview panel -------------------------------------------------- serverPreviewToggleButton = new GUIButton(new RectTransform(new Vector2(0.02f, 1.0f), serverListHolder.RectTransform, Anchor.CenterRight) { MinSize = new Point(20, 0) }, style: "UIToggleButton") { Visible = false, OnClicked = (btn, userdata) => { serverPreview.RectTransform.RelativeSize = new Vector2(0.25f, 1.0f); serverPreview.Visible = !serverPreview.Visible; serverPreview.IgnoreLayoutGroups = !serverPreview.Visible; serverListHolder.Recalculate(); btn.Children.ForEach(c => c.SpriteEffects = serverPreview.Visible ? SpriteEffects.None : SpriteEffects.FlipHorizontally); return true; } }; serverPreview = new GUIFrame(new RectTransform(new Vector2(0.3f, 1.0f), serverListHolder.RectTransform, Anchor.Center), style: null) { Color = new Color(12, 14, 15, 255) * 0.5f, OutlineColor = Color.Black, IgnoreLayoutGroups = true, Visible = false }; // Spacing new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), bottomRow.RectTransform), style: null); var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.075f), bottomRow.RectTransform, Anchor.Center), isHorizontal: true) { RelativeSpacing = 0.02f, Stretch = true }; GUIButton button = new GUIButton(new RectTransform(new Vector2(0.25f, 0.9f), buttonContainer.RectTransform), TextManager.Get("Back"), style: "GUIButtonLarge") { OnClicked = GameMain.MainMenuScreen.ReturnToMainMenu }; new GUIButton(new RectTransform(new Vector2(0.25f, 0.9f), buttonContainer.RectTransform), TextManager.Get("ServerListRefresh"), style: "GUIButtonLarge") { OnClicked = (btn, userdata) => { RefreshServers(); return true; } }; /*var directJoinButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.9f), buttonContainer.RectTransform), TextManager.Get("serverlistdirectjoin"), style: "GUIButtonLarge") { OnClicked = (btn, userdata) => { ShowDirectJoinPrompt(); return true; } };*/ joinButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.9f), buttonContainer.RectTransform), TextManager.Get("ServerListJoin"), style: "GUIButtonLarge") { OnClicked = (btn, userdata) => { if (ipBox.UserData is ServerInfo selectedServer) { JoinServer(selectedServer.IP + ":" + selectedServer.Port, selectedServer.ServerName); } else if (!string.IsNullOrEmpty(ipBox.Text)) { JoinServer(ipBox.Text, ""); } return true; }, Enabled = false }; //-------------------------------------------------------- bottomRow.Recalculate(); serverListHolder.Recalculate(); serverListContainer.Recalculate(); labelHolder.RectTransform.MaxSize = new Point(serverList.Content.Rect.Width, int.MaxValue); labelHolder.Recalculate(); serverList.Content.RectTransform.SizeChanged += () => { labelHolder.RectTransform.MaxSize = new Point(serverList.Content.Rect.Width, int.MaxValue); labelHolder.Recalculate(); foreach (GUITextBlock labelText in labelTexts) { labelText.Text = ToolBox.LimitString(labelText.ToolTip, labelText.Font, labelText.Rect.Width); } }; button.SelectedColor = button.Color; refreshDisableTimer = DateTime.Now; } private void OnResolutionChanged() { menu.RectTransform.MinSize = new Point(GameMain.GraphicsHeight, 0); labelHolder.RectTransform.MaxSize = new Point(serverList.Content.Rect.Width, int.MaxValue); foreach (GUITextBlock labelText in labelTexts) { labelText.Text = ToolBox.LimitString(labelText.ToolTip, labelText.Font, labelText.Rect.Width); } } private bool SortList(GUIButton button, object obj) { if (!(obj is string sortBy)) { return false; } SortList(sortBy, toggle: true); return true; } private void SortList(string sortBy, bool toggle) { GUIButton button = labelHolder.GetChildByUserData(sortBy) as GUIButton; if (button == null) { return; } sortedBy = sortBy; var arrowUp = button.GetChildByUserData("arrowup"); var arrowDown = button.GetChildByUserData("arrowdown"); //disable arrow buttons in other labels foreach (var child in button.Parent.Children) { if (child != button) { child.GetChildByUserData("arrowup").Visible = false; child.GetChildByUserData("arrowdown").Visible = false; } } bool ascending = arrowUp.Visible; if (toggle) { ascending = !ascending; } arrowUp.Visible = ascending; arrowDown.Visible = !ascending; serverList.Content.RectTransform.SortChildren((c1, c2) => { ServerInfo s1 = c1.GUIComponent.UserData as ServerInfo; ServerInfo s2 = c2.GUIComponent.UserData as ServerInfo; switch (sortBy) { case "ServerListCompatible": bool? s1Compatible = NetworkMember.IsCompatible(GameMain.Version.ToString(), s1.GameVersion); if (!s1.ContentPackageHashes.Any()) { s1Compatible = null; } if (s1Compatible.HasValue) { s1Compatible = s1Compatible.Value && s1.ContentPackagesMatch(GameMain.SelectedPackages); }; bool? s2Compatible = NetworkMember.IsCompatible(GameMain.Version.ToString(), s2.GameVersion); if (!s2.ContentPackageHashes.Any()) { s2Compatible = null; } if (s2Compatible.HasValue) { s2Compatible = s2Compatible.Value && s2.ContentPackagesMatch(GameMain.SelectedPackages); }; //convert to int to make sorting easier //1 Compatible //0 Unknown //-1 Incompatible int s1CompatibleInt = s1Compatible.HasValue ? (s1Compatible.Value ? 1 : -1) : 0; int s2CompatibleInt = s2Compatible.HasValue ? (s2Compatible.Value ? 1 : -1) : 0; return s2CompatibleInt.CompareTo(s1CompatibleInt) * (ascending ? 1 : -1); case "ServerListHasPassword": if (s1.HasPassword == s2.HasPassword) { return 0; } return (s1.HasPassword ? 1 : -1) * (ascending ? 1 : -1); case "ServerListName": return s1.ServerName.CompareTo(s2.ServerName) * (ascending ? 1 : -1); case "ServerListRoundStarted": if (s1.GameStarted == s2.GameStarted) { return 0; } return (s1.GameStarted ? 1 : -1) * (ascending ? 1 : -1); case "ServerListPlayers": return s2.PlayerCount.CompareTo(s1.PlayerCount) * (ascending ? 1 : -1); case "ServerListPing": return s2.Ping.CompareTo(s1.Ping) * (ascending ? 1 : -1); default: return 0; } }); } public override void Select() { base.Select(); RefreshServers(); } private void FilterServers() { serverList.Content.RemoveChild(serverList.Content.FindChild("noresults")); foreach (GUIComponent child in serverList.Content.Children) { if (!(child.UserData is ServerInfo)) continue; ServerInfo serverInfo = (ServerInfo)child.UserData; Version remoteVersion = null; if (!string.IsNullOrEmpty(serverInfo.GameVersion)) { Version.TryParse(serverInfo.GameVersion, out remoteVersion); } bool incompatible = (!serverInfo.ContentPackageHashes.Any() && serverInfo.ContentPackagesMatch(GameMain.Config.SelectedContentPackages)) || (remoteVersion != null && !NetworkMember.IsCompatible(GameMain.Version, remoteVersion)); child.Visible = serverInfo.ServerName.ToLowerInvariant().Contains(searchBox.Text.ToLowerInvariant()) && (!filterPassword.Selected || !serverInfo.HasPassword) && (!filterIncompatible.Selected || !incompatible) && (!filterFull.Selected || serverInfo.PlayerCount < serverInfo.MaxPlayers) && (!filterEmpty.Selected || serverInfo.PlayerCount > 0); } if (serverList.Content.Children.All(c => !c.Visible)) { new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), serverList.Content.RectTransform), TextManager.Get("NoMatchingServers")) { UserData = "noresults" }; } serverList.UpdateScrollBarSize(); } /*private void ShowDirectJoinPrompt() { var msgBox = new GUIMessageBox(TextManager.Get("ServerListDirectJoin"), "", new string[] { TextManager.Get("OK"), TextManager.Get("Cancel") }, relativeSize: new Vector2(0.25f, 0.2f), minSize: new Point(400, 150)); var content = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.3f), msgBox.InnerFrame.RectTransform, Anchor.Center) { MinSize = new Point(0, 50) }) { IgnoreLayoutGroups = true, Stretch = true, RelativeSpacing = 0.05f }; new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform), TextManager.Get("ServerIP")); var ipBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform)); var okButton = msgBox.Buttons[0]; okButton.Enabled = false; okButton.OnClicked = (btn, userdata) => { JoinServer(ipBox.Text, ""); msgBox.Close(); return true; }; var cancelButton = msgBox.Buttons[1]; cancelButton.OnClicked = msgBox.Close; ipBox.OnTextChanged += (textBox, text) => { okButton.Enabled = !string.IsNullOrEmpty(text); return true; }; }*/ private void RefreshServers() { if (waitingForRefresh) { return; } serverList.ClearChildren(); serverPreview.ClearChildren(); joinButton.Enabled = false; ipBox.UserData = null; ipBox.Text = ""; new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), serverList.Content.RectTransform), TextManager.Get("RefreshingServerList"), textAlignment: Alignment.Center) { CanBeFocused = false }; CoroutineManager.StartCoroutine(WaitForRefresh()); } private IEnumerable WaitForRefresh() { waitingForRefresh = true; if (refreshDisableTimer > DateTime.Now) { yield return new WaitForSeconds((float)(refreshDisableTimer - DateTime.Now).TotalSeconds); } if (GameMain.Config.UseSteamMatchmaking) { serverList.ClearChildren(); if (!SteamManager.GetServers(AddToServerList, UpdateServerInfo, ServerQueryFinished)) { serverList.ClearChildren(); new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), serverList.Content.RectTransform), TextManager.Get("ServerListNoSteamConnection"), textAlignment: Alignment.Center) { CanBeFocused = false }; } } else { CoroutineManager.StartCoroutine(SendMasterServerRequest()); waitingForRefresh = false; } refreshDisableTimer = DateTime.Now + AllowedRefreshInterval; yield return CoroutineStatus.Success; } private void UpdateServerList(string masterServerData) { serverList.ClearChildren(); if (masterServerData.Substring(0, 5).ToLowerInvariant() == "error") { DebugConsole.ThrowError("Error while connecting to master server (" + masterServerData + ")!"); return; } string[] lines = masterServerData.Split('\n'); List serverInfos = new List(); for (int i = 0; i < lines.Length; i++) { string[] arguments = lines[i].Split('|'); if (arguments.Length < 3) continue; string ip = arguments[0]; string port = arguments[1]; string serverName = arguments[2]; bool gameStarted = arguments.Length > 3 && arguments[3] == "1"; string currPlayersStr = arguments.Length > 4 ? arguments[4] : ""; string maxPlayersStr = arguments.Length > 5 ? arguments[5] : ""; bool hasPassWord = arguments.Length > 6 && arguments[6] == "1"; string gameVersion = arguments.Length > 7 ? arguments[7] : ""; string contentPackageNames = arguments.Length > 8 ? arguments[8] : ""; string contentPackageHashes = arguments.Length > 9 ? arguments[9] : ""; int.TryParse(currPlayersStr, out int playerCount); int.TryParse(maxPlayersStr, out int maxPlayers); var serverInfo = new ServerInfo() { IP = ip, Port = port, ServerName = serverName, GameStarted = gameStarted, PlayerCount = playerCount, MaxPlayers = maxPlayers, HasPassword = hasPassWord, GameVersion = gameVersion }; foreach (string contentPackageName in contentPackageNames.Split(',')) { if (string.IsNullOrEmpty(contentPackageName)) continue; serverInfo.ContentPackageNames.Add(contentPackageName); } foreach (string contentPackageHash in contentPackageHashes.Split(',')) { if (string.IsNullOrEmpty(contentPackageHash)) continue; serverInfo.ContentPackageHashes.Add(contentPackageHash); } serverInfos.Add(serverInfo); } serverList.Content.ClearChildren(); if (serverInfos.Count() == 0) { new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), serverList.Content.RectTransform), TextManager.Get("NoServers"), textAlignment: Alignment.Center) { CanBeFocused = false }; return; } foreach (ServerInfo serverInfo in serverInfos) { AddToServerList(serverInfo); } } private void AddToServerList(ServerInfo serverInfo) { var serverFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.06f), serverList.Content.RectTransform) { MinSize = new Point(0, 35) }, style: "InnerFrame", color: Color.White * 0.5f) { UserData = serverInfo }; new GUILayoutGroup(new RectTransform(new Vector2(0.98f, 1.0f), serverFrame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true, //RelativeSpacing = 0.02f }; UpdateServerInfo(serverInfo); SortList(sortedBy, toggle: false); FilterServers(); } private void UpdateServerInfo(ServerInfo serverInfo) { var serverFrame = serverList.Content.FindChild(serverInfo); if (serverFrame == null) return; var serverContent = serverFrame.Children.First() as GUILayoutGroup; serverContent.ClearChildren(); var compatibleBox = new GUITickBox(new RectTransform(new Vector2(columnRelativeWidth[0], 0.9f), serverContent.RectTransform, Anchor.Center), label: "") { Enabled = false, Selected = serverInfo.GameVersion == GameMain.Version.ToString() && serverInfo.ContentPackagesMatch(GameMain.SelectedPackages), UserData = "compatible" }; var passwordBox = new GUITickBox(new RectTransform(new Vector2(columnRelativeWidth[1], 0.5f), serverContent.RectTransform, Anchor.Center), label: "", style: "GUIServerListPasswordTickBox") { ToolTip = TextManager.Get((serverInfo.HasPassword) ? "ServerListHasPassword" : "FilterPassword"), Selected = serverInfo.HasPassword, Enabled = false, UserData = "password" }; var serverName = new GUITextBlock(new RectTransform(new Vector2(columnRelativeWidth[2] * 1.1f, 1.0f), serverContent.RectTransform), serverInfo.ServerName, style: "GUIServerListTextBox"); new GUITickBox(new RectTransform(new Vector2(columnRelativeWidth[3], 0.9f), serverContent.RectTransform, Anchor.Center), label: "") { ToolTip = TextManager.Get((serverInfo.GameStarted) ? "ServerListRoundStarted" : "ServerListRoundNotStarted"), Selected = serverInfo.GameStarted, Enabled = false }; var serverPlayers = new GUITextBlock(new RectTransform(new Vector2(columnRelativeWidth[4], 1.0f), serverContent.RectTransform), serverInfo.PlayerCount + "/" + serverInfo.MaxPlayers, style: "GUIServerListTextBox", textAlignment: Alignment.Right) { ToolTip = TextManager.Get("ServerListPlayers") }; var serverPingText = new GUITextBlock(new RectTransform(new Vector2(columnRelativeWidth[5], 1.0f), serverContent.RectTransform), "?", style: "GUIServerListTextBox", textColor: Color.White * 0.5f, textAlignment: Alignment.Right) { ToolTip = TextManager.Get("ServerListPing") }; if (serverInfo.PingChecked) { serverPingText.Text = serverInfo.Ping > -1 ? serverInfo.Ping.ToString() : "?"; serverPingText.TextColor = GetPingTextColor(serverInfo.Ping); } else if (!string.IsNullOrEmpty(serverInfo.IP)) { try { GetServerPing(serverInfo, serverPingText); } catch (NullReferenceException ex) { DebugConsole.ThrowError("Ping is null", ex); } } if (GameMain.Config.UseSteamMatchmaking && serverInfo.RespondedToSteamQuery.HasValue && serverInfo.RespondedToSteamQuery.Value == false) { string toolTip = TextManager.Get("ServerListNoSteamQueryResponse"); compatibleBox.Selected = false; serverContent.Children.ForEach(c => c.ToolTip = toolTip); serverName.TextColor *= 0.8f; serverPlayers.TextColor *= 0.8f; } else if (string.IsNullOrEmpty(serverInfo.GameVersion) || !serverInfo.ContentPackageHashes.Any()) { compatibleBox.Selected = false; new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.8f), compatibleBox.Box.RectTransform, Anchor.Center), " ? ", Color.Yellow * 0.85f, textAlignment: Alignment.Center) { ToolTip = TextManager.Get(string.IsNullOrEmpty(serverInfo.GameVersion) ? "ServerListUnknownVersion" : "ServerListUnknownContentPackage") }; } else if (!compatibleBox.Selected) { string toolTip = ""; if (serverInfo.GameVersion != GameMain.Version.ToString()) toolTip = TextManager.GetWithVariable("ServerListIncompatibleVersion", "[version]", serverInfo.GameVersion); for (int i = 0; i < serverInfo.ContentPackageNames.Count; i++) { if (!GameMain.SelectedPackages.Any(cp => cp.MD5hash.Hash == serverInfo.ContentPackageHashes[i])) { if (toolTip != "") toolTip += "\n"; toolTip += TextManager.GetWithVariables("ServerListIncompatibleContentPackage", new string[2] { "[contentpackage]", "[hash]" }, new string[2] { serverInfo.ContentPackageNames[i], Md5Hash.GetShortHash(serverInfo.ContentPackageHashes[i]) }); } } serverContent.Children.ForEach(c => c.ToolTip = toolTip); serverName.TextColor *= 0.5f; serverPlayers.TextColor *= 0.5f; } serverContent.Recalculate(); SortList(sortedBy, toggle: false); FilterServers(); } private void ServerQueryFinished() { if (serverList.Content.Children.All(c => !c.Visible)) { new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), serverList.Content.RectTransform), TextManager.Get("NoMatchingServers")) { UserData = "noresults" }; } waitingForRefresh = false; } private IEnumerable SendMasterServerRequest() { RestClient client = null; try { client = new RestClient(NetConfig.MasterServerUrl); } catch (Exception e) { DebugConsole.ThrowError("Error while connecting to master server", e); } if (client == null) yield return CoroutineStatus.Success; var request = new RestRequest("masterserver2.php", Method.GET); request.AddParameter("gamename", "barotrauma"); request.AddParameter("action", "listservers"); // execute the request masterServerResponded = false; var restRequestHandle = client.ExecuteAsync(request, response => MasterServerCallBack(response)); DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 8); while (!masterServerResponded) { if (DateTime.Now > timeOut) { serverList.ClearChildren(); restRequestHandle.Abort(); new GUIMessageBox(TextManager.Get("MasterServerErrorLabel"), TextManager.Get("MasterServerTimeOutError")); yield return CoroutineStatus.Success; } yield return CoroutineStatus.Running; } if (masterServerResponse.ErrorException != null) { serverList.ClearChildren(); new GUIMessageBox(TextManager.Get("MasterServerErrorLabel"), TextManager.GetWithVariable("MasterServerErrorException", "[error]", masterServerResponse.ErrorException.ToString())); } else if (masterServerResponse.StatusCode != System.Net.HttpStatusCode.OK) { serverList.ClearChildren(); switch (masterServerResponse.StatusCode) { case System.Net.HttpStatusCode.NotFound: new GUIMessageBox(TextManager.Get("MasterServerErrorLabel"), TextManager.GetWithVariable("MasterServerError404", "[masterserverurl]", NetConfig.MasterServerUrl)); break; case System.Net.HttpStatusCode.ServiceUnavailable: new GUIMessageBox(TextManager.Get("MasterServerErrorLabel"), TextManager.Get("MasterServerErrorUnavailable")); break; default: new GUIMessageBox(TextManager.Get("MasterServerErrorLabel"), TextManager.GetWithVariables("MasterServerErrorDefault", new string[2] { "[statuscode]", "[statusdescription]" }, new string[2] { masterServerResponse.StatusCode.ToString(), masterServerResponse.StatusDescription })); break; } } else { UpdateServerList(masterServerResponse.Content); } yield return CoroutineStatus.Success; } private void MasterServerCallBack(IRestResponse response) { masterServerResponse = response; masterServerResponded = true; } private bool JoinServer(string ip, string serverName) { if (string.IsNullOrWhiteSpace(clientNameBox.Text)) { clientNameBox.Flash(); return false; } GameMain.Config.DefaultPlayerName = clientNameBox.Text; GameMain.Config.SaveNewPlayerConfig(); CoroutineManager.StartCoroutine(ConnectToServer(ip, serverName)); return true; } private IEnumerable ConnectToServer(string ip, string serverName) { #if !DEBUG try { #endif GameMain.Client = new GameClient(clientNameBox.Text, ip, serverName); #if !DEBUG } catch (Exception e) { DebugConsole.ThrowError("Failed to start the client", e); } #endif yield return CoroutineStatus.Success; } public void GetServerPing(ServerInfo serverInfo, GUITextBlock serverPingText) { serverInfo.PingChecked = false; serverInfo.Ping = -1; var pingThread = new Thread(() => { PingServer(serverInfo, 1000); }) { IsBackground = true }; pingThread.Start(); CoroutineManager.StartCoroutine(UpdateServerPingText(serverInfo, serverPingText, 1000)); } private IEnumerable UpdateServerPingText(ServerInfo serverInfo, GUITextBlock serverPingText, int timeOut) { DateTime timeOutTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: timeOut); while (DateTime.Now < timeOutTime) { if (serverInfo.PingChecked) { if (serverInfo.Ping != -1) { serverPingText.TextColor = GetPingTextColor(serverInfo.Ping); } serverPingText.Text = serverInfo.Ping > -1 ? serverInfo.Ping.ToString() : "?"; yield return CoroutineStatus.Success; } yield return CoroutineStatus.Running; } yield return CoroutineStatus.Success; } private Color GetPingTextColor(int ping) { if (ping < 0) { return Color.DarkRed; } return ToolBox.GradientLerp(ping / 200.0f, Color.LightGreen, Color.Yellow * 0.8f, Color.Red * 0.75f); } public void PingServer(ServerInfo serverInfo, int timeOut) { if (serverInfo?.IP == null) { serverInfo.PingChecked = true; serverInfo.Ping = -1; return; } long rtt = -1; IPAddress address = IPAddress.Parse(serverInfo.IP); if (address != null) { //don't attempt to ping if the address is IPv6 and it's not supported if (address.AddressFamily != AddressFamily.InterNetworkV6 || Socket.OSSupportsIPv6) { Ping ping = new Ping(); byte[] buffer = new byte[32]; try { PingReply pingReply = ping.Send(address, timeOut, buffer, new PingOptions(128, true)); if (pingReply != null) { switch (pingReply.Status) { case IPStatus.Success: rtt = pingReply.RoundtripTime; break; default: rtt = -1; break; } } } catch (PingException ex) { string errorMsg = "Failed to ping a server (" + serverInfo.ServerName + ", " + serverInfo.IP + ") - " + (ex?.InnerException?.Message ?? ex.Message); GameAnalyticsManager.AddErrorEventOnce("ServerListScreen.PingServer:PingException" + serverInfo.IP, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, errorMsg); #if DEBUG DebugConsole.NewMessage(errorMsg, Color.Red); #endif } } } serverInfo.PingChecked = true; serverInfo.Ping = (int)rtt; } public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch) { graphics.Clear(Color.CornflowerBlue); GameMain.TitleScreen.DrawLoadingText = false; GameMain.MainMenuScreen.DrawBackground(graphics, spriteBatch); spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, GameMain.ScissorTestEnable); GUI.Draw(Cam, spriteBatch); spriteBatch.End(); } public override void AddToGUIUpdateList() { menu.AddToGUIUpdateList(); } } }