2052 lines
89 KiB
C#
2052 lines
89 KiB
C#
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.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.NetworkInformation;
|
|
using System.Net.Sockets;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Xml.Linq;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
class ServerListScreen : Screen
|
|
{
|
|
//how often the client is allowed to refresh servers
|
|
private TimeSpan AllowedRefreshInterval = new TimeSpan(0, 0, 3);
|
|
|
|
private GUIFrame menu;
|
|
|
|
private GUIListBox serverList;
|
|
private GUIFrame serverPreview;
|
|
|
|
private GUIButton joinButton;
|
|
private ServerInfo selectedServer;
|
|
|
|
//friends list
|
|
private GUILayoutGroup friendsButtonHolder;
|
|
|
|
private GUIButton friendsDropdownButton;
|
|
private GUIListBox friendsDropdown;
|
|
|
|
private class FriendInfo
|
|
{
|
|
public UInt64 SteamID;
|
|
public string Name;
|
|
public Sprite Sprite;
|
|
public string StatusText;
|
|
public bool PlayingThisGame;
|
|
public bool PlayingAnotherGame;
|
|
public string ConnectName;
|
|
public string ConnectEndpoint;
|
|
public UInt64 ConnectLobby;
|
|
|
|
public bool InServer
|
|
{
|
|
get
|
|
{
|
|
return PlayingThisGame && !string.IsNullOrWhiteSpace(StatusText) && (!string.IsNullOrWhiteSpace(ConnectEndpoint) || ConnectLobby != 0);
|
|
}
|
|
}
|
|
}
|
|
private List<FriendInfo> friendsList;
|
|
private GUIFrame friendPopup;
|
|
private double friendsListUpdateTime;
|
|
|
|
//favorite servers/history
|
|
private const string recentServersFile = "Data/recentservers.xml";
|
|
private const string favoriteServersFile = "Data/favoriteservers.xml";
|
|
private List<ServerInfo> favoriteServers;
|
|
private List<ServerInfo> recentServers;
|
|
|
|
private readonly HashSet<string> activePings = new HashSet<string>();
|
|
|
|
private enum ServerListTab
|
|
{
|
|
All = 0,
|
|
Favorites = 1,
|
|
Recent = 2
|
|
};
|
|
private ServerListTab selectedTab;
|
|
private ServerListTab SelectedTab
|
|
{
|
|
get { return selectedTab; }
|
|
set
|
|
{
|
|
if (selectedTab == value) { return; }
|
|
var tabVals = Enum.GetValues(typeof(ServerListTab));
|
|
for (int i = 0; i < tabVals.Length; i++)
|
|
{
|
|
tabButtons[i].Selected = false;
|
|
}
|
|
tabButtons[(int)value].Selected = true;
|
|
selectedTab = value;
|
|
FilterServers();
|
|
}
|
|
}
|
|
private GUIButton[] tabButtons;
|
|
|
|
private static Sprite[] playStyleBanners;
|
|
//server playstyle and tags
|
|
public static Sprite[] PlayStyleBanners
|
|
{
|
|
get
|
|
{
|
|
if (playStyleBanners == null)
|
|
{
|
|
LoadPlayStyleBanners();
|
|
}
|
|
return playStyleBanners;
|
|
}
|
|
}
|
|
public static Color[] PlayStyleColors
|
|
{
|
|
get; private set;
|
|
}
|
|
|
|
public GUITextBox ClientNameBox { get; private set; }
|
|
|
|
public static Dictionary<string, Sprite> PlayStyleIcons
|
|
{
|
|
get; private set;
|
|
}
|
|
public static Dictionary<string, Color> PlayStyleIconColors
|
|
{
|
|
get; private set;
|
|
}
|
|
|
|
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 GUILayoutGroup labelHolder;
|
|
private readonly List<GUITextBlock> labelTexts = new List<GUITextBlock>();
|
|
|
|
//filters
|
|
private GUITextBox searchBox;
|
|
private GUITickBox filterSameVersion;
|
|
private GUITickBox filterPassword;
|
|
private GUITickBox filterIncompatible;
|
|
private GUITickBox filterFull;
|
|
private GUITickBox filterEmpty;
|
|
private GUITickBox filterWhitelisted;
|
|
private GUITickBox filterFriendlyFire;
|
|
private GUITickBox filterKarma;
|
|
private GUITickBox filterTraitor;
|
|
private GUITickBox filterModded;
|
|
private GUITickBox filterVoip;
|
|
private List<GUITickBox> playStyleTickBoxes;
|
|
private List<GUITickBox> gameModeTickBoxes;
|
|
|
|
private string sortedBy;
|
|
|
|
private GUIButton serverPreviewToggleButton;
|
|
|
|
//a timer for preventing the client from spamming the refresh button faster than AllowedRefreshInterval
|
|
private DateTime refreshDisableTimer;
|
|
private bool waitingForRefresh;
|
|
|
|
private bool steamPingInfoReady;
|
|
|
|
private const float sidebarWidth = 0.2f;
|
|
public ServerListScreen()
|
|
{
|
|
GameMain.Instance.OnResolutionChanged += CreateUI;
|
|
CreateUI();
|
|
}
|
|
|
|
private void CreateUI()
|
|
{
|
|
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.98f, 0.98f), 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,
|
|
AutoScaleHorizontal = true
|
|
};
|
|
|
|
var infoHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.33f), topRow.RectTransform), isHorizontal: true, Anchor.BottomLeft) { RelativeSpacing = 0.01f, Stretch = false };
|
|
|
|
var clientNameHolder = new GUILayoutGroup(new RectTransform(new Vector2(sidebarWidth, 1.0f), infoHolder.RectTransform)) { RelativeSpacing = 0.05f };
|
|
|
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), clientNameHolder.RectTransform), TextManager.Get("YourName"), font: GUI.SubHeadingFont);
|
|
ClientNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.5f), clientNameHolder.RectTransform), "")
|
|
{
|
|
Text = GameMain.Config.PlayerName,
|
|
MaxTextLength = Client.MaxNameLength,
|
|
OverflowClip = true
|
|
};
|
|
|
|
if (string.IsNullOrEmpty(ClientNameBox.Text))
|
|
{
|
|
ClientNameBox.Text = SteamManager.GetUsername();
|
|
}
|
|
ClientNameBox.OnTextChanged += (textbox, text) =>
|
|
{
|
|
GameMain.Config.PlayerName = text;
|
|
return true;
|
|
};
|
|
|
|
var tabButtonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f - sidebarWidth - infoHolder.RelativeSpacing, 0.5f), infoHolder.RectTransform), isHorizontal: true);
|
|
|
|
var tabVals = Enum.GetValues(typeof(ServerListTab));
|
|
tabButtons = new GUIButton[tabVals.Length];
|
|
foreach (ServerListTab tab in tabVals)
|
|
{
|
|
tabButtons[(int)tab] = new GUIButton(new RectTransform(new Vector2(0.2f, 1.0f), tabButtonHolder.RectTransform),
|
|
TextManager.Get("ServerListTab." + tab.ToString()), style: "GUITabButton")
|
|
{
|
|
OnClicked = (btn, usrdat) =>
|
|
{
|
|
SelectedTab = tab;
|
|
return false;
|
|
}
|
|
};
|
|
}
|
|
|
|
var friendsButtonFrame = new GUIFrame(new RectTransform(new Vector2(0.31f, 2.0f), tabButtonHolder.RectTransform, Anchor.BottomRight), style: "InnerFrame")
|
|
{
|
|
IgnoreLayoutGroups = true
|
|
};
|
|
|
|
friendsButtonHolder = new GUILayoutGroup(new RectTransform(new Vector2(0.98f, 0.9f), friendsButtonFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopLeft) { RelativeSpacing = 0.01f, IsHorizontal = true };
|
|
friendsList = new List<FriendInfo>();
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
// 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, childAnchor: Anchor.CenterLeft)
|
|
{
|
|
OutlineColor = Color.Black
|
|
};
|
|
|
|
GUILayoutGroup serverListContainer = null;
|
|
GUIFrame filtersHolder = null;
|
|
GUIButton filterToggle = null;
|
|
|
|
void RecalculateHolder()
|
|
{
|
|
float listContainerSubtract = filtersHolder.Visible ? sidebarWidth : 0.0f;
|
|
listContainerSubtract += serverPreview.Visible ? sidebarWidth : 0.0f;
|
|
|
|
float toggleButtonsSubtract = 1.1f * filterToggle.Rect.Width / serverListHolder.Rect.Width;
|
|
listContainerSubtract += filterToggle.Visible ? toggleButtonsSubtract : 0.0f;
|
|
listContainerSubtract += serverPreviewToggleButton.Visible ? toggleButtonsSubtract : 0.0f;
|
|
|
|
serverListContainer.RectTransform.RelativeSize = new Vector2(1.0f - listContainerSubtract, 1.0f);
|
|
serverListHolder.Recalculate();
|
|
}
|
|
|
|
// filters -------------------------------------------
|
|
|
|
filtersHolder = new GUIFrame(new RectTransform(new Vector2(sidebarWidth, 1.0f), serverListHolder.RectTransform, Anchor.Center), style: null)
|
|
{
|
|
Color = new Color(12, 14, 15, 255) * 0.5f,
|
|
OutlineColor = Color.Black
|
|
};
|
|
|
|
float elementHeight = 0.05f;
|
|
var filterTitle = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), filtersHolder.RectTransform), TextManager.Get("FilterServers"), font: GUI.SubHeadingFont)
|
|
{
|
|
Padding = Vector4.Zero,
|
|
AutoScaleHorizontal = true,
|
|
CanBeFocused = false
|
|
};
|
|
|
|
var searchHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, elementHeight), filtersHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, elementHeight) }, 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 filters = new GUIListBox(new RectTransform(new Vector2(0.98f, 1.0f - elementHeight * 2), filtersHolder.RectTransform, Anchor.BottomLeft))
|
|
{
|
|
ScrollBarVisible = true,
|
|
Spacing = (int)(5 * GUI.Scale)
|
|
};
|
|
|
|
filterToggle = new GUIButton(new RectTransform(new Vector2(0.01f, 1.0f), serverListHolder.RectTransform)
|
|
{ MinSize = new Point(20, 0), MaxSize = new Point(int.MaxValue, (int)(150 * GUI.Scale)) },
|
|
style: "UIToggleButton")
|
|
{
|
|
OnClicked = (btn, userdata) =>
|
|
{
|
|
filtersHolder.RectTransform.RelativeSize = new Vector2(sidebarWidth, 1.0f);
|
|
filtersHolder.Visible = !filtersHolder.Visible;
|
|
filtersHolder.IgnoreLayoutGroups = !filtersHolder.Visible;
|
|
|
|
RecalculateHolder();
|
|
|
|
btn.Children.ForEach(c => c.SpriteEffects = !filtersHolder.Visible ? SpriteEffects.None : SpriteEffects.FlipHorizontally);
|
|
return true;
|
|
}
|
|
};
|
|
filterToggle.Children.ForEach(c => c.SpriteEffects = SpriteEffects.FlipHorizontally);
|
|
|
|
List<GUITextBlock> filterTextList = new List<GUITextBlock>();
|
|
|
|
filterSameVersion = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filters.Content.RectTransform), TextManager.Get("FilterSameVersion"))
|
|
{
|
|
ToolTip = TextManager.Get("FilterSameVersion"),
|
|
Selected = true,
|
|
OnSelected = (tickBox) => { FilterServers(); return true; }
|
|
};
|
|
filterTextList.Add(filterSameVersion.TextBlock);
|
|
|
|
filterPassword = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filters.Content.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), filters.Content.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), filters.Content.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), filters.Content.RectTransform), TextManager.Get("FilterEmptyServers"))
|
|
{
|
|
ToolTip = TextManager.Get("FilterEmptyServers"),
|
|
OnSelected = (tickBox) => { FilterServers(); return true; }
|
|
};
|
|
filterTextList.Add(filterEmpty.TextBlock);
|
|
|
|
filterWhitelisted = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filters.Content.RectTransform), TextManager.Get("FilterWhitelistedServers"))
|
|
{
|
|
ToolTip = TextManager.Get("FilterWhitelistedServers"),
|
|
OnSelected = (tickBox) => { FilterServers(); return true; }
|
|
};
|
|
filterTextList.Add(filterWhitelisted.TextBlock);
|
|
|
|
// Filter Tags
|
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), filters.Content.RectTransform), TextManager.Get("servertags"), font: GUI.SubHeadingFont)
|
|
{
|
|
CanBeFocused = false
|
|
};
|
|
|
|
filterKarma = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filters.Content.RectTransform), TextManager.Get("servertag.karma.true"))
|
|
{
|
|
ToolTip = TextManager.Get("servertag.karma.true"),
|
|
OnSelected = (tickBox) => { FilterServers(); return true; }
|
|
};
|
|
filterTextList.Add(filterKarma.TextBlock);
|
|
|
|
filterTraitor = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filters.Content.RectTransform), TextManager.Get("servertag.traitors.true"))
|
|
{
|
|
ToolTip = TextManager.Get("servertag.traitors.true"),
|
|
OnSelected = (tickBox) => { FilterServers(); return true; }
|
|
};
|
|
filterTextList.Add(filterTraitor.TextBlock);
|
|
|
|
filterFriendlyFire = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filters.Content.RectTransform), TextManager.Get("servertag.friendlyfire.false"))
|
|
{
|
|
ToolTip = TextManager.Get("servertag.friendlyfire.false"),
|
|
OnSelected = (tickBox) => { FilterServers(); return true; }
|
|
};
|
|
filterTextList.Add(filterFriendlyFire.TextBlock);
|
|
|
|
filterVoip = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filters.Content.RectTransform), TextManager.Get("servertag.voip.false"))
|
|
{
|
|
ToolTip = TextManager.Get("servertag.voip.false"),
|
|
OnSelected = (tickBox) => { FilterServers(); return true; }
|
|
};
|
|
filterTextList.Add(filterVoip.TextBlock);
|
|
|
|
filterModded = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filters.Content.RectTransform), TextManager.Get("servertag.modded.true"))
|
|
{
|
|
ToolTip = TextManager.Get("servertag.modded.true"),
|
|
OnSelected = (tickBox) => { FilterServers(); return true; }
|
|
};
|
|
filterTextList.Add(filterModded.TextBlock);
|
|
|
|
// Play Style Selection
|
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), filters.Content.RectTransform), TextManager.Get("ServerSettingsPlayStyle"), font: GUI.SubHeadingFont)
|
|
{
|
|
CanBeFocused = false
|
|
};
|
|
|
|
playStyleTickBoxes = new List<GUITickBox>();
|
|
foreach (PlayStyle playStyle in Enum.GetValues(typeof(PlayStyle)))
|
|
{
|
|
var selectionTick = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filters.Content.RectTransform), TextManager.Get("servertag." + playStyle))
|
|
{
|
|
ToolTip = TextManager.Get("servertag." + playStyle),
|
|
Selected = true,
|
|
OnSelected = (tickBox) => { FilterServers(); return true; },
|
|
UserData = playStyle
|
|
};
|
|
playStyleTickBoxes.Add(selectionTick);
|
|
filterTextList.Add(selectionTick.TextBlock);
|
|
}
|
|
|
|
// Game mode Selection
|
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), filters.Content.RectTransform), TextManager.Get("gamemode")) { CanBeFocused = false };
|
|
|
|
gameModeTickBoxes = new List<GUITickBox>();
|
|
foreach (GameModePreset mode in GameModePreset.List)
|
|
{
|
|
if (mode.IsSinglePlayer) continue;
|
|
|
|
var selectionTick = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), filters.Content.RectTransform), mode.Name)
|
|
{
|
|
ToolTip = mode.Name,
|
|
Selected = true,
|
|
OnSelected = (tickBox) => { FilterServers(); return true; },
|
|
UserData = mode.Identifier
|
|
};
|
|
gameModeTickBoxes.Add(selectionTick);
|
|
filterTextList.Add(selectionTick.TextBlock);
|
|
}
|
|
|
|
filters.Content.RectTransform.SizeChanged += () =>
|
|
{
|
|
filters.Content.RectTransform.RecalculateChildren(true, true);
|
|
filterTextList.ForEach(t => t.Text = t.ToolTip);
|
|
GUITextBlock.AutoScaleAndNormalize(filterTextList, defaultScale: 1.0f);
|
|
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)(filters.Content.Rect.Width * 0.8f)));
|
|
}
|
|
};
|
|
|
|
// server list ---------------------------------------------------------------------
|
|
|
|
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, childAnchor: Anchor.BottomLeft)
|
|
{
|
|
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: "GUIButtonSmall")
|
|
{
|
|
ToolTip = TextManager.Get(columnLabel[i]),
|
|
ForceUpperCase = true,
|
|
UserData = columnLabel[i],
|
|
OnClicked = SortList
|
|
};
|
|
btn.Color *= 0.5f;
|
|
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;
|
|
selectedServer = serverInfo;
|
|
if (!serverPreview.Visible)
|
|
{
|
|
serverPreview.RectTransform.RelativeSize = new Vector2(sidebarWidth, 1.0f);
|
|
serverPreviewToggleButton.Visible = true;
|
|
serverPreviewToggleButton.IgnoreLayoutGroups = false;
|
|
serverPreview.Visible = true;
|
|
serverPreview.IgnoreLayoutGroups = false;
|
|
RecalculateHolder();
|
|
}
|
|
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.01f, 1.0f), serverListHolder.RectTransform)
|
|
{ MinSize = new Point(20, 0), MaxSize = new Point(int.MaxValue, (int)(150 * GUI.Scale)) },
|
|
style: "UIToggleButton")
|
|
{
|
|
Visible = false,
|
|
OnClicked = (btn, userdata) =>
|
|
{
|
|
serverPreview.RectTransform.RelativeSize = new Vector2(0.2f, 1.0f);
|
|
serverPreview.Visible = !serverPreview.Visible;
|
|
serverPreview.IgnoreLayoutGroups = !serverPreview.Visible;
|
|
|
|
RecalculateHolder();
|
|
|
|
btn.Children.ForEach(c => c.SpriteEffects = serverPreview.Visible ? SpriteEffects.None : SpriteEffects.FlipHorizontally);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
serverPreview = new GUIFrame(new RectTransform(new Vector2(sidebarWidth, 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"))
|
|
{
|
|
OnClicked = GameMain.MainMenuScreen.ReturnToMainMenu
|
|
};
|
|
|
|
new GUIButton(new RectTransform(new Vector2(0.25f, 0.9f), buttonContainer.RectTransform),
|
|
TextManager.Get("ServerListRefresh"))
|
|
{
|
|
OnClicked = (btn, userdata) => { RefreshServers(); return true; }
|
|
};
|
|
|
|
var directJoinButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.9f), buttonContainer.RectTransform),
|
|
TextManager.Get("serverlistdirectjoin"))
|
|
{
|
|
OnClicked = (btn, userdata) => { ShowDirectJoinPrompt(); return true; }
|
|
};
|
|
|
|
joinButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.9f), buttonContainer.RectTransform),
|
|
TextManager.Get("ServerListJoin"))
|
|
{
|
|
OnClicked = (btn, userdata) =>
|
|
{
|
|
if (selectedServer != null)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(selectedServer.IP) && !string.IsNullOrWhiteSpace(selectedServer.Port) && int.TryParse(selectedServer.Port, out _))
|
|
{
|
|
JoinServer(selectedServer.IP + ":" + selectedServer.Port, selectedServer.ServerName);
|
|
}
|
|
else if (selectedServer.LobbyID != 0)
|
|
{
|
|
Steam.SteamManager.JoinLobby(selectedServer.LobbyID, true);
|
|
}
|
|
else
|
|
{
|
|
new GUIMessageBox("", TextManager.Get("ServerOffline"));
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
Enabled = false
|
|
};
|
|
|
|
buttonContainer.RectTransform.MinSize = new Point(0, (int)(buttonContainer.RectTransform.Children.Max(c => c.MinSize.Y) * 1.2f));
|
|
|
|
//--------------------------------------------------------
|
|
|
|
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);
|
|
}
|
|
RecalculateHolder();
|
|
};
|
|
|
|
button.SelectedColor = button.Color;
|
|
refreshDisableTimer = DateTime.Now;
|
|
|
|
//recent and favorite servers
|
|
ReadServerMemFromFile(recentServersFile, ref recentServers);
|
|
ReadServerMemFromFile(favoriteServersFile, ref favoriteServers);
|
|
recentServers.ForEach(s => s.Recent = true);
|
|
favoriteServers.ForEach(s => s.Favorite = true);
|
|
|
|
SelectedTab = ServerListTab.All;
|
|
tabButtons[(int)selectedTab].Selected = true;
|
|
|
|
RecalculateHolder();
|
|
}
|
|
|
|
|
|
private static void LoadPlayStyleBanners()
|
|
{
|
|
//playstyle banners
|
|
playStyleBanners = new Sprite[Enum.GetValues(typeof(PlayStyle)).Length];
|
|
PlayStyleColors = new Color[Enum.GetValues(typeof(PlayStyle)).Length];
|
|
PlayStyleIcons = new Dictionary<string, Sprite>();
|
|
PlayStyleIconColors = new Dictionary<string, Color>();
|
|
|
|
XDocument playStylesDoc = XMLExtensions.TryLoadXml("Content/UI/Server/PlayStyles.xml");
|
|
|
|
XElement rootElement = playStylesDoc.Root;
|
|
foreach (var element in rootElement.Elements())
|
|
{
|
|
switch (element.Name.ToString().ToLowerInvariant())
|
|
{
|
|
case "playstylebanner":
|
|
if (Enum.TryParse(element.GetAttributeString("identifier", ""), out PlayStyle playStyle))
|
|
{
|
|
PlayStyleBanners[(int)playStyle] = new Sprite(element, lazyLoad: true);
|
|
PlayStyleColors[(int)playStyle] = element.GetAttributeColor("color", Color.White);
|
|
}
|
|
break;
|
|
case "playstyleicon":
|
|
string identifier = element.GetAttributeString("identifier", "");
|
|
if (string.IsNullOrEmpty(identifier)) { continue; }
|
|
PlayStyleIcons[identifier] = new Sprite(element, lazyLoad: true);
|
|
PlayStyleIconColors[identifier] = element.GetAttributeColor("color", Color.White);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ReadServerMemFromFile(string file, ref List<ServerInfo> servers)
|
|
{
|
|
if (servers == null) { servers = new List<ServerInfo>(); }
|
|
|
|
if (!File.Exists(file)) { return; }
|
|
|
|
XDocument doc = XMLExtensions.TryLoadXml(file);
|
|
if (doc == null) { return; }
|
|
|
|
foreach (XElement element in doc.Root.Elements())
|
|
{
|
|
if (element.Name != "ServerInfo") { continue; }
|
|
servers.Add(ServerInfo.FromXElement(element));
|
|
}
|
|
}
|
|
|
|
private void WriteServerMemToFile(string file, List<ServerInfo> servers)
|
|
{
|
|
if (servers == null) { return; }
|
|
|
|
XDocument doc = new XDocument();
|
|
XElement rootElement = new XElement("servers");
|
|
doc.Add(rootElement);
|
|
|
|
foreach (ServerInfo info in servers)
|
|
{
|
|
rootElement.Add(info.ToXElement());
|
|
}
|
|
|
|
doc.Save(file);
|
|
}
|
|
|
|
public ServerInfo UpdateServerInfoWithServerSettings(object endpoint, ServerSettings serverSettings)
|
|
{
|
|
UInt64 steamId = 0;
|
|
string ip = ""; string port = "";
|
|
if (endpoint is UInt64 id) { steamId = id; }
|
|
else if (endpoint is string strEndpoint)
|
|
{
|
|
string[] address = strEndpoint.Split(':');
|
|
if (address.Length == 1)
|
|
{
|
|
ip = strEndpoint;
|
|
port = NetConfig.DefaultPort.ToString();
|
|
}
|
|
else
|
|
{
|
|
ip = string.Join(":", address.Take(address.Length - 1));
|
|
port = address[address.Length - 1];
|
|
}
|
|
}
|
|
|
|
bool isInfoNew = false;
|
|
ServerInfo info = serverList.Content.FindChild(d => (d.UserData is ServerInfo serverInfo) && serverInfo != null &&
|
|
(steamId != 0 ? steamId == serverInfo.OwnerID : (ip == serverInfo.IP && port == serverInfo.Port)))?.UserData as ServerInfo;
|
|
if (info == null)
|
|
{
|
|
isInfoNew = true;
|
|
info = new ServerInfo();
|
|
}
|
|
|
|
info.ServerName = serverSettings.ServerName;
|
|
info.ServerMessage = serverSettings.ServerMessageText;
|
|
info.OwnerID = steamId;
|
|
info.LobbyID = SteamManager.CurrentLobbyID;
|
|
info.IP = ip;
|
|
info.Port = port;
|
|
info.GameMode = GameMain.NetLobbyScreen.SelectedMode?.Identifier ?? "";
|
|
info.GameStarted = Screen.Selected != GameMain.NetLobbyScreen;
|
|
info.GameVersion = GameMain.Version.ToString();
|
|
info.MaxPlayers = serverSettings.MaxPlayers;
|
|
info.PlayStyle = PlayStyle.SomethingDifferent;
|
|
info.RespondedToSteamQuery = true;
|
|
info.UsingWhiteList = serverSettings.Whitelist.Enabled;
|
|
info.TraitorsEnabled = serverSettings.TraitorsEnabled;
|
|
info.SubSelectionMode = serverSettings.SubSelectionMode;
|
|
info.ModeSelectionMode = serverSettings.ModeSelectionMode;
|
|
info.VoipEnabled = serverSettings.VoiceChatEnabled;
|
|
info.FriendlyFireEnabled = serverSettings.AllowFriendlyFire;
|
|
info.KarmaEnabled = serverSettings.KarmaEnabled;
|
|
info.PlayerCount = GameMain.Client.ConnectedClients.Count;
|
|
info.PingChecked = false;
|
|
info.HasPassword = serverSettings.HasPassword;
|
|
info.OwnerVerified = true;
|
|
|
|
if (isInfoNew)
|
|
{
|
|
AddToServerList(info);
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
public void AddToRecentServers(ServerInfo info)
|
|
{
|
|
if (!string.IsNullOrEmpty(info.IP))
|
|
{
|
|
//don't add localhost to recent servers
|
|
if (IPAddress.TryParse(info.IP, out IPAddress ip) && IPAddress.IsLoopback(ip)) { return; }
|
|
}
|
|
|
|
info.Recent = true;
|
|
ServerInfo existingInfo = recentServers.Find(serverInfo => info.OwnerID == serverInfo.OwnerID && (info.OwnerID != 0 ? true : (info.IP == serverInfo.IP && info.Port == serverInfo.Port)));
|
|
if (existingInfo == null)
|
|
{
|
|
recentServers.Add(info);
|
|
}
|
|
else
|
|
{
|
|
int index = recentServers.IndexOf(existingInfo);
|
|
recentServers[index] = info;
|
|
}
|
|
|
|
WriteServerMemToFile(recentServersFile, recentServers);
|
|
}
|
|
|
|
public void AddToFavoriteServers(ServerInfo info)
|
|
{
|
|
info.Favorite = true;
|
|
ServerInfo existingInfo = favoriteServers.Find(serverInfo => info.OwnerID == serverInfo.OwnerID && (info.OwnerID != 0 ? true : (info.IP == serverInfo.IP && info.Port == serverInfo.Port)));
|
|
if (existingInfo == null)
|
|
{
|
|
favoriteServers.Add(info);
|
|
}
|
|
else
|
|
{
|
|
int index = favoriteServers.IndexOf(existingInfo);
|
|
favoriteServers[index] = info;
|
|
}
|
|
|
|
WriteServerMemToFile(favoriteServersFile, favoriteServers);
|
|
}
|
|
|
|
public void RemoveFromFavoriteServers(ServerInfo info)
|
|
{
|
|
info.Favorite = false;
|
|
ServerInfo existingInfo = favoriteServers.Find(serverInfo => info.OwnerID == serverInfo.OwnerID && (info.OwnerID != 0 ? true : (info.IP == serverInfo.IP && info.Port == serverInfo.Port)));
|
|
if (existingInfo != null)
|
|
{
|
|
favoriteServers.Remove(existingInfo);
|
|
WriteServerMemToFile(favoriteServersFile, favoriteServers);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
if (s1 == null && s2 == null)
|
|
{
|
|
return 0;
|
|
}
|
|
else if (s1 == null)
|
|
{
|
|
return ascending ? 1 : -1;
|
|
}
|
|
else if (s2 == null)
|
|
{
|
|
return ascending ? -1 : 1;
|
|
}
|
|
|
|
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 string.Compare(s1.ServerName, 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();
|
|
SelectedTab = ServerListTab.All;
|
|
|
|
Steamworks.SteamMatchmaking.ResetActions();
|
|
|
|
RefreshServers();
|
|
}
|
|
|
|
public override void Deselect()
|
|
{
|
|
base.Deselect();
|
|
}
|
|
|
|
public override void Update(double deltaTime)
|
|
{
|
|
base.Update(deltaTime);
|
|
|
|
UpdateFriendsList();
|
|
|
|
if (PlayerInput.PrimaryMouseButtonClicked())
|
|
{
|
|
friendPopup = null;
|
|
if (friendsDropdown != null && friendsDropdownButton != null &&
|
|
!friendsDropdown.Rect.Contains(PlayerInput.MousePosition) &&
|
|
!friendsDropdownButton.Rect.Contains(PlayerInput.MousePosition))
|
|
{
|
|
friendsDropdown.Visible = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
//never show newer versions
|
|
//(ignore revision number, it doesn't affect compatibility)
|
|
if (remoteVersion != null &&
|
|
(remoteVersion.Major > GameMain.Version.Major || remoteVersion.Minor > GameMain.Version.Minor || remoteVersion.Build > GameMain.Version.Build))
|
|
{
|
|
child.Visible = false;
|
|
}
|
|
else
|
|
{
|
|
bool incompatible =
|
|
(!serverInfo.ContentPackageHashes.Any() && serverInfo.ContentPackagesMatch(GameMain.Config.SelectedContentPackages)) ||
|
|
(remoteVersion != null && !NetworkMember.IsCompatible(GameMain.Version, remoteVersion));
|
|
|
|
child.Visible =
|
|
serverInfo.OwnerVerified &&
|
|
serverInfo.ServerName.Contains(searchBox.Text, StringComparison.OrdinalIgnoreCase) &&
|
|
(!filterSameVersion.Selected || (remoteVersion != null && NetworkMember.IsCompatible(remoteVersion, GameMain.Version))) &&
|
|
(!filterPassword.Selected || !serverInfo.HasPassword) &&
|
|
(!filterIncompatible.Selected || !incompatible) &&
|
|
(!filterFull.Selected || serverInfo.PlayerCount < serverInfo.MaxPlayers) &&
|
|
(!filterEmpty.Selected || serverInfo.PlayerCount > 0) &&
|
|
(!filterWhitelisted.Selected || serverInfo.UsingWhiteList == true) &&
|
|
(!filterKarma.Selected || serverInfo.KarmaEnabled == true) &&
|
|
(!filterFriendlyFire.Selected || serverInfo.FriendlyFireEnabled == false) &&
|
|
(!filterTraitor.Selected || serverInfo.TraitorsEnabled == YesNoMaybe.Yes || serverInfo.TraitorsEnabled == YesNoMaybe.Maybe) &&
|
|
(!filterVoip.Selected || serverInfo.VoipEnabled == false) &&
|
|
(!filterModded.Selected || serverInfo.GetPlayStyleTags().Any(t => t.Contains("modded.true"))) &&
|
|
((selectedTab == ServerListTab.All && (serverInfo.LobbyID != 0 || !string.IsNullOrWhiteSpace(serverInfo.Port))) ||
|
|
(selectedTab == ServerListTab.Recent && serverInfo.Recent) ||
|
|
(selectedTab == ServerListTab.Favorites && serverInfo.Favorite));
|
|
}
|
|
|
|
foreach (GUITickBox tickBox in playStyleTickBoxes)
|
|
{
|
|
var playStyle = (PlayStyle)tickBox.UserData;
|
|
|
|
if (!tickBox.Selected && serverInfo.PlayStyle == playStyle)
|
|
{
|
|
child.Visible = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
foreach (GUITickBox tickBox in gameModeTickBoxes)
|
|
{
|
|
var gameMode = (string)tickBox.UserData;
|
|
if (!tickBox.Selected && serverInfo.GameMode.Equals(gameMode, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
child.Visible = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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("ServerListJoin"), TextManager.Get("AddToFavorites"), TextManager.Get("Cancel") },
|
|
relativeSize: new Vector2(0.25f, 0.2f), minSize: new Point(400, 150));
|
|
msgBox.Content.ChildAnchor = Anchor.TopCenter;
|
|
|
|
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.5f), msgBox.Content.RectTransform), childAnchor: Anchor.TopCenter)
|
|
{
|
|
IgnoreLayoutGroups = false,
|
|
Stretch = true,
|
|
RelativeSpacing = 0.05f
|
|
};
|
|
|
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform), TextManager.Get("ServerEndpoint"), textAlignment: Alignment.Center);
|
|
var endpointBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform));
|
|
|
|
content.RectTransform.NonScaledSize = new Point(content.Rect.Width, (int)(content.RectTransform.Children.Sum(c => c.Rect.Height)));
|
|
content.RectTransform.IsFixedSize = true;
|
|
msgBox.InnerFrame.RectTransform.MinSize = new Point(0, (int)((content.RectTransform.NonScaledSize.Y + msgBox.Content.RectTransform.Children.Sum(c => c.NonScaledSize.Y + msgBox.Content.AbsoluteSpacing)) * 1.1f));
|
|
|
|
var okButton = msgBox.Buttons[0];
|
|
okButton.Enabled = false;
|
|
okButton.OnClicked = (btn, userdata) =>
|
|
{
|
|
JoinServer(endpointBox.Text, "");
|
|
msgBox.Close();
|
|
return true;
|
|
};
|
|
|
|
var favoriteButton = msgBox.Buttons[1];
|
|
favoriteButton.Enabled = false;
|
|
favoriteButton.OnClicked = (button, userdata) =>
|
|
{
|
|
UInt64 steamId = SteamManager.SteamIDStringToUInt64(endpointBox.Text);
|
|
string ip = ""; int port = 0;
|
|
if (steamId == 0)
|
|
{
|
|
string hostIP = endpointBox.Text;
|
|
|
|
string[] address = hostIP.Split(':');
|
|
if (address.Length == 1)
|
|
{
|
|
ip = hostIP;
|
|
port = NetConfig.DefaultPort;
|
|
}
|
|
else
|
|
{
|
|
ip = string.Join(":", address.Take(address.Length - 1));
|
|
if (!int.TryParse(address[address.Length - 1], out port))
|
|
{
|
|
DebugConsole.ThrowError("Invalid port: " + address[address.Length - 1] + "!");
|
|
port = NetConfig.DefaultPort;
|
|
}
|
|
}
|
|
}
|
|
|
|
//TODO: add a better way to get the query port, right now we're just assuming that it'll always be the default
|
|
ServerInfo serverInfo = new ServerInfo()
|
|
{
|
|
ServerName = "Server",
|
|
OwnerID = steamId,
|
|
IP = ip,
|
|
Port = port.ToString(),
|
|
QueryPort = NetConfig.DefaultQueryPort.ToString(),
|
|
GameVersion = GameMain.Version.ToString(),
|
|
PlayStyle = PlayStyle.Serious
|
|
};
|
|
|
|
var serverFrame = serverList.Content.FindChild(d => (d.UserData is ServerInfo info) &&
|
|
info.OwnerID == serverInfo.OwnerID &&
|
|
(serverInfo.OwnerID != 0 ? true : (info.IP == serverInfo.IP && info.Port == serverInfo.Port)));
|
|
|
|
if (serverFrame != null)
|
|
{
|
|
serverInfo = serverFrame.UserData as ServerInfo;
|
|
}
|
|
else
|
|
{
|
|
AddToServerList(serverInfo);
|
|
}
|
|
|
|
AddToFavoriteServers(serverInfo);
|
|
|
|
SelectedTab = ServerListTab.Favorites;
|
|
FilterServers();
|
|
|
|
serverInfo.QueryLiveInfo(UpdateServerInfo);
|
|
|
|
msgBox.Close();
|
|
return false;
|
|
};
|
|
|
|
var cancelButton = msgBox.Buttons[2];
|
|
cancelButton.OnClicked = msgBox.Close;
|
|
|
|
endpointBox.OnTextChanged += (textBox, text) =>
|
|
{
|
|
okButton.Enabled = favoriteButton.Enabled = !string.IsNullOrEmpty(text);
|
|
return true;
|
|
};
|
|
}
|
|
|
|
private bool JoinFriend(GUIButton button, object userdata)
|
|
{
|
|
FriendInfo info = userdata as FriendInfo;
|
|
|
|
if (info.InServer)
|
|
{
|
|
if (info.ConnectLobby != 0)
|
|
{
|
|
GameMain.Instance.ConnectLobby = info.ConnectLobby;
|
|
GameMain.Instance.ConnectEndpoint = null;
|
|
GameMain.Instance.ConnectName = null;
|
|
}
|
|
else
|
|
{
|
|
GameMain.Instance.ConnectLobby = 0;
|
|
GameMain.Instance.ConnectEndpoint = info.ConnectEndpoint;
|
|
GameMain.Instance.ConnectName = info.ConnectName;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool OpenFriendPopup(GUIButton button, object userdata)
|
|
{
|
|
FriendInfo info = userdata as FriendInfo;
|
|
|
|
if (info.InServer)
|
|
{
|
|
friendPopup = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas));
|
|
var serverNameText = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), friendPopup.RectTransform), info.ConnectName ?? "[Unnamed]");
|
|
var joinButton = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), friendPopup.RectTransform, Anchor.TopRight), TextManager.Get("ServerListJoin"))
|
|
{
|
|
UserData = info
|
|
};
|
|
joinButton.OnClicked = JoinFriend;
|
|
|
|
Vector2 frameDims = joinButton.Font.MeasureString(info.ConnectName ?? "[Unnamed]");
|
|
frameDims.X /= 0.6f;
|
|
frameDims.Y *= 1.5f;
|
|
friendPopup.RectTransform.NonScaledSize = frameDims.ToPoint();
|
|
friendPopup.RectTransform.RelativeOffset = Vector2.Zero;
|
|
friendPopup.RectTransform.AbsoluteOffset = PlayerInput.MousePosition.ToPoint();
|
|
friendPopup.RectTransform.RecalculateChildren(true);
|
|
friendPopup.RectTransform.SetPosition(Anchor.TopLeft);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private enum AvatarSize
|
|
{
|
|
Small,
|
|
Medium,
|
|
Large
|
|
}
|
|
|
|
private void UpdateFriendsList()
|
|
{
|
|
if (!SteamManager.IsInitialized) { return; }
|
|
|
|
if (friendsListUpdateTime > Timing.TotalTime) { return; }
|
|
friendsListUpdateTime = Timing.TotalTime + 5.0;
|
|
|
|
float prevDropdownScroll = friendsDropdown?.ScrollBar.BarScrollValue ?? 0.0f;
|
|
|
|
if (friendsDropdown == null)
|
|
{
|
|
friendsDropdown = new GUIListBox(new RectTransform(Vector2.One, GUI.Canvas))
|
|
{
|
|
OutlineColor = Color.Black,
|
|
Visible = false
|
|
};
|
|
}
|
|
friendsDropdown.ClearChildren();
|
|
|
|
AvatarSize avatarSize = AvatarSize.Large;
|
|
if (friendsButtonHolder.RectTransform.Rect.Height <= 24)
|
|
{
|
|
avatarSize = AvatarSize.Small;
|
|
}
|
|
else if (friendsButtonHolder.RectTransform.Rect.Height <= 48)
|
|
{
|
|
avatarSize = AvatarSize.Medium;
|
|
}
|
|
|
|
List<Steamworks.Friend> friends = Steamworks.SteamFriends.GetFriends().ToList();
|
|
|
|
for (int i = friendsList.Count - 1; i >= 0; i--)
|
|
{
|
|
var friend = friendsList[i];
|
|
if (!friends.Any(g => g.Id == friend.SteamID && g.IsOnline))
|
|
{
|
|
friend.Sprite?.Remove();
|
|
friendsList.RemoveAt(i);
|
|
}
|
|
}
|
|
|
|
foreach (var friend in friends)
|
|
{
|
|
if (!friend.IsOnline) { continue; }
|
|
|
|
FriendInfo info = friendsList.Find(f => f.SteamID == friend.Id);
|
|
if (info == null)
|
|
{
|
|
info = new FriendInfo()
|
|
{
|
|
SteamID = friend.Id
|
|
};
|
|
friendsList.Insert(0, info);
|
|
}
|
|
|
|
if (info.Sprite == null)
|
|
{
|
|
Func<Steamworks.SteamId, Task<Steamworks.Data.Image?>> avatarFunc = null;
|
|
switch (avatarSize)
|
|
{
|
|
case AvatarSize.Small:
|
|
avatarFunc = Steamworks.SteamFriends.GetSmallAvatarAsync;
|
|
break;
|
|
case AvatarSize.Medium:
|
|
avatarFunc = Steamworks.SteamFriends.GetMediumAvatarAsync;
|
|
break;
|
|
case AvatarSize.Large:
|
|
avatarFunc = Steamworks.SteamFriends.GetLargeAvatarAsync;
|
|
break;
|
|
}
|
|
TaskPool.Add(avatarFunc(friend.Id), (Task<Steamworks.Data.Image?> task) =>
|
|
{
|
|
if (!task.Result.HasValue) { return; }
|
|
|
|
var avatarImage = task.Result.Value;
|
|
|
|
const int desaturatedWeight = 180;
|
|
|
|
byte[] avatarData = (byte[])avatarImage.Data.Clone();
|
|
for (int i = 0; i < avatarData.Length; i += 4)
|
|
{
|
|
int luma = (avatarData[i + 0] * 299 + avatarData[i + 1] * 587 + avatarData[i + 2] * 114) / 1000;
|
|
luma = (int)(luma * 0.7f + ((luma / 100.0f) * (luma / 255.0f) * 255.0f * 0.3f));
|
|
int chn0 = ((avatarData[i + 0] * (255 - desaturatedWeight)) / 255) + ((luma * desaturatedWeight) / 255);
|
|
int chn1 = ((avatarData[i + 1] * (255 - desaturatedWeight)) / 255) + ((luma * desaturatedWeight) / 255);
|
|
int chn2 = ((avatarData[i + 2] * (255 - desaturatedWeight)) / 255) + ((luma * desaturatedWeight) / 255);
|
|
int chn3 = 255;
|
|
|
|
chn0 = chn0 * chn3 / 255;
|
|
chn1 = chn1 * chn3 / 255;
|
|
chn2 = chn2 * chn3 / 255;
|
|
|
|
avatarData[i + 0] = chn0 > 255 ? (byte)255 : (byte)chn0;
|
|
avatarData[i + 1] = chn1 > 255 ? (byte)255 : (byte)chn1;
|
|
avatarData[i + 2] = chn2 > 255 ? (byte)255 : (byte)chn2;
|
|
avatarData[i + 3] = chn3 > 255 ? (byte)255 : (byte)chn3;
|
|
}
|
|
CrossThread.RequestExecutionOnMainThread(() =>
|
|
{
|
|
//TODO: create an avatar atlas?
|
|
var avatarTexture = new Texture2D(GameMain.Instance.GraphicsDevice, (int)avatarImage.Width, (int)avatarImage.Height);
|
|
avatarTexture.SetData(avatarData);
|
|
info.Sprite = new Sprite(avatarTexture, null, null);
|
|
});
|
|
});
|
|
}
|
|
|
|
info.Name = friend.Name;
|
|
|
|
info.ConnectName = null;
|
|
info.ConnectEndpoint = null;
|
|
info.ConnectLobby = 0;
|
|
|
|
info.PlayingThisGame = friend.IsPlayingThisGame;
|
|
info.PlayingAnotherGame = friend.GameInfo.HasValue;
|
|
|
|
if (friend.IsPlayingThisGame)
|
|
{
|
|
info.StatusText = friend.GetRichPresence("status") ?? "";
|
|
string connectCommand = friend.GetRichPresence("connect") ?? "";
|
|
|
|
try
|
|
{
|
|
ToolBox.ParseConnectCommand(ToolBox.SplitCommand(connectCommand), out info.ConnectName, out info.ConnectEndpoint, out info.ConnectLobby);
|
|
}
|
|
catch (IndexOutOfRangeException e)
|
|
{
|
|
#if DEBUG
|
|
DebugConsole.ThrowError($"Failed to parse a Steam friend's connect command ({connectCommand})", e);
|
|
#else
|
|
DebugConsole.Log($"Failed to parse a Steam friend's connect command ({connectCommand})\n" + e.StackTrace);
|
|
#endif
|
|
info.ConnectName = null;
|
|
info.ConnectEndpoint = null;
|
|
info.ConnectLobby = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
info.StatusText = TextManager.Get(info.PlayingAnotherGame ? "FriendPlayingAnotherGame" : "FriendNotPlaying");
|
|
}
|
|
}
|
|
|
|
friendsList.Sort((a, b) =>
|
|
{
|
|
if (a.InServer && !b.InServer) { return -1; }
|
|
if (b.InServer && !a.InServer) { return 1; }
|
|
if (a.PlayingThisGame && !b.PlayingThisGame) { return -1; }
|
|
if (b.PlayingThisGame && !a.PlayingThisGame) { return 1; }
|
|
return 0;
|
|
});
|
|
|
|
friendsButtonHolder.ClearChildren();
|
|
|
|
if (friendsList.Count > 0)
|
|
{
|
|
friendsDropdownButton = new GUIButton(new RectTransform(Vector2.One, friendsButtonHolder.RectTransform, Anchor.BottomRight, Pivot.BottomRight, scaleBasis: ScaleBasis.BothHeight), "\u2022 \u2022 \u2022", style: "GUIButtonFriendsDropdown")
|
|
{
|
|
Font = GUI.GlobalFont,
|
|
OnClicked = (button, udt) =>
|
|
{
|
|
friendsDropdown.RectTransform.NonScaledSize = new Point(friendsButtonHolder.Rect.Height * 5 * 166 / 100, friendsButtonHolder.Rect.Height * 4 * 166 / 100);
|
|
friendsDropdown.RectTransform.AbsoluteOffset = new Point(friendsButtonHolder.Rect.X, friendsButtonHolder.Rect.Bottom);
|
|
friendsDropdown.RectTransform.RecalculateChildren(true);
|
|
friendsDropdown.Visible = !friendsDropdown.Visible;
|
|
return false;
|
|
}
|
|
};
|
|
}
|
|
else
|
|
{
|
|
friendsDropdownButton = null;
|
|
friendsDropdown.Visible = false;
|
|
}
|
|
|
|
int buttonCount = 0;
|
|
|
|
for (int i = 0; i < friendsList.Count; i++)
|
|
{
|
|
var friend = friendsList[i];
|
|
buttonCount++;
|
|
|
|
if (buttonCount <= 5)
|
|
{
|
|
string style = "GUIButtonFriendNotPlaying";
|
|
if (friend.InServer)
|
|
{
|
|
style = "GUIButtonFriendPlaying";
|
|
}
|
|
else
|
|
{
|
|
style = friend.PlayingThisGame ? "GUIButtonFriendPlaying" : "GUIButtonFriendNotPlaying";
|
|
}
|
|
|
|
var guiButton = new GUIButton(new RectTransform(Vector2.One, friendsButtonHolder.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: style)
|
|
{
|
|
UserData = friend,
|
|
OnClicked = OpenFriendPopup
|
|
};
|
|
guiButton.ToolTip = friend.Name + "\n" + friend.StatusText;
|
|
|
|
if (friend.Sprite != null)
|
|
{
|
|
static Color BrightenColor(Color color)
|
|
{
|
|
Vector3 hls = ToolBox.RgbToHLS(color);
|
|
hls.Y = hls.Y * 0.3f + 0.7f;
|
|
hls.Z = hls.Z * 0.6f + 0.4f;
|
|
|
|
return ToolBox.HLSToRGB(hls);
|
|
}
|
|
|
|
var imgColor = BrightenColor(guiButton.Color);
|
|
var imgHoverColor = BrightenColor(guiButton.HoverColor);
|
|
var imgSelectColor = BrightenColor(guiButton.SelectedColor);
|
|
var imgPressColor = BrightenColor(guiButton.PressedColor);
|
|
var guiImage = new GUIImage(new RectTransform(Vector2.One * 0.925f, guiButton.RectTransform, Anchor.Center) { RelativeOffset = new Vector2(0.025f, 0.025f) }, friend.Sprite, null, true)
|
|
{
|
|
Color = imgColor,
|
|
HoverColor = imgHoverColor,
|
|
SelectedColor = imgSelectColor,
|
|
PressedColor = imgPressColor,
|
|
CanBeFocused = false
|
|
};
|
|
guiImage = new GUIImage(new RectTransform(Vector2.One * 0.925f, guiButton.RectTransform, Anchor.Center) { RelativeOffset = new Vector2(0.025f, 0.025f) }, friend.Sprite, null, true)
|
|
{
|
|
Color = Color.White * 0.8f,
|
|
HoverColor = Color.White * 0.8f,
|
|
SelectedColor = Color.White * 0.8f,
|
|
PressedColor = Color.White * 0.8f,
|
|
BlendState = BlendState.Additive,
|
|
CanBeFocused = false
|
|
};
|
|
}
|
|
}
|
|
|
|
var friendFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.167f), friendsDropdown.Content.RectTransform), style: "GUIFrameFriendsDropdown");
|
|
var guiImage2TheSequel = new GUIImage(new RectTransform(Vector2.One * 0.9f, friendFrame.RectTransform, Anchor.CenterLeft, scaleBasis: ScaleBasis.BothHeight) { RelativeOffset = new Vector2(0.02f, 0.02f) } , friend.Sprite, null, true);
|
|
|
|
var textBlock = new GUITextBlock(new RectTransform(Vector2.One * 0.8f, friendFrame.RectTransform, Anchor.CenterLeft, scaleBasis: ScaleBasis.BothHeight) { RelativeOffset = new Vector2(1.0f / 7.7f, 0.0f) }, friend.Name + "\n" + friend.StatusText)
|
|
{
|
|
Font = GUI.SmallFont
|
|
};
|
|
if (friend.PlayingThisGame) { textBlock.TextColor = GUI.Style.Green; }
|
|
if (friend.PlayingAnotherGame) { textBlock.TextColor = GUI.Style.Blue; }
|
|
|
|
if (friend.InServer)
|
|
{
|
|
var joinButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.6f), friendFrame.RectTransform, Anchor.CenterRight) { RelativeOffset = new Vector2(0.05f, 0.0f) }, TextManager.Get("ServerListJoin"), style: "GUIButtonJoinFriend")
|
|
{
|
|
UserData = friend
|
|
};
|
|
joinButton.OnClicked = JoinFriend;
|
|
}
|
|
}
|
|
|
|
friendsDropdown.RectTransform.NonScaledSize = new Point(friendsButtonHolder.Rect.Height * 5 * 166 / 100, friendsButtonHolder.Rect.Height * 4 * 166 / 100);
|
|
friendsDropdown.RectTransform.AbsoluteOffset = new Point(friendsButtonHolder.Rect.X, friendsButtonHolder.Rect.Bottom);
|
|
friendsDropdown.RectTransform.RecalculateChildren(true);
|
|
|
|
friendsDropdown.ScrollBar.BarScrollValue = prevDropdownScroll;
|
|
}
|
|
|
|
private void RefreshServers()
|
|
{
|
|
if (waitingForRefresh) { return; }
|
|
|
|
steamPingInfoReady = false;
|
|
|
|
CoroutineManager.StopCoroutines("EstimateLobbyPing");
|
|
|
|
TaskPool.Add(Steamworks.SteamNetworkingUtils.WaitForPingDataAsync(), (task) =>
|
|
{
|
|
steamPingInfoReady = true;
|
|
});
|
|
|
|
friendsListUpdateTime = Timing.TotalTime - 1.0;
|
|
UpdateFriendsList();
|
|
|
|
serverList.ClearChildren();
|
|
serverPreview.ClearChildren();
|
|
joinButton.Enabled = false;
|
|
selectedServer = null;
|
|
|
|
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<object> WaitForRefresh()
|
|
{
|
|
waitingForRefresh = true;
|
|
if (refreshDisableTimer > DateTime.Now)
|
|
{
|
|
yield return new WaitForSeconds((float)(refreshDisableTimer - DateTime.Now).TotalSeconds);
|
|
}
|
|
|
|
recentServers.Concat(favoriteServers).ForEach(si => si.OwnerVerified = false);
|
|
if (GameMain.Config.UseSteamMatchmaking)
|
|
{
|
|
serverList.ClearChildren();
|
|
if (!SteamManager.GetServers(AddToServerList, 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
|
|
{
|
|
List<ServerInfo> knownServers = recentServers.Concat(favoriteServers).ToList();
|
|
foreach (ServerInfo info in knownServers)
|
|
{
|
|
AddToServerList(info);
|
|
info.QueryLiveInfo(UpdateServerInfo);
|
|
}
|
|
}
|
|
}
|
|
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).Equals("error", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
DebugConsole.ThrowError("Error while connecting to master server (" + masterServerData + ")!");
|
|
return;
|
|
}
|
|
|
|
string[] lines = masterServerData.Split('\n');
|
|
List<ServerInfo> serverInfos = new List<ServerInfo>();
|
|
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,
|
|
OwnerVerified = true
|
|
};
|
|
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 = serverList.Content.FindChild(d => (d.UserData is ServerInfo info) &&
|
|
(info.LobbyID == serverInfo.LobbyID ||
|
|
(info.LobbyID == 0 && info.OwnerID == serverInfo.OwnerID &&
|
|
serverInfo.OwnerVerified)) &&
|
|
(serverInfo.OwnerID != 0 ? true : (info.IP == serverInfo.IP && info.Port == serverInfo.Port)));
|
|
|
|
if (serverFrame == null)
|
|
{
|
|
serverFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.06f), serverList.Content.RectTransform) { MinSize = new Point(0, 35) },
|
|
style: "ListBoxElement")
|
|
{
|
|
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
|
|
};
|
|
}
|
|
else
|
|
{
|
|
int index = recentServers.IndexOf(serverFrame.UserData as ServerInfo);
|
|
if (index >= 0)
|
|
{
|
|
recentServers[index] = serverInfo;
|
|
serverInfo.Recent = true;
|
|
}
|
|
index = favoriteServers.IndexOf(serverFrame.UserData as ServerInfo);
|
|
if (index >= 0)
|
|
{
|
|
favoriteServers[index] = serverInfo;
|
|
serverInfo.Favorite = true;
|
|
}
|
|
}
|
|
serverFrame.UserData = serverInfo;
|
|
|
|
if (serverInfo.OwnerVerified)
|
|
{
|
|
var childrenToRemove = serverList.Content.FindChildren(c => (c.UserData is ServerInfo info) && info != serverInfo &&
|
|
(serverInfo.OwnerID != 0 ? info.OwnerID == serverInfo.OwnerID : info.IP == serverInfo.IP)).ToList();
|
|
foreach (var child in childrenToRemove)
|
|
{
|
|
serverList.Content.RemoveChild(child);
|
|
}
|
|
}
|
|
|
|
UpdateServerInfo(serverInfo);
|
|
|
|
SortList(sortedBy, toggle: false);
|
|
FilterServers();
|
|
}
|
|
|
|
private void UpdateServerInfo(ServerInfo serverInfo)
|
|
{
|
|
var serverFrame = serverList.Content.FindChild(d => (d.UserData is ServerInfo info) &&
|
|
(info.LobbyID == serverInfo.LobbyID ||
|
|
(info.LobbyID == 0 && info.OwnerID == serverInfo.OwnerID &&
|
|
serverInfo.OwnerVerified)) &&
|
|
(serverInfo.OwnerID != 0 ? true : (info.IP == serverInfo.IP && info.Port == serverInfo.Port)));
|
|
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: "")
|
|
{
|
|
CanBeFocused = 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,
|
|
CanBeFocused = false,
|
|
UserData = "password"
|
|
};
|
|
|
|
var serverName = new GUITextBlock(new RectTransform(new Vector2(columnRelativeWidth[2] * 1.1f, 1.0f), serverContent.RectTransform),
|
|
#if !DEBUG
|
|
serverInfo.ServerName,
|
|
#else
|
|
((serverInfo.OwnerID != 0 || serverInfo.LobbyID != 0) ? "[STEAMP2P] " : "[LIDGREN] ") + serverInfo.ServerName,
|
|
#endif
|
|
style: "GUIServerListTextBox");
|
|
serverName.UserData = serverName.Text;
|
|
serverName.RectTransform.SizeChanged += () =>
|
|
{
|
|
serverName.Text = ToolBox.LimitString(serverName.Text, serverName.Font, serverName.Rect.Width);
|
|
};
|
|
|
|
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,
|
|
CanBeFocused = 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);
|
|
}
|
|
}
|
|
else if (serverInfo.PingLocation != null)
|
|
{
|
|
CoroutineManager.StartCoroutine(EstimateLobbyPing(serverInfo, serverPingText), "EstimateLobbyPing");
|
|
}
|
|
|
|
if (serverInfo.LobbyID == 0 && (string.IsNullOrWhiteSpace(serverInfo.IP) || string.IsNullOrWhiteSpace(serverInfo.Port)))
|
|
{
|
|
string toolTip = TextManager.Get("ServerOffline");
|
|
serverContent.Children.ForEach(c => c.ToolTip = toolTip);
|
|
serverName.TextColor *= 0.8f;
|
|
serverPlayers.TextColor *= 0.8f;
|
|
}
|
|
else 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), " ? ", GUI.Style.Orange * 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();
|
|
|
|
if (serverInfo.Favorite)
|
|
{
|
|
AddToFavoriteServers(serverInfo);
|
|
}
|
|
|
|
SortList(sortedBy, toggle: false);
|
|
FilterServers();
|
|
}
|
|
|
|
private IEnumerable<object> EstimateLobbyPing(ServerInfo serverInfo, GUITextBlock serverPingText)
|
|
{
|
|
while (!steamPingInfoReady)
|
|
{
|
|
yield return CoroutineStatus.Running;
|
|
}
|
|
|
|
Steamworks.Data.PingLocation pingLocation = serverInfo.PingLocation.Value;
|
|
serverInfo.Ping = Steamworks.SteamNetworkingUtils.LocalPingLocation?.EstimatePingTo(pingLocation) ?? -1;
|
|
serverInfo.PingChecked = true;
|
|
serverPingText.TextColor = GetPingTextColor(serverInfo.Ping);
|
|
serverPingText.Text = serverInfo.Ping > -1 ? serverInfo.Ping.ToString() : "?";
|
|
|
|
yield return CoroutineStatus.Success;
|
|
}
|
|
|
|
private void ServerQueryFinished()
|
|
{
|
|
if (!serverList.Content.Children.Any())
|
|
{
|
|
new GUITextBlock(new RectTransform(Vector2.One, serverList.Content.RectTransform),
|
|
TextManager.Get("NoServers"), textAlignment: Alignment.Center)
|
|
{
|
|
CanBeFocused = false
|
|
};
|
|
}
|
|
else if (serverList.Content.Children.All(c => !c.Visible))
|
|
{
|
|
new GUITextBlock(new RectTransform(Vector2.One, serverList.Content.RectTransform),
|
|
TextManager.Get("NoMatchingServers"), textAlignment: Alignment.Center)
|
|
{
|
|
CanBeFocused = false,
|
|
UserData = "noresults"
|
|
};
|
|
}
|
|
waitingForRefresh = false;
|
|
}
|
|
|
|
private IEnumerable<object> 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 endpoint, string serverName)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(ClientNameBox.Text))
|
|
{
|
|
ClientNameBox.Flash();
|
|
return false;
|
|
}
|
|
|
|
GameMain.Config.PlayerName = ClientNameBox.Text;
|
|
GameMain.Config.SaveNewPlayerConfig();
|
|
|
|
CoroutineManager.StartCoroutine(ConnectToServer(endpoint, serverName), "ConnectToServer");
|
|
|
|
return true;
|
|
}
|
|
|
|
private IEnumerable<object> ConnectToServer(string endpoint, string serverName)
|
|
{
|
|
string serverIP = null;
|
|
UInt64 serverSteamID = SteamManager.SteamIDStringToUInt64(endpoint);
|
|
if (serverSteamID == 0) { serverIP = endpoint; }
|
|
|
|
#if !DEBUG
|
|
try
|
|
{
|
|
#endif
|
|
GameMain.Client = new GameClient(GameMain.Config.PlayerName, serverIP, serverSteamID, 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)
|
|
{
|
|
if (CoroutineManager.IsCoroutineRunning("ConnectToServer")) { return; }
|
|
|
|
lock (activePings)
|
|
{
|
|
if (activePings.Contains(serverInfo.IP)) { return; }
|
|
activePings.Add(serverInfo.IP);
|
|
}
|
|
|
|
serverInfo.PingChecked = false;
|
|
serverInfo.Ping = -1;
|
|
|
|
TaskPool.Add(PingServerAsync(serverInfo?.IP, 1000),
|
|
new Tuple<ServerInfo, GUITextBlock>(serverInfo, serverPingText),
|
|
(rtt, obj) =>
|
|
{
|
|
var info = obj.Item1;
|
|
var text = obj.Item2;
|
|
info.Ping = rtt.Result; info.PingChecked = true;
|
|
text.TextColor = GetPingTextColor(info.Ping);
|
|
text.Text = info.Ping > -1 ? info.Ping.ToString() : "?";
|
|
lock (activePings)
|
|
{
|
|
activePings.Remove(serverInfo.IP);
|
|
}
|
|
});
|
|
}
|
|
|
|
private Color GetPingTextColor(int ping)
|
|
{
|
|
if (ping < 0) { return Color.DarkRed; }
|
|
return ToolBox.GradientLerp(ping / 200.0f, GUI.Style.Green, GUI.Style.Orange, GUI.Style.Red);
|
|
}
|
|
|
|
public async Task<int> PingServerAsync(string ip, int timeOut)
|
|
{
|
|
await Task.Yield();
|
|
int activePingCount = 100;
|
|
while (activePingCount > 25)
|
|
{
|
|
lock (activePings)
|
|
{
|
|
activePingCount = activePings.Count;
|
|
}
|
|
await Task.Delay(25);
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(ip))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
long rtt = -1;
|
|
IPAddress address = null;
|
|
IPAddress.TryParse(ip, out address);
|
|
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 (Exception ex)
|
|
{
|
|
string errorMsg = "Failed to ping a server (" + ip + ") - " + (ex?.InnerException?.Message ?? ex.Message);
|
|
GameAnalyticsManager.AddErrorEventOnce("ServerListScreen.PingServer:PingException" + ip, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, errorMsg);
|
|
#if DEBUG
|
|
DebugConsole.NewMessage(errorMsg, Color.Red);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
return (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, GUI.SamplerState, null, GameMain.ScissorTestEnable);
|
|
|
|
GUI.Draw(Cam, spriteBatch);
|
|
|
|
spriteBatch.End();
|
|
}
|
|
|
|
public override void AddToGUIUpdateList()
|
|
{
|
|
menu.AddToGUIUpdateList();
|
|
|
|
friendPopup?.AddToGUIUpdateList();
|
|
|
|
friendsDropdown?.AddToGUIUpdateList();
|
|
}
|
|
|
|
}
|
|
}
|