4 using Microsoft.Xna.Framework;
5 using Microsoft.Xna.Framework.Graphics;
7 using System.Collections.Generic;
8 using System.Collections.Immutable;
10 using System.Threading.Tasks;
11 using System.Xml.Linq;
18 private enum MsgUserData
26 private static readonly TimeSpan AllowedRefreshInterval = TimeSpan.FromSeconds(3);
28 private DateTime lastRefreshTime = DateTime.Now;
34 private GUIFrame serverPreviewContainer;
38 private Option<ServerInfo> selectedServer;
42 private enum TernaryOption
56 public readonly
struct Tab
61 private readonly List<ServerInfo> servers;
62 public IReadOnlyList<ServerInfo>
Servers => servers;
67 servers =
new List<ServerInfo>();
69 TextManager.Get($
"ServerListTab.{tabEnum}"), style:
"GUITabButton")
73 serverListScreen.selectedTab = tabEnum;
83 if (
Storage.IsNullOrEmpty()) {
return; }
85 XDocument doc = XMLExtensions.TryLoadXml(
Storage, out _);
86 if (doc?.Root is
null) {
return; }
94 servers.Remove(info); servers.Add(info);
97 public void Clear() => servers.Clear();
101 XDocument doc =
new XDocument();
102 XElement rootElement =
new XElement(
"servers");
103 doc.Add(rootElement);
114 private readonly Dictionary<TabEnum, Tab> tabs =
new Dictionary<TabEnum, Tab>();
116 private TabEnum _selectedTabBackingField;
119 get => _selectedTabBackingField;
122 _selectedTabBackingField = value;
123 tabs.ForEach(kvp => kvp.Value.Button.Selected = (value == kvp.Key));
128 private ServerProvider serverProvider =
null;
134 ServerListCompatible,
135 ServerListHasPassword,
137 ServerListRoundStarted,
141 private struct Column
143 public float RelativeWidth;
144 public ColumnLabel Label;
146 public static implicit
operator Column((
float W, ColumnLabel L) pair) =>
147 new Column { RelativeWidth = pair.W, Label = pair.L };
149 public static Column[] Normalize(params Column[] columns)
151 var totalWidth = columns.
Select(c => c.RelativeWidth).Aggregate((a, b) => a + b);
152 for (
int i = 0; i < columns.Length; i++)
154 columns[i].RelativeWidth /= totalWidth;
160 private static readonly ImmutableDictionary<ColumnLabel, Column> columns =
162 (0.1f, ColumnLabel.ServerListCompatible),
163 (0.1f, ColumnLabel.ServerListHasPassword),
164 (0.7f, ColumnLabel.ServerListName),
165 (0.12f, ColumnLabel.ServerListRoundStarted),
166 (0.08f, ColumnLabel.ServerListPlayers),
167 (0.08f, ColumnLabel.ServerListPing)
168 ).Select(c => (c.Label, c)).ToImmutableDictionary();
170 private GUILayoutGroup labelHolder;
171 private readonly List<GUITextBlock> labelTexts =
new List<GUITextBlock>();
174 private GUITextBox searchBox;
175 private GUITickBox filterSameVersion;
176 private GUITickBox filterPassword;
177 private GUITickBox filterFull;
178 private GUITickBox filterEmpty;
179 private GUIDropDown languageDropdown;
180 private Dictionary<Identifier, GUIDropDown> ternaryFilters;
181 private Dictionary<Identifier, GUITickBox> filterTickBoxes;
182 private Dictionary<Identifier, GUITickBox> playStyleTickBoxes;
183 private Dictionary<Identifier, GUITickBox> gameModeTickBoxes;
184 private GUITickBox filterOffensive;
187 private TernaryOption filterFriendlyFireValue = TernaryOption.Any;
188 private TernaryOption filterKarmaValue = TernaryOption.Any;
189 private TernaryOption filterTraitorValue = TernaryOption.Any;
190 private TernaryOption filterVoipValue = TernaryOption.Any;
191 private TernaryOption filterModdedValue = TernaryOption.Any;
193 private ColumnLabel sortedBy;
194 private bool sortedAscending =
true;
196 private const float sidebarWidth = 0.2f;
199 selectedServer = Option<ServerInfo>.None();
204 private static Task<string> GetDefaultUserName()
209 private void AddTernaryFilter(RectTransform parent,
float elementHeight, Identifier tag, Action<TernaryOption> valueSetter)
211 var filterLayoutGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, elementHeight), parent), isHorizontal:
true)
216 var box =
new GUIFrame(
new RectTransform(Vector2.One, filterLayoutGroup.RectTransform,
Anchor.CenterLeft, scaleBasis:
ScaleBasis.BothHeight)
221 HoverColor = Color.Gray,
222 SelectedColor = Color.DarkGray,
225 if (box.RectTransform.MinSize.Y > 0)
227 box.RectTransform.MinSize =
new Point(box.RectTransform.MinSize.Y);
228 box.RectTransform.Resize(box.RectTransform.MinSize);
230 Vector2 textBlockScale =
new Vector2((
float)(filterLayoutGroup.Rect.Width - filterLayoutGroup.Rect.Height) / (
float)Math.Max(filterLayoutGroup.Rect.Width, 1.0), 1.0f);
232 var filterLabel =
new GUITextBlock(
new RectTransform(
new Vector2(0.6f, 1.0f) * textBlockScale, filterLayoutGroup.RectTransform,
Anchor.CenterLeft), TextManager.Get(
"servertag." + tag +
".label"), textAlignment: Alignment.CenterLeft)
234 UserData = TextManager.Get($
"servertag.{tag}.label")
236 GUIStyle.Apply(filterLabel,
"GUITextBlock",
null);
238 var dropDown =
new GUIDropDown(
new RectTransform(
new Vector2(0.4f, 1.0f) * textBlockScale, filterLayoutGroup.RectTransform,
Anchor.CenterLeft), elementCount: 3);
239 dropDown.AddItem(TextManager.Get(
"any"), TernaryOption.Any);
240 dropDown.AddItem(TextManager.Get($
"servertag.{tag}.true"), TernaryOption.Enabled, TextManager.Get(
241 $
"servertagdescription.{tag}.true"));
242 dropDown.AddItem(TextManager.Get($
"servertag.{tag}.false"), TernaryOption.Disabled, TextManager.Get(
243 $
"servertagdescription.{tag}.false"));
244 dropDown.SelectItem(TernaryOption.Any);
245 dropDown.OnSelected = (_, data) => {
246 valueSetter((TernaryOption)data);
252 ternaryFilters.Add(tag, dropDown);
255 private void CreateUI()
257 menu =
new GUIFrame(
new RectTransform(
new Vector2(0.95f, 0.85f), GUI.Canvas,
Anchor.Center) { MinSize = new Point(GameMain.GraphicsHeight, 0) });
259 var paddedFrame =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.98f, 0.98f), menu.
RectTransform,
Anchor.Center))
261 RelativeSpacing = 0.02f,
269 var topRow =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.15f), paddedFrame.RectTransform)) { Stretch =
true };
271 var titleContainer =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.995f, 0.33f), topRow.RectTransform), isHorizontal:
true) { Stretch =
true };
273 var title =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 1.0f), titleContainer.RectTransform), TextManager.Get(
"JoinServer"), font: GUIStyle.LargeFont)
275 Padding = Vector4.Zero,
277 AutoScaleHorizontal =
true
280 var friendsButton =
new GUIButton(
281 new RectTransform(Vector2.One * 0.9f, titleContainer.RectTransform, scaleBasis:
ScaleBasis.BothHeight),
282 style:
"FriendsButton")
284 OnClicked = (_, _) =>
291 new GUIFrame(
new RectTransform(Vector2.One, friendsButton.RectTransform,
Anchor.Center),
292 style:
"FriendsButtonIcon")
297 var infoHolder =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.33f), topRow.RectTransform), isHorizontal:
true,
Anchor.BottomLeft) { RelativeSpacing = 0.01f, Stretch =
false };
299 var clientNameHolder =
new GUILayoutGroup(
new RectTransform(
new Vector2(sidebarWidth, 1.0f), infoHolder.RectTransform)) { RelativeSpacing = 0.05f };
301 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.0f), clientNameHolder.RectTransform), TextManager.Get(
"YourName"), font: GUIStyle.SubHeadingFont);
302 ClientNameBox =
new GUITextBox(
new RectTransform(
new Vector2(1.0f, 0.5f), clientNameHolder.RectTransform),
"")
304 Text = MultiplayerPreferences.Instance.PlayerName,
309 var tabButtonHolder =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f - sidebarWidth - infoHolder.RelativeSpacing, 0.5f), infoHolder.RectTransform), isHorizontal:
true);
311 tabs[
TabEnum.All] =
new Tab(
TabEnum.All,
this, tabButtonHolder,
"");
312 tabs[
TabEnum.Favorites] =
new Tab(
TabEnum.Favorites,
this, tabButtonHolder,
"Data/favoriteservers.xml");
313 tabs[
TabEnum.Recent] =
new Tab(
TabEnum.Recent,
this, tabButtonHolder,
"Data/recentservers.xml");
319 var bottomRow =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 1.0f - topRow.RectTransform.RelativeSize.Y),
320 paddedFrame.RectTransform,
Anchor.CenterRight))
325 var serverListHolder =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 1.0f), bottomRow.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft)
327 OutlineColor = Color.Black
330 GUILayoutGroup serverListContainer =
null;
331 GUIFrame filtersHolder =
null;
335 filtersHolder =
new GUIFrame(
new RectTransform(
new Vector2(sidebarWidth, 1.0f), serverListHolder.RectTransform,
Anchor.Center), style:
null)
337 Color =
new Color(12, 14, 15, 255) * 0.5f,
338 OutlineColor = Color.Black
341 float elementHeight = 0.05f;
342 var filterTitle =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, elementHeight), filtersHolder.RectTransform), TextManager.Get(
"FilterServers"), font: GUIStyle.SubHeadingFont)
344 Padding = Vector4.Zero,
345 AutoScaleHorizontal =
true,
349 var searchHolder =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, elementHeight), filtersHolder.RectTransform) { RelativeOffset = new Vector2(0.0f, elementHeight) }, isHorizontal:
true) { Stretch =
true };
351 var searchTitle =
new GUITextBlock(
new RectTransform(
new Vector2(0.001f, 1.0f), searchHolder.RectTransform), TextManager.Get(
"Search") +
"...");
352 searchBox =
new GUITextBox(
new RectTransform(
new Vector2(1.0f, 1.0f), searchHolder.RectTransform),
"");
353 searchBox.OnSelected += (sender, userdata) => { searchTitle.Visible =
false; };
354 searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible =
true; };
355 searchBox.OnTextChanged += (txtBox, txt) => {
FilterServers();
return true; };
357 var filters =
new GUIListBox(
new RectTransform(
new Vector2(0.98f, 1.0f - elementHeight * 2), filtersHolder.RectTransform,
Anchor.BottomLeft))
359 ScrollBarVisible =
true,
360 Spacing = (int)(5 * GUI.Scale)
363 ternaryFilters =
new Dictionary<Identifier, GUIDropDown>();
364 filterTickBoxes =
new Dictionary<Identifier, GUITickBox>();
366 RectTransform createFilterRectT()
367 =>
new RectTransform(
new Vector2(1.0f, elementHeight), filters.Content.RectTransform);
369 GUITickBox addTickBox(Identifier key, LocalizedString text =
null,
bool defaultState =
false,
bool addTooltip =
false)
371 text ??= TextManager.Get(key);
372 var tickBox =
new GUITickBox(createFilterRectT(), text)
376 ToolTip = addTooltip ? text :
null,
377 OnSelected = (tickBox) =>
384 filterTickBoxes.Add(key, tickBox);
388 filterSameVersion = addTickBox(
"FilterSameVersion".ToIdentifier(), defaultState:
true);
389 filterPassword = addTickBox(
"FilterPassword".ToIdentifier());
390 filterFull = addTickBox(
"FilterFullServers".ToIdentifier());
391 filterEmpty = addTickBox(
"FilterEmptyServers".ToIdentifier());
392 filterOffensive = addTickBox(
"FilterOffensiveServers".ToIdentifier());
395 if (ServerLanguageOptions.Options.Any())
397 var languageKey =
"Language".ToIdentifier();
398 var allLanguagesKey =
"AllLanguages".ToIdentifier();
400 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.05f), filters.Content.RectTransform), TextManager.Get(languageKey), font: GUIStyle.SubHeadingFont)
405 languageDropdown =
new GUIDropDown(createFilterRectT(), selectMultiple:
true);
407 languageDropdown.AddItem(TextManager.Get(allLanguagesKey), allLanguagesKey);
408 var allTickbox = languageDropdown.ListBox.Content.FindChild(allLanguagesKey)?.GetChild<GUITickBox>();
411 new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.0f), languageDropdown.ListBox.Content.RectTransform)
413 MinSize = new Point(0, GUI.IntScaleCeiling(2))
416 Color = Color.DarkGray,
420 var selectedLanguages
421 = ServerListFilters.Instance.GetAttributeLanguageIdentifierArray(
423 Array.Empty<LanguageIdentifier>());
424 foreach (var (label, identifier, _) in ServerLanguageOptions.Options)
426 languageDropdown.AddItem(label, identifier);
429 if (!selectedLanguages.Any())
431 selectedLanguages = ServerLanguageOptions.Options.Select(o => o.Identifier).ToArray();
434 foreach (var lang
in selectedLanguages)
436 languageDropdown.SelectItem(lang);
439 if (ServerLanguageOptions.Options.All(o => selectedLanguages.Any(l => o.Identifier == l)))
441 languageDropdown.SelectItem(allLanguagesKey);
442 languageDropdown.Text = TextManager.Get(allLanguagesKey);
445 var langTickboxes = languageDropdown.ListBox.Content.Children
446 .Where(c => c.UserData is LanguageIdentifier)
447 .Select(c => c.GetChild<GUITickBox>())
450 bool inSelectedCall =
false;
451 languageDropdown.AfterSelected = (_, userData) =>
453 if (inSelectedCall) {
return true; }
456 inSelectedCall =
true;
458 if (Equals(allLanguagesKey, userData))
460 foreach (var tb
in langTickboxes)
462 tb.Selected = allTickbox.Selected;
466 bool noneSelected = langTickboxes.All(tb => !tb.Selected);
467 bool allSelected = langTickboxes.All(tb => tb.Selected);
469 if (allSelected != allTickbox.Selected)
471 allTickbox.Selected = allSelected;
476 languageDropdown.Text = TextManager.Get(allLanguagesKey);
478 else if (noneSelected)
480 languageDropdown.Text = TextManager.Get(
"None");
483 var languages = languageDropdown.SelectedDataMultiple.OfType<LanguageIdentifier>();
485 ServerListFilters.Instance.SetAttribute(languageKey,
string.Join(
", ", languages));
486 GameSettings.SaveCurrentConfig();
491 inSelectedCall =
false;
498 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.05f), filters.Content.RectTransform), TextManager.Get(
"servertags"), font: GUIStyle.SubHeadingFont)
503 AddTernaryFilter(filters.Content.RectTransform, elementHeight,
"karma".ToIdentifier(), (value) => { filterKarmaValue = value; });
504 AddTernaryFilter(filters.Content.RectTransform, elementHeight,
"traitors".ToIdentifier(), (value) => { filterTraitorValue = value; });
505 AddTernaryFilter(filters.Content.RectTransform, elementHeight,
"friendlyfire".ToIdentifier(), (value) => { filterFriendlyFireValue = value; });
506 AddTernaryFilter(filters.Content.RectTransform, elementHeight,
"voip".ToIdentifier(), (value) => { filterVoipValue = value; });
507 AddTernaryFilter(filters.Content.RectTransform, elementHeight,
"modded".ToIdentifier(), (value) => { filterModdedValue = value; });
510 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.05f), filters.Content.RectTransform), TextManager.Get(
"ServerSettingsPlayStyle"), font: GUIStyle.SubHeadingFont)
515 playStyleTickBoxes =
new Dictionary<Identifier, GUITickBox>();
518 var selectionTick = addTickBox($
"servertag.{playStyle}".ToIdentifier(), defaultState:
true, addTooltip:
true);
519 selectionTick.UserData = playStyle;
520 playStyleTickBoxes.Add($
"servertag.{playStyle}".ToIdentifier(), selectionTick);
524 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.05f), filters.Content.RectTransform), TextManager.Get(
"gamemode"), font: GUIStyle.SubHeadingFont) { CanBeFocused =
false };
526 gameModeTickBoxes =
new Dictionary<Identifier, GUITickBox>();
527 foreach (GameModePreset mode
in GameModePreset.List)
529 if (mode.IsSinglePlayer) {
continue; }
531 var selectionTick = addTickBox(mode.Identifier, mode.Name, defaultState:
true, addTooltip:
true);
532 selectionTick.UserData = mode.Identifier;
533 gameModeTickBoxes.Add(mode.Identifier, selectionTick);
536 filters.Content.RectTransform.SizeChanged += () =>
538 filters.Content.RectTransform.RecalculateChildren(
true,
true);
539 filterTickBoxes.ForEach(t => t.Value.Text = t.Value.UserData is LocalizedString lStr ? lStr : t.Value.UserData.ToString());
540 gameModeTickBoxes.ForEach(tb => tb.Value.Text = tb.Value.ToolTip);
541 playStyleTickBoxes.ForEach(tb => tb.Value.Text = tb.Value.ToolTip);
542 GUITextBlock.AutoScaleAndNormalize(
543 filterTickBoxes.Values.Select(tb => tb.TextBlock)
544 .Concat(ternaryFilters.Values.Select(dd => dd.Parent.GetChild<GUITextBlock>())),
546 if (filterTickBoxes.Values.First().TextBlock.TextScale < 0.8f)
548 filterTickBoxes.ForEach(t => t.Value.TextBlock.TextScale = 1.0f);
549 filterTickBoxes.ForEach(t => t.Value.TextBlock.Text = ToolBox.LimitString(t.Value.TextBlock.Text, t.Value.TextBlock.Font, (
int)(filters.Content.Rect.Width * 0.8f)));
555 serverListContainer =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 1.0f), serverListHolder.RectTransform)) { Stretch =
true };
557 labelHolder =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.05f), serverListContainer.RectTransform) { MinSize = new Point(0, 15) },
558 isHorizontal:
true, childAnchor:
Anchor.BottomLeft)
563 foreach (var column
in columns.Values)
565 var label = TextManager.Get(column.Label.ToString());
566 var btn =
new GUIButton(
new RectTransform(
new Vector2(column.RelativeWidth, 1.0f), labelHolder.RectTransform),
567 text: label, textAlignment: Alignment.Center, style:
"GUIButtonSmall")
571 UserData = column.Label,
575 labelTexts.Add(btn.TextBlock);
577 GUIImage arrowImg(
object userData, SpriteEffects sprEffects)
578 =>
new GUIImage(
new RectTransform(
new Vector2(0.5f, 0.3f), btn.RectTransform,
Anchor.BottomCenter, scaleBasis:
ScaleBasis.BothHeight), style:
"GUIButtonVerticalArrow", scaleToFit:
true)
580 CanBeFocused =
false,
582 SpriteEffects = sprEffects,
586 arrowImg(
"arrowup", SpriteEffects.None);
587 arrowImg(
"arrowdown", SpriteEffects.FlipVertically);
590 serverList =
new GUIListBox(
new RectTransform(
new Vector2(1.0f, 1.0f), serverListContainer.RectTransform,
Anchor.Center))
592 PlaySoundOnSelect =
true,
593 ScrollBarVisible =
true,
594 OnSelected = (btn, obj) =>
596 if (GUI.MouseOn is GUIButton) {
return false; }
597 if (obj is not
ServerInfo serverInfo) {
return false; }
600 selectedServer = Option<ServerInfo>.Some(serverInfo);
601 if (!serverPreviewContainer.
Visible)
604 serverPreviewContainer.
Visible =
true;
607 serverInfo.CreatePreviewWindow(serverPreview.
Content);
611 btn.Children.ForEach(c => c.SpriteEffects = serverPreviewContainer.
Visible ? SpriteEffects.None : SpriteEffects.FlipHorizontally);
617 serverPreviewContainer =
new GUIFrame(
new RectTransform(
new Vector2(sidebarWidth, 1.0f), serverListHolder.RectTransform,
Anchor.Center), style:
null)
619 Color =
new Color(12, 14, 15, 255) * 0.5f,
620 OutlineColor = Color.Black,
621 IgnoreLayoutGroups =
true
623 serverPreview =
new GUIListBox(
new RectTransform(Vector2.One, serverPreviewContainer.
RectTransform,
Anchor.Center))
625 Padding = Vector4.One * 10 * GUI.Scale,
627 OnSelected = (component, o) =>
false
630 panelAnimator =
new PanelAnimator(
new RectTransform(Vector2.One, serverListHolder.RectTransform),
633 serverPreviewContainer);
637 new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.02f), bottomRow.RectTransform), style:
null);
639 var buttonContainer =
new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 0.075f), bottomRow.RectTransform,
Anchor.Center), isHorizontal:
true)
641 RelativeSpacing = 0.02f,
645 GUIButton button =
new GUIButton(
new RectTransform(
new Vector2(0.25f, 0.9f), buttonContainer.RectTransform),
646 TextManager.Get(
"Back"))
648 OnClicked = GameMain.MainMenuScreen.ReturnToMainMenu
651 scanServersButton =
new GUIButton(
new RectTransform(
new Vector2(0.25f, 0.9f), buttonContainer.RectTransform),
652 TextManager.Get(
"ServerListRefresh"))
654 OnClicked = (btn, userdata) => { RefreshServers();
return true; }
657 var directJoinButton =
new GUIButton(
new RectTransform(
new Vector2(0.25f, 0.9f), buttonContainer.RectTransform),
658 TextManager.Get(
"serverlistdirectjoin"))
660 OnClicked = (btn, userdata) =>
669 ShowDirectJoinPrompt();
674 joinButton =
new GUIButton(
new RectTransform(
new Vector2(0.25f, 0.9f), buttonContainer.RectTransform),
675 TextManager.Get(
"ServerListJoin"))
677 OnClicked = (btn, userdata) =>
679 if (selectedServer.TryUnwrap(out var serverInfo))
681 JoinServer(serverInfo.Endpoints, serverInfo.ServerName);
688 buttonContainer.RectTransform.MinSize =
new Point(0, (
int)(buttonContainer.RectTransform.Children.Max(c => c.MinSize.Y) * 1.2f));
692 bottomRow.Recalculate();
693 serverListHolder.Recalculate();
694 serverListContainer.Recalculate();
695 labelHolder.RectTransform.MaxSize =
new Point(serverList.
Content.
Rect.Width,
int.MaxValue);
696 labelHolder.RectTransform.AbsoluteOffset =
new Point((
int)serverList.
Padding.X, 0);
697 labelHolder.Recalculate();
701 labelHolder.RectTransform.MaxSize =
new Point(serverList.
Content.
Rect.Width,
int.MaxValue);
702 labelHolder.RectTransform.AbsoluteOffset =
new Point((
int)serverList.
Padding.X, 0);
703 labelHolder.Recalculate();
704 foreach (GUITextBlock labelText
in labelTexts)
706 labelText.Text = ToolBox.LimitString(labelText.ToolTip, labelText.Font, labelText.Rect.Width);
710 button.SelectedColor = button.Color;
718 d.UserData is
ServerInfo existingServerInfo &&
720 if (existingElement ==
null)
722 AddToServerList(serverInfo);
726 existingElement.
UserData = serverInfo;
732 if (info.
Endpoints.First().Address.IsLocalHost) {
return; }
733 tabs[
TabEnum.Recent].AddOrUpdate(info);
738 => tabs[
TabEnum.Favorites].Contains(info);
742 tabs[
TabEnum.Favorites].AddOrUpdate(info);
743 tabs[
TabEnum.Favorites].Save();
748 tabs[
TabEnum.Favorites].Remove(info);
749 tabs[
TabEnum.Favorites].Save();
752 private bool SortList(
GUIButton button,
object obj)
754 if (obj is not ColumnLabel sortBy) {
return false; }
755 SortList(sortBy, toggle:
true);
759 private void SortList(ColumnLabel sortBy,
bool toggle)
761 if (labelHolder.GetChildByUserData(sortBy) is not GUIButton button) {
return; }
765 var arrowUp = button.GetChildByUserData(
"arrowup");
766 var arrowDown = button.GetChildByUserData(
"arrowdown");
769 foreach (var child
in button.Parent.Children)
773 child.GetChildByUserData(
"arrowup").Visible =
false;
774 child.GetChildByUserData(
"arrowdown").Visible =
false;
778 sortedAscending = arrowUp.Visible;
781 sortedAscending = !sortedAscending;
784 arrowUp.Visible = sortedAscending;
785 arrowDown.Visible = !sortedAscending;
788 if (c1.GUIComponent.UserData is not
ServerInfo s1) { return 0; }
789 if (c2.GUIComponent.UserData is not
ServerInfo s2) { return 0; }
790 return CompareServer(sortBy, s1, s2, sortedAscending);
796 serverPreviewContainer.
Visible =
false;
805 foreach (var child
in children)
807 if (child.GUIComponent.UserData is not
ServerInfo serverInfo2 || serverInfo.
Equals(serverInfo2)) {
continue; }
808 if (CompareServer(sortedBy, serverInfo, serverInfo2, sortedAscending) >= 0)
818 private static int CompareServer(ColumnLabel sortBy,
ServerInfo s1,
ServerInfo s2,
bool ascending)
822 bool s1HasPing = s1.
Ping.IsSome();
823 bool s2HasPing = s2.
Ping.IsSome();
824 if (s1HasPing != s2HasPing)
826 return s1HasPing ? -1 : 1;
829 int comparison = ascending ? 1 : -1;
832 case ColumnLabel.ServerListCompatible:
833 bool s1Compatible = NetworkMember.IsCompatible(GameMain.Version, s1.
GameVersion);
834 bool s2Compatible = NetworkMember.IsCompatible(GameMain.Version, s2.
GameVersion);
836 if (s1Compatible == s2Compatible) {
return 0; }
837 return (s1Compatible ? -1 : 1) * comparison;
838 case ColumnLabel.ServerListHasPassword:
841 case ColumnLabel.ServerListName:
843 return string.Compare(s1.
ServerName, s2.
ServerName, StringComparison.CurrentCulture) * comparison;
844 case ColumnLabel.ServerListRoundStarted:
847 case ColumnLabel.ServerListPlayers:
849 case ColumnLabel.ServerListPing:
850 return (s1.
Ping.TryUnwrap(out var s1Ping), s2.
Ping.TryUnwrap(out var s2Ping))
switch
853 (
true,
true) => s2Ping.CompareTo(s1Ping),
866 if (
string.IsNullOrEmpty(ClientNameBox.Text))
868 TaskPool.Add(
"GetDefaultUserName",
869 GetDefaultUserName(),
872 if (!t.TryGetResult(out
string name)) { return; }
873 if (ClientNameBox.Text.IsNullOrEmpty())
875 ClientNameBox.Text = name;
876 string nameWithoutInvisibleSymbols = string.Empty;
877 foreach (char c in ClientNameBox.Text)
879 Vector2 size = ClientNameBox.Font.MeasureChar(c);
880 if (size.X > 0 && size.Y > 0)
882 nameWithoutInvisibleSymbols += c;
885 if (nameWithoutInvisibleSymbols != ClientNameBox.Text)
887 MultiplayerPreferences.Instance.PlayerName = ClientNameBox.Text = nameWithoutInvisibleSymbols;
888 new GUIMessageBox(TextManager.Get(
"Warning"), TextManager.GetWithVariable(
"NameContainsInvisibleSymbols",
"[name]", nameWithoutInvisibleSymbols));
894 ClientNameBox.OnTextChanged += (textbox, text) =>
899 if (EosInterface.IdQueries.IsLoggedIntoEosConnect)
901 if (SteamManager.IsInitialized)
903 serverProvider =
new CompositeServerProvider(
905 new SteamDedicatedServerProvider(),
906 new SteamP2PServerProvider());
913 else if (SteamManager.IsInitialized)
915 serverProvider =
new CompositeServerProvider(
916 new SteamDedicatedServerProvider(),
917 new SteamP2PServerProvider());
921 serverProvider =
null;
924 Steamworks.SteamMatchmaking.ResetActions();
926 selectedTab = TabEnum.All;
927 GameMain.ServerListScreen.LoadServerFilters();
928 if (GameSettings.CurrentConfig.ShowOffensiveServerPrompt)
930 var filterOffensivePrompt =
new GUIMessageBox(
string.Empty, TextManager.Get(
"FilterOffensiveServersPrompt"),
new LocalizedString[] { TextManager.Get(
"yes"), TextManager.Get(
"no") });
931 filterOffensivePrompt.Buttons[0].OnClicked = (btn, userData) =>
933 filterOffensive.Selected =
true;
934 filterOffensivePrompt.Close();
937 filterOffensivePrompt.Buttons[1].OnClicked = filterOffensivePrompt.Close;
939 var config = GameSettings.CurrentConfig;
940 config.ShowOffensiveServerPrompt =
false;
941 GameSettings.SetCurrentConfig(config);
944 if (GameMain.Client !=
null)
946 GameMain.Client.Quit();
947 GameMain.Client =
null;
956 serverProvider?.Cancel();
957 GameSettings.SaveCurrentConfig();
960 public override void Update(
double deltaTime)
962 base.Update(deltaTime);
966 scanServersButton.
Enabled = (DateTime.Now - lastRefreshTime) >= AllowedRefreshInterval;
971 RemoveMsgFromServerList(MsgUserData.NoMatchingServers);
975 child.
Visible = ShouldShowServer(serverInfo);
980 PutMsgInServerList(MsgUserData.NoMatchingServers);
985 private bool AllLanguagesVisible
989 if (languageDropdown is
null) {
return true; }
992 int tickBoxCount = languageDropdown.ListBox.Content.CountChildren - 1;
993 int selectedCount = languageDropdown.SelectedIndexMultiple.Count();
995 return selectedCount >= tickBoxCount;
999 private bool ShouldShowServer(
ServerInfo serverInfo)
1004 if (ToolBox.VersionNewerIgnoreRevision(GameMain.Version, serverInfo.
GameVersion))
1009 if (SpamServerFilters.IsFiltered(serverInfo)) {
return false; }
1011 if (!
string.IsNullOrEmpty(searchBox.Text) && !serverInfo.
ServerName.Contains(searchBox.Text, StringComparison.OrdinalIgnoreCase)) {
return false; }
1013 if (filterSameVersion.Selected)
1015 if (!NetworkMember.IsCompatible(serverInfo.
GameVersion, GameMain.Version)) {
return false; }
1017 if (filterPassword.Selected)
1021 if (filterFull.Selected)
1025 if (filterEmpty.Selected)
1027 if (serverInfo.
PlayerCount <= 0) {
return false; }
1029 if (filterOffensive.Selected)
1031 if (ForbiddenWordFilter.IsForbidden(serverInfo.
ServerName)) {
return false; }
1034 if (filterKarmaValue != TernaryOption.Any)
1036 if (serverInfo.
KarmaEnabled != (filterKarmaValue == TernaryOption.Enabled)) {
return false; }
1038 if (filterFriendlyFireValue != TernaryOption.Any)
1040 if (serverInfo.
FriendlyFireEnabled != (filterFriendlyFireValue == TernaryOption.Enabled)) {
return false; }
1042 if (filterTraitorValue != TernaryOption.Any)
1044 if ((serverInfo.
TraitorProbability > 0.0f) != (filterTraitorValue == TernaryOption.Enabled))
1049 if (filterVoipValue != TernaryOption.Any)
1051 if (serverInfo.
VoipEnabled != (filterVoipValue == TernaryOption.Enabled)) {
return false; }
1053 if (filterModdedValue != TernaryOption.Any)
1055 if (serverInfo.
IsModded != (filterModdedValue == TernaryOption.Enabled)) {
return false; }
1058 foreach (GUITickBox tickBox
in playStyleTickBoxes.Values)
1060 var playStyle = (
PlayStyle)tickBox.UserData;
1061 if (!tickBox.Selected && serverInfo.
PlayStyle == playStyle)
1067 if (!AllLanguagesVisible)
1069 if (!languageDropdown.SelectedDataMultiple.OfType<LanguageIdentifier>().Contains(serverInfo.
Language))
1075 foreach (GUITickBox tickBox
in gameModeTickBoxes.Values)
1077 var gameMode = (Identifier)tickBox.UserData;
1078 if (!tickBox.Selected && !serverInfo.
GameMode.IsEmpty && serverInfo.
GameMode == gameMode)
1087 private void ShowDirectJoinPrompt()
1089 var msgBox =
new GUIMessageBox(TextManager.Get(
"ServerListDirectJoin"),
"",
1090 new LocalizedString[] { TextManager.Get(
"ServerListJoin"), TextManager.Get(
"AddToFavorites"), TextManager.Get(
"Cancel") },
1091 relativeSize:
new Vector2(0.25f, 0.2f), minSize:
new Point(400, 150));
1092 msgBox.Content.ChildAnchor =
Anchor.TopCenter;
1094 var content =
new GUILayoutGroup(
new RectTransform(
new Vector2(0.8f, 0.5f), msgBox.Content.RectTransform), childAnchor:
Anchor.TopCenter)
1096 IgnoreLayoutGroups =
false,
1098 RelativeSpacing = 0.05f
1101 new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.5f), content.RectTransform),
1102 SteamManager.IsInitialized ? TextManager.Get(
"ServerEndpoint") : TextManager.Get(
"ServerIP"), textAlignment: Alignment.Center);
1103 var endpointBox =
new GUITextBox(
new RectTransform(
new Vector2(1.0f, 0.5f), content.RectTransform));
1105 content.RectTransform.NonScaledSize =
new Point(content.Rect.Width, (
int)(content.RectTransform.Children.Sum(c => c.Rect.Height)));
1106 content.RectTransform.IsFixedSize =
true;
1107 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));
1109 var okButton = msgBox.Buttons[0];
1110 okButton.Enabled =
false;
1111 okButton.OnClicked = (btn, userdata) =>
1113 if (
Endpoint.
Parse(endpointBox.Text).TryUnwrap(out var endpoint))
1117 new GUIMessageBox(TextManager.Get(
"error"), TextManager.Get(
"CannotJoinSteamServer.SteamNotInitialized"));
1121 JoinServer(endpoint.ToEnumerable().ToImmutableArray(),
"");
1126 JoinServer(((
Endpoint)lidgrenEndpoint).ToEnumerable().ToImmutableArray(),
"");
1130 new GUIMessageBox(TextManager.Get(
"error"), TextManager.GetWithVariable(
"invalidipaddress",
"[serverip]:[port]", endpointBox.Text));
1131 endpointBox.Flash();
1137 var favoriteButton = msgBox.Buttons[1];
1138 favoriteButton.Enabled =
false;
1139 favoriteButton.OnClicked = (button, userdata) =>
1141 if (!
Endpoint.
Parse(endpointBox.Text).TryUnwrap(out var endpoint)) {
return false; }
1145 ServerName =
"Server",
1146 GameVersion = GameMain.Version
1151 && info.
Equals(serverInfo));
1153 if (serverFrame !=
null)
1159 AddToServerList(serverInfo);
1162 AddToFavoriteServers(serverInfo);
1164 selectedTab = TabEnum.Favorites;
1171 var cancelButton = msgBox.Buttons[2];
1172 cancelButton.OnClicked = msgBox.Close;
1174 endpointBox.OnTextChanged += (textBox, text) =>
1176 okButton.Enabled = favoriteButton.Enabled = !
string.IsNullOrEmpty(text);
1181 private void RemoveMsgFromServerList()
1184 .Where(c => c.UserData is MsgUserData)
1188 private void RemoveMsgFromServerList(MsgUserData userData)
1193 private void PutMsgInServerList(MsgUserData userData)
1195 RemoveMsgFromServerList();
1197 TextManager.Get(userData.ToString()), textAlignment: Alignment.Center)
1199 CanBeFocused =
false,
1204 private void RefreshServers()
1206 lastRefreshTime = DateTime.Now;
1207 serverProvider?.Cancel();
1208 currentServerDataRecvCallbackObj =
null;
1210 PingUtils.QueryPingData();
1212 tabs[TabEnum.All].Clear();
1217 selectedServer = Option.None;
1219 if (selectedTab == TabEnum.All)
1221 PutMsgInServerList(MsgUserData.RefreshingServerList);
1225 var servers = tabs[selectedTab].Servers.ToArray();
1226 foreach (var server
in servers)
1228 server.Ping = Option<int>.None();
1229 AddToServerList(server, skipPing:
true);
1234 PutMsgInServerList(MsgUserData.NoServers);
1239 var (onServerDataReceived, onQueryCompleted) = MakeServerQueryCallbacks();
1240 serverProvider?.RetrieveServers(onServerDataReceived, onQueryCompleted);
1243 private GUIComponent FindFrameMatchingServerInfo(
ServerInfo serverInfo)
1245 bool matches(GUIComponent c)
1247 && info.
Equals(serverInfo);
1252 DebugConsole.ThrowError($
"There are several entries in the server list for endpoints {string.Join(",
", serverInfo.Endpoints)}");
1259 private object currentServerDataRecvCallbackObj =
null;
1260 private (Action<ServerInfo, ServerProvider> OnServerDataReceived, Action OnQueryCompleted) MakeServerQueryCallbacks()
1262 var uniqueObject =
new object();
1263 currentServerDataRecvCallbackObj = uniqueObject;
1265 bool shouldRunCallback()
1271 return ReferenceEquals(currentServerDataRecvCallbackObj, uniqueObject);
1275 (serverInfo, serverProvider) =>
1277 if (!shouldRunCallback()) {
return; }
1280 && EosInterface.IdQueries.IsLoggedIntoEosConnect)
1282 if (serverInfo.EosCrossplay)
1290 if (selectedTab == TabEnum.All)
1292 AddToServerList(serverInfo);
1296 if (FindFrameMatchingServerInfo(serverInfo) == null) { return; }
1297 UpdateServerInfoUI(serverInfo);
1298 PingUtils.GetServerPing(serverInfo, UpdateServerInfoUI);
1303 if (shouldRunCallback()) { ServerQueryFinished(); }
1308 private void AddToServerList(
ServerInfo serverInfo,
bool skipPing =
false)
1310 const int MaxAllowedPlayers = 1000;
1311 const int MaxAllowedSimilarServers = 10;
1312 const float MinSimilarityPercentage = 0.8f;
1314 if (
string.IsNullOrWhiteSpace(serverInfo.
ServerName)) {
return; }
1319 if (serverInfo.
MaxPlayers > MaxAllowedPlayers) {
return; }
1321 int similarServerCount = 0;
1322 string serverInfoStr = getServerInfoStr(serverInfo);
1325 if (!serverElement.Visible) {
continue; }
1326 if (serverElement.UserData is not
ServerInfo otherServer || otherServer == serverInfo) {
continue; }
1327 if (ToolBox.LevenshteinDistance(serverInfoStr, getServerInfoStr(otherServer)) < serverInfoStr.Length * (1.0f - MinSimilarityPercentage))
1329 similarServerCount++;
1330 if (similarServerCount > MaxAllowedSimilarServers)
1332 DebugConsole.Log($
"Server {serverInfo.ServerName} seems to be almost identical to {otherServer.ServerName}. Hiding as a potential spam server.");
1337 if (similarServerCount > MaxAllowedSimilarServers) {
return; }
1339 static string getServerInfoStr(
ServerInfo serverInfo)
1342 if (str.Length > 200) {
return str.Substring(0, 200); }
1346 RemoveMsgFromServerList(MsgUserData.RefreshingServerList);
1347 RemoveMsgFromServerList(MsgUserData.NoServers);
1348 var serverFrame =
new GUIFrame(
new RectTransform(
new Vector2(1.0f, 0.06f), serverList.
Content.
RectTransform) { MinSize = new Point(0, 35) },
1349 style:
"ListBoxElement")
1351 UserData = serverInfo,
1354 serverFrame.OnSecondaryClicked += (_, data) =>
1356 if (data is not
ServerInfo info) {
return false; }
1357 CreateContextMenu(info);
1361 new GUILayoutGroup(
new RectTransform(
new Vector2(1.0f, 1.0f), serverFrame.RectTransform,
Anchor.Center), isHorizontal:
true, childAnchor:
Anchor.CenterLeft)
1365 UpdateServerInfoUI(serverInfo);
1366 if (!skipPing) { PingUtils.GetServerPing(serverInfo, UpdateServerInfoUI); }
1369 private static readonly Vector2 confirmPopupSize =
new Vector2(0.2f, 0.2625f);
1370 private static readonly Point confirmPopupMinSize =
new Point(300, 300);
1372 private void CreateContextMenu(
ServerInfo info)
1374 var favoriteOption =
new ContextMenuOption(IsFavorite(info) ?
"removefromfavorites" :
"addtofavorites", isEnabled:
true, () =>
1376 if (IsFavorite(info))
1378 RemoveFromFavoriteServers(info);
1382 AddToFavoriteServers(info);
1386 var reportOption =
new ContextMenuOption(
"reportserver", isEnabled:
true, () => { CreateReportPrompt(info); });
1387 var filterOption =
new ContextMenuOption(
"filterserver", isEnabled:
true, () =>
1389 CreateFilterServerPrompt(info);
1392 Tooltip = TextManager.Get(
"filterservertooltip")
1395 GUIContextMenu.CreateContextMenu(favoriteOption, filterOption, reportOption);
1400 GUI.AskForConfirmation(
1401 header: TextManager.Get(
"filterserver"),
1402 body: TextManager.GetWithVariables(
"filterserverconfirm", (
"[server]", info.
ServerName), (
"[filepath]", SpamServerFilter.SavePath)),
1405 SpamServerFilters.AddServerToLocalSpamList(info);
1407 if (GameMain.ServerListScreen is not { } serverListScreen) {
return; }
1409 if (serverListScreen.selectedServer.TryUnwrap(out var selectedServer) && selectedServer.Equals(info))
1411 serverListScreen.HideServerPreview();
1413 serverListScreen.FilterServers();
1414 }, relativeSize: confirmPopupSize, minSize: confirmPopupMinSize);
1417 private enum ReportReason
1426 if (!GameAnalyticsManager.SendUserStatistics)
1428 GUI.NotifyPrompt(TextManager.Get(
"reportserver"), TextManager.Get(
"reportserverdisabled"));
1433 headerText: TextManager.Get(
"reportserver"),
1435 relativeSize:
new Vector2(0.2f, 0.4f),
1436 minSize:
new Point(380, 430),
1441 new GUITextBlock(
new RectTransform(
new Vector2(1f, 0.3f), layout.RectTransform), TextManager.GetWithVariable(
"reportserverexplanation",
"[server]", info.
ServerName), wrap:
true)
1443 ToolTip = TextManager.Get(
"reportserverprompttooltip")
1448 var enums = Enum.GetValues<ReportReason>();
1449 foreach (ReportReason reason
in enums)
1451 new GUITickBox(
new RectTransform(
new Vector2(1f, 1f / enums.Length), listBox.Content.RectTransform), TextManager.Get($
"reportreason.{reason}"))
1465 var reportAndHideButton =
new GUIButton(
new RectTransform(
new Vector2(1f, 0.333f), buttonLayout.RectTransform), TextManager.Get(
"reportoption.reportandhide"))
1468 OnClicked = (_, _) =>
1470 CreateFilterServerPrompt(info);
1475 var reportButton =
new GUIButton(
new RectTransform(
new Vector2(1f, 0.333f), buttonLayout.RectTransform), TextManager.Get(
"reportoption.report"))
1478 OnClicked = (_, _) =>
1480 ReportServer(info, GetUserSelectedReasons());
1486 new GUIButton(
new RectTransform(
new Vector2(1f, 0.333f), buttonLayout.RectTransform), TextManager.Get(
"cancel"))
1488 OnClicked = (_, _) =>
1497 child.OnSelected += _ =>
1499 reportAndHideButton.
Enabled = reportButton.Enabled = GetUserSelectedReasons().Any();
1504 IEnumerable<ReportReason> GetUserSelectedReasons()
1505 => listBox.Content.Children
1506 .Where(
static c => c.UserData is ReportReason && c.Selected)
1507 .Select(
static c => (ReportReason)c.UserData).ToArray();
1510 private static void ReportServer(
ServerInfo info, IEnumerable<ReportReason> reasons)
1512 if (!reasons.Any()) {
return; }
1513 GameAnalyticsManager.AddErrorEvent(GameAnalyticsManager.ErrorSeverity.Info, $
"[Spam] Reported server: Name: \"{info.ServerName}\", Message: \"{info.ServerMessage}\", Endpoint: \"{info.Endpoints.First().StringRepresentation}\". Reason: \"{string.Join(",
", reasons)}\".");
1516 private void UpdateServerInfoUI(
ServerInfo serverInfo)
1518 var serverFrame = FindFrameMatchingServerInfo(serverInfo);
1519 if (serverFrame ==
null) {
return; }
1521 serverFrame.UserData = serverInfo;
1523 var serverContent = serverFrame.Children.First() as GUILayoutGroup;
1524 serverContent.ClearChildren();
1526 Dictionary<ColumnLabel, GUIFrame> sections =
new Dictionary<ColumnLabel, GUIFrame>();
1527 foreach (ColumnLabel label
in Enum.GetValues(typeof(ColumnLabel)))
1531 new RectTransform(
new Vector2(columns[label].RelativeWidth, 1.0f), serverContent.RectTransform),
1535 void disableElementFocus()
1537 sections.Values.ForEach(c =>
1539 c.CanBeFocused =
false;
1540 c.Children.First().CanBeFocused =
false;
1544 RectTransform columnRT(ColumnLabel label,
float scale = 0.95f)
1545 =>
new RectTransform(Vector2.One * scale, sections[label].RectTransform,
Anchor.Center);
1547 void sectionTooltip(ColumnLabel label, RichString toolTip)
1549 var section = sections[label];
1550 section.CanBeFocused =
true;
1551 section.ToolTip = toolTip;
1554 var compatibleBox =
new GUITickBox(columnRT(ColumnLabel.ServerListCompatible), label:
"")
1556 CanBeFocused =
false,
1558 NetworkMember.IsCompatible(GameMain.Version, serverInfo.
GameVersion),
1559 UserData =
"compatible"
1562 var passwordBox =
new GUITickBox(columnRT(ColumnLabel.ServerListHasPassword, scale: 0.6f), label:
"", style:
"GUIServerListPasswordTickBox")
1565 UserData =
"password",
1566 CanBeFocused =
false
1568 sectionTooltip(ColumnLabel.ServerListHasPassword,
1569 TextManager.Get((serverInfo.
HasPassword) ?
"ServerListHasPassword" :
"FilterPassword"));
1571 var serverName =
new GUITextBlock(columnRT(ColumnLabel.ServerListName),
1573 $
"[{serverInfo.Endpoints.First().GetType().Name}] " +
1576 style:
"GUIServerListTextBox") { CanBeFocused =
false };
1580 serverName.TextColor = GUIStyle.ModdedServerColor;
1583 new GUITickBox(columnRT(ColumnLabel.ServerListRoundStarted), label:
"")
1586 CanBeFocused =
false
1588 sectionTooltip(ColumnLabel.ServerListRoundStarted,
1589 TextManager.Get(serverInfo.
GameStarted ?
"ServerListRoundStarted" :
"ServerListRoundNotStarted"));
1591 var serverPlayers =
new GUITextBlock(columnRT(ColumnLabel.ServerListPlayers),
1592 $
"{serverInfo.PlayerCount}/{serverInfo.MaxPlayers}", style:
"GUIServerListTextBox", textAlignment: Alignment.Right)
1594 ToolTip = TextManager.Get(
"ServerListPlayers")
1597 var serverPingText =
new GUITextBlock(columnRT(ColumnLabel.ServerListPing),
"?",
1598 style:
"GUIServerListTextBox", textColor: Color.White * 0.5f, textAlignment: Alignment.Right)
1600 ToolTip = TextManager.Get(
"ServerListPing")
1603 if (serverInfo.
Ping.TryUnwrap(out var ping))
1605 serverPingText.Text = ping.ToString();
1606 serverPingText.TextColor = GetPingTextColor(ping);
1611 serverPingText.Text =
"-";
1612 serverPingText.ToolTip = TextManager.Get(
"EosPingUnavailable");
1613 serverPingText.TextAlignment = Alignment.Center;
1617 serverPingText.Text =
"?";
1618 serverPingText.TextColor = Color.DarkRed;
1621 LocalizedString toolTip =
"";
1624 toolTip = TextManager.Get(
"ServerOffline");
1625 serverName.TextColor *= 0.8f;
1626 serverPlayers.TextColor *= 0.8f;
1630 compatibleBox.Selected =
false;
1631 new GUITextBlock(
new RectTransform(
new Vector2(0.8f, 0.8f), compatibleBox.Box.RectTransform,
Anchor.Center),
1632 " ? ", GUIStyle.Orange * 0.85f, textAlignment: Alignment.Center)
1634 ToolTip = TextManager.Get(
"ServerListUnknownContentPackage")
1637 else if (!compatibleBox.Selected)
1641 toolTip = TextManager.GetWithVariable(
"ServerListIncompatibleVersion",
"[version]", serverInfo.
GameVersion.ToString());
1644 int maxIncompatibleToList = 10;
1645 List<LocalizedString> incompatibleModNames =
new List<LocalizedString>();
1648 bool listAsIncompatible = !ContentPackageManager.EnabledPackages.All.Any(cp => cp.Hash.StringRepresentation == contentPackage.Hash);
1649 if (listAsIncompatible)
1651 incompatibleModNames.Add(TextManager.GetWithVariables(
"ModNameAndHashFormat",
1652 (
"[name]", contentPackage.Name),
1653 (
"[hash]", Md5Hash.GetShortHash(contentPackage.Hash))));
1656 if (incompatibleModNames.Any())
1658 toolTip +=
'\n' + TextManager.Get(
"ModDownloadHeader") +
"\n" +
string.Join(
", ", incompatibleModNames.Take(maxIncompatibleToList));
1659 if (incompatibleModNames.Count > maxIncompatibleToList)
1661 toolTip +=
'\n' + TextManager.GetWithVariable(
"workshopitemdownloadprompttruncated",
"[number]", (incompatibleModNames.Count - maxIncompatibleToList).ToString());
1665 serverName.TextColor *= 0.5f;
1666 serverPlayers.TextColor *= 0.5f;
1672 if (ContentPackageManager.EnabledPackages.All.None(cp => cp.Hash.StringRepresentation == contentPackage.Hash))
1674 if (toolTip !=
"") { toolTip +=
"\n"; }
1675 toolTip += TextManager.GetWithVariable(
"ServerListIncompatibleContentPackageWorkshopAvailable",
"[contentpackage]", contentPackage.Name);
1680 disableElementFocus();
1682 string separator = toolTip.IsNullOrWhiteSpace() ?
"" :
"\n\n";
1683 serverFrame.ToolTip = RichString.Rich(toolTip + separator + $
"‖color:gui.blue‖{TextManager.GetWithVariable("serverlisttooltip
", "[button]
", PlayerInput.SecondaryMouseLabel)}‖end‖");
1685 foreach (var section
in sections.Values)
1687 var child = section.Children.First();
1688 child.RectTransform.ScaleBasis
1699 bool isDirty =
true;
1700 void markAsDirty() => isDirty =
true;
1701 serverContent.GetAllChildren().ForEach(c =>
1703 c.RectTransform.ResetSizeChanged();
1704 c.RectTransform.SizeChanged += markAsDirty;
1706 new GUICustomComponent(
new RectTransform(Vector2.Zero, serverContent.RectTransform), onUpdate: (_, __) =>
1708 if (serverFrame.MouseRect.Height <= 0 || !isDirty) { return; }
1709 serverContent.GetAllChildren().ForEach(c =>
1713 case GUITextBlock textBlock:
1714 textBlock.SetTextPos();
1716 case GUITickBox tickBox:
1717 tickBox.ResizeBox();
1721 serverName.Text = ToolBox.LimitString(serverInfo.
ServerName, serverName.Font, serverName.Rect.Width);
1726 serverContent.Recalculate();
1728 if (tabs[TabEnum.Favorites].Contains(serverInfo))
1730 AddToFavoriteServers(serverInfo);
1733 InsertServer(serverInfo, serverFrame);
1737 private void ServerQueryFinished()
1739 currentServerDataRecvCallbackObj =
null;
1742 PutMsgInServerList(MsgUserData.NoServers);
1746 PutMsgInServerList(MsgUserData.NoMatchingServers);
1750 public void JoinServer(ImmutableArray<Endpoint> endpoints,
string serverName)
1752 if (
string.IsNullOrWhiteSpace(ClientNameBox.Text))
1754 ClientNameBox.Flash();
1755 ClientNameBox.Select();
1761 GameSettings.SaveCurrentConfig();
1765 TaskPool.Add(
"GetDefaultUserName",
1766 GetDefaultUserName(),
1769 if (!t.TryGetResult(out
string name)) { return; }
1778 void startClient(
string name)
1789 DebugConsole.ThrowError(
"Failed to start the client", e);
1795 private static Color GetPingTextColor(
int ping)
1797 if (ping < 0) {
return Color.DarkRed; }
1798 return ToolBox.GradientLerp(ping / 200.0f, GUIStyle.Green, GUIStyle.Orange, GUIStyle.Red);
1801 public override void Draw(
double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
1803 graphics.Clear(Color.CornflowerBlue);
1807 GUI.Draw(Cam, spriteBatch);
1818 if (loadingServerFilters) {
return; }
1819 foreach (KeyValuePair<Identifier, GUITickBox> filterBox
in filterTickBoxes)
1823 foreach (KeyValuePair<Identifier, GUIDropDown> ternaryFilter
in ternaryFilters)
1827 GameSettings.SaveCurrentConfig();
1830 private bool loadingServerFilters;
1833 loadingServerFilters =
true;
1834 XDocument currentConfigDoc = XMLExtensions.TryLoadXml(GameSettings.PlayerConfigPath);
1836 foreach (KeyValuePair<Identifier, GUITickBox> filterBox
in filterTickBoxes)
1838 filterBox.Value.Selected =
1841 foreach (KeyValuePair<Identifier, GUIDropDown> ternaryFilter
in ternaryFilters)
1843 TernaryOption ternaryOption =
1846 (TernaryOption)ternaryFilter.Value.SelectedData);
1848 var child = ternaryFilter.Value.ListBox.Content.GetChildByUserData(ternaryOption);
1849 ternaryFilter.Value.Select(ternaryFilter.Value.ListBox.Content.GetChildIndex(child));
1851 loadingServerFilters =
false;
virtual void RemoveChild(GUIComponent child)
virtual void AddToGUIUpdateList(bool ignoreChildren=false, int order=0)
virtual void ClearChildren()
GUIComponent FindChild(Func< GUIComponent, bool > predicate, bool recursive=false)
IEnumerable< GUIComponent > GetAllChildren()
Returns all child elements in the hierarchy.
RectTransform RectTransform
IEnumerable< GUIComponent > Children
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
void UpdateScrollBarSize()
override void ForceLayoutRecalculation()
override void ClearChildren()
void Select(int forcedCaretIndex=-1, bool ignoreSelectSound=false)
override void Flash(Color? color=null, float flashDuration=1.5f, bool useRectangleFlash=false, bool useCircularFlash=false, Vector2? flashRectOffset=null)
static RasterizerState ScissorTestEnable
Action ResolutionChanged
NOTE: Use very carefully. You need to ensure that you ALWAYS unsubscribe from this when you no longer...
static MainMenuScreen MainMenuScreen
void DrawBackground(GraphicsDevice graphics, SpriteBatch spriteBatch)
static MultiplayerPreferences Instance
static Option< Endpoint > Parse(string str)
static Option< LidgrenEndpoint > ParseFromWithHostNameCheck(string endpointStr, bool tryParseHostName)
ImmutableArray< Endpoint > Endpoints
ImmutableArray< ServerListContentPackageInfo > ContentPackages
static Option< ServerInfo > FromXElement(XElement element)
override bool Equals(object? obj)
LanguageIdentifier Language
static ServerListFilters Instance
static void Init(XElement? elem)
bool GetAttributeBool(Identifier key, bool def)
void SetAttribute(Identifier key, string val)
void AddToRecentServers(ServerInfo info)
bool IsFavorite(ServerInfo info)
void AddToFavoriteServers(ServerInfo info)
override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
void UpdateOrAddServerInfo(ServerInfo serverInfo)
override void AddToGUIUpdateList()
By default, submits the screen's main GUIFrame and, if requested upon construction,...
static void CreateFilterServerPrompt(ServerInfo info)
void StoreServerFilters()
void JoinServer(ImmutableArray< Endpoint > endpoints, string serverName)
void RemoveFromFavoriteServers(ServerInfo info)
static void CreateReportPrompt(ServerInfo info)
override void Update(double deltaTime)
override async Task< string > GetSelfUserName()
static ? SocialOverlay Instance
static readonly LocalizedString ShortcutBindText
IReadOnlyList< ServerInfo > Servers
readonly GUIButton Button
void AddOrUpdate(ServerInfo info)
Tab(TabEnum tabEnum, ServerListScreen serverListScreen, GUILayoutGroup tabber, string storage)
bool Remove(ServerInfo info)
bool Contains(ServerInfo info)