Client LuaCsForBarotrauma
ServerInfo.cs
1 #nullable enable
2 
4 using Microsoft.Xna.Framework;
5 using System;
6 using System.Collections.Generic;
7 using System.Collections.Immutable;
8 using System.Linq;
9 using System.Reflection;
10 using System.Xml.Linq;
11 using Barotrauma.Steam;
12 using System.Globalization;
13 
14 namespace Barotrauma.Networking
15 {
17  {
18  public abstract class DataSource
19  {
20  public static Option<DataSource> Parse(XElement element)
21  => ReflectionUtils.ParseDerived<DataSource, XElement>(element);
22  public abstract void Write(XElement element);
23  }
24 
25  public ImmutableArray<Endpoint> Endpoints { get; }
26 
27  public Option<DataSource> MetadataSource = Option.None;
28 
29  [Serialize("", IsPropertySaveable.Yes)]
30  public string ServerName { get; set; } = "";
31 
32  [Serialize("", IsPropertySaveable.Yes)]
33  public string ServerMessage { get; set; } = "";
34 
35  public int PlayerCount { get; set; }
36 
38  public int MaxPlayers { get; set; }
39 
40  public bool GameStarted { get; set; }
41 
42  [Serialize(false, IsPropertySaveable.Yes)]
43  public bool HasPassword { get; set; }
44 
45  [Serialize("", IsPropertySaveable.Yes)]
46  public Identifier GameMode { get; set; }
47 
49  public SelectionMode ModeSelectionMode { get; set; }
50 
52  public SelectionMode SubSelectionMode { get; set; }
53 
54  [Serialize(false, IsPropertySaveable.Yes)]
55  public bool AllowSpectating { get; set; }
56 
57  [Serialize(false, IsPropertySaveable.Yes)]
58  public bool VoipEnabled { get; set; }
59 
60  [Serialize(false, IsPropertySaveable.Yes)]
61  public bool KarmaEnabled { get; set; }
62 
63  [Serialize(false, IsPropertySaveable.Yes)]
64  public bool FriendlyFireEnabled { get; set; }
65 
66  [Serialize(false, IsPropertySaveable.Yes)]
67  public bool AllowRespawn { get; set; }
68 
69  [Serialize(0.0f, IsPropertySaveable.Yes)]
70  public float TraitorProbability { get; set; }
71 
73  public PlayStyle PlayStyle { get; set; }
74 
75  [Serialize("", IsPropertySaveable.Yes)]
76  public LanguageIdentifier Language { get; set; }
77 
78  public bool EosCrossplay { get; set; }
79 
80  [Serialize("", IsPropertySaveable.Yes)]
81  public string SelectedSub { get; set; } = string.Empty;
82 
83  public Version GameVersion { get; set; } = new Version(0, 0, 0, 0);
84 
85  public Option<int> Ping = Option<int>.None();
86 
87  public bool Checked = false;
88 
89  public ImmutableArray<ServerListContentPackageInfo> ContentPackages;
90 
91  public int ContentPackageCount;
92 
93  public bool IsModded => ContentPackages.Any(p => !GameMain.VanillaContent.NameMatches(p.Name));
94 
95  public ServerInfo(params Endpoint[] endpoint) : this(endpoint.ToImmutableArray()) { }
96 
97  public ServerInfo(ImmutableArray<Endpoint> endpoints)
98  {
100  Endpoints = endpoints;
101  ContentPackages = ImmutableArray<ServerListContentPackageInfo>.Empty;
102  }
103 
104  public static ServerInfo FromServerEndpoints(ImmutableArray<Endpoint> endpoints, ServerSettings serverSettings)
105  {
106  var serverInfo = new ServerInfo(endpoints)
107  {
108  GameMode = GameMain.NetLobbyScreen.SelectedMode?.Identifier ?? Identifier.Empty,
112  ContentPackages = ContentPackageManager.EnabledPackages.All.Select(p => new ServerListContentPackageInfo(p)).ToImmutableArray(),
114 
115  // -------------------------------------
116  // Settings that cannot be copied via
117  // SerializableProperty because they do
118  // not implement the attribute
119  ServerName = serverSettings.ServerName,
120  ServerMessage = serverSettings.ServerMessageText,
121  // -------------------------------------
122  // Settings that cannot be copied via
123  // SerializableProperty due to name mismatch
124  HasPassword = serverSettings.HasPassword,
125  VoipEnabled = serverSettings.VoiceChatEnabled,
126  FriendlyFireEnabled = serverSettings.AllowFriendlyFire,
127  // -------------------------------------
128 
129  Checked = true
130  };
131 
132  var serverInfoSerializableProperties
133  = SerializableProperty.GetProperties(serverInfo);
134  var serverSettingsSerializableProperties
135  = SerializableProperty.GetProperties(serverSettings);
136 
137  var intersection = serverInfoSerializableProperties.Keys
138  .Where(serverSettingsSerializableProperties.ContainsKey);
139 
140  foreach (var key in intersection)
141  {
142  var propToGet = serverSettingsSerializableProperties[key];
143  var propToSet = serverInfoSerializableProperties[key];
144  if (!propToGet.PropertyInfo.CanRead) { continue; }
145  if (!propToSet.PropertyInfo.CanWrite) { continue; }
146  propToSet.SetValue(
147  serverInfo,
148  propToGet.GetValue(serverSettings));
149  }
150 
151  return serverInfo;
152  }
153 
154  public void CreatePreviewWindow(GUIFrame frame)
155  {
156  frame.ClearChildren();
157 
158  var serverListScreen = GameMain.ServerListScreen;
159 
160  var title = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform), ServerName, font: GUIStyle.LargeFont)
161  {
162  ToolTip = ServerName,
163  CanBeFocused = false
164  };
165  title.Text = ToolBox.LimitString(title.Text, title.Font, (int)(title.Rect.Width * 0.85f));
166 
167  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform),
168  TextManager.AddPunctuation(':', TextManager.Get("ServerListVersion"),
169  GameVersion == new Version(0, 0, 0, 0) ? TextManager.Get("Unknown") : GameVersion.ToString()))
170  {
171  CanBeFocused = false
172  };
173 
174  PlayStyle playStyle = PlayStyle;
175  Sprite? playStyleBannerSprite = GUIStyle.GetComponentStyle($"PlayStyleBanner.{playStyle}")?.GetSprite(GUIComponent.ComponentState.None);
176 
177  GUIComponent playStyleBanner;
178  Color playStyleBannerColor;
179  if (playStyleBannerSprite != null)
180  {
181  float playStyleBannerAspectRatio = (float)playStyleBannerSprite.SourceRect.Width / (float)playStyleBannerSprite.SourceRect.Height;
182  playStyleBanner = new GUIImage(new RectTransform(new Vector2(1.0f, 1.0f / playStyleBannerAspectRatio), frame.RectTransform, scaleBasis: ScaleBasis.BothWidth),
183  playStyleBannerSprite, null, true);
184  playStyleBannerColor = playStyleBannerSprite.SourceElement.GetAttributeColor("bannercolor", Color.Black);
185  }
186  else
187  {
188  playStyleBanner = new GUIFrame(new RectTransform((1.0f, 0.2f), frame.RectTransform), style: null)
189  {
190  Color = Color.Black,
191  DisabledColor = Color.Black,
192  OutlineColor = Color.Black,
193  PressedColor = Color.Black,
194  SelectedColor = Color.Black,
195  HoverColor = Color.Black
196  };
197  playStyleBannerColor = Color.Black;
198  }
199 
200  var playStyleName = new GUITextBlock(
201  new RectTransform(new Vector2(0.15f, 0.0f), playStyleBanner.RectTransform)
202  { RelativeOffset = new Vector2(0.0f, 0.06f) },
203  TextManager.AddPunctuation(':', TextManager.Get("serverplaystyle"),
204  TextManager.Get($"servertag.{playStyle}")), textColor: Color.White,
205  font: GUIStyle.SmallFont, textAlignment: Alignment.Center,
206  color: playStyleBannerColor, style: "GUISlopedHeader");
207  playStyleName.RectTransform.NonScaledSize = (playStyleName.Font.MeasureString(playStyleName.Text) + new Vector2(20, 5) * GUI.Scale).ToPoint();
208  playStyleName.RectTransform.IsFixedSize = true;
209 
210  var serverType = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform),
211  Endpoints.First().ServerTypeString,
212  textAlignment: Alignment.TopLeft)
213  {
214  CanBeFocused = false
215  };
216  serverType.RectTransform.MinSize = new Point(0, (int)(serverType.Rect.Height * 1.5f));
217 
218  var content = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), frame.RectTransform))
219  {
220  Stretch = true
221  };
222 
223  var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.25f), playStyleBanner.RectTransform, Anchor.BottomRight),
224  isHorizontal: true, childAnchor: Anchor.BottomRight);
225 
226  //shadow behind the buttons
227  new GUIFrame(new RectTransform(new Vector2(3.15f, 1.05f), buttonContainer.RectTransform, Anchor.BottomRight, scaleBasis: ScaleBasis.Smallest), style: null)
228  {
229  Color = Color.Black * 0.7f,
230  IgnoreLayoutGroups = true
231  };
232 
233  bool isFavorite = serverListScreen.IsFavorite(this);
234  static LocalizedString favoriteTickBoxToolTip(bool isFavorite)
235  => TextManager.Get(isFavorite ? "RemoveFromFavorites" : "AddToFavorites");
236 
237  GUITickBox favoriteTickBox = new GUITickBox(new RectTransform(Vector2.One, buttonContainer.RectTransform, scaleBasis: ScaleBasis.Smallest),
238  "", null, "GUIServerListFavoriteTickBox")
239  {
240  UserData = this,
241  Selected = isFavorite,
242  ToolTip = favoriteTickBoxToolTip(isFavorite),
243  OnSelected = tickbox =>
244  {
245  ServerInfo info = (ServerInfo)tickbox.UserData;
246  if (tickbox.Selected)
247  {
249  }
250  else
251  {
253  }
254  tickbox.ToolTip = favoriteTickBoxToolTip(tickbox.Selected);
255  return true;
256  }
257  };
258 
259  new GUIButton(new RectTransform(Vector2.One, buttonContainer.RectTransform, scaleBasis: ScaleBasis.Smallest), style: "GUIServerListReportServer")
260  {
261  ToolTip = TextManager.Get("reportserver"),
262  OnClicked = (_, _) => {ServerListScreen.CreateReportPrompt(this); return true; }
263  };
264 
265  new GUIButton(new RectTransform(Vector2.One, buttonContainer.RectTransform, scaleBasis: ScaleBasis.Smallest), style: "GUIServerListHideServer")
266  {
267  ToolTip = TextManager.Get("filterserver"),
268  OnClicked = (_, _) =>
269  {
271  return true;
272  }
273  };
274 
275  // playstyle tags -----------------------------------------------------------------------------
276 
277  var playStyleContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), content.RectTransform), isHorizontal: true)
278  {
279  Stretch = true,
280  RelativeSpacing = 0.01f,
281  CanBeFocused = true
282  };
283 
284  var playStyleTags = GetPlayStyleTags();
285  foreach (var tag in playStyleTags)
286  {
287  var playStyleIcon = GUIStyle.GetComponentStyle($"PlayStyleIcon.{tag}")
288  ?.GetSprite(GUIComponent.ComponentState.None);
289  if (playStyleIcon is null) { continue; }
290 
291  new GUIImage(new RectTransform(Vector2.One, playStyleContainer.RectTransform),
292  playStyleIcon, scaleToFit: true)
293  {
294  ToolTip = TextManager.Get($"servertagdescription.{tag}"),
295  Color = Color.White
296  };
297  }
298 
299  playStyleContainer.Recalculate();
300 
301  // -----------------------------------------------------------------------------
302 
303  const float elementHeight = 0.075f;
304 
305  // Spacing
306  new GUIFrame(new RectTransform(new Vector2(1.0f, 0.025f), content.RectTransform), style: null);
307 
308  var serverMsg = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.3f), content.RectTransform)) { ScrollBarVisible = true };
309  var msgText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), serverMsg.Content.RectTransform), ServerMessage ?? string.Empty, font: GUIStyle.SmallFont, wrap: true)
310  {
311  CanBeFocused = false
312  };
313  serverMsg.Content.RectTransform.SizeChanged += () => { msgText.CalculateHeightFromText(); };
314  msgText.RectTransform.SizeChanged += () => { serverMsg.UpdateScrollBarSize(); };
315 
316  var languageLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("Language"));
317  new GUITextBlock(new RectTransform(Vector2.One, languageLabel.RectTransform),
318  ServerLanguageOptions.Options.FirstOrNull(o => o.Identifier == Language)?.Label ?? TextManager.Get("Unknown"),
319  textAlignment: Alignment.Right);
320 
321  var gameMode = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("GameMode"));
322  new GUITextBlock(new RectTransform(Vector2.One, gameMode.RectTransform),
323  TextManager.Get(GameMode.IsEmpty ? "Unknown" : "GameMode." + GameMode).Fallback(GameMode.Value),
324  textAlignment: Alignment.Right);
325 
326  if (!string.IsNullOrEmpty(SelectedSub))
327  {
328  var submarineText = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("Submarine"));
329  new GUITextBlock(new RectTransform(Vector2.One, submarineText.RectTransform),
330  SelectedSub,
331  textAlignment: Alignment.Right);
332  }
333 
334  GUITextBlock playStyleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("serverplaystyle"));
335  new GUITextBlock(new RectTransform(Vector2.One, playStyleText.RectTransform), TextManager.Get("servertag." + playStyle), textAlignment: Alignment.Right);
336 
337  var subSelection = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("ServerListSubSelection"));
338  new GUITextBlock(new RectTransform(Vector2.One, subSelection.RectTransform), TextManager.Get(SubSelectionMode.ToString()), textAlignment: Alignment.Right);
339 
340  var modeSelection = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("ServerListModeSelection"));
341  new GUITextBlock(new RectTransform(Vector2.One, modeSelection.RectTransform), TextManager.Get(ModeSelectionMode.ToString()), textAlignment: Alignment.Right);
342 
343  if (gameMode.TextSize.X + gameMode.GetChild<GUITextBlock>().TextSize.X > gameMode.Rect.Width ||
344  subSelection.TextSize.X + subSelection.GetChild<GUITextBlock>().TextSize.X > subSelection.Rect.Width ||
345  modeSelection.TextSize.X + modeSelection.GetChild<GUITextBlock>().TextSize.X > modeSelection.Rect.Width)
346  {
347  gameMode.Font = subSelection.Font = modeSelection.Font = GUIStyle.SmallFont;
348  gameMode.GetChild<GUITextBlock>().Font = subSelection.GetChild<GUITextBlock>().Font = modeSelection.GetChild<GUITextBlock>().Font = GUIStyle.SmallFont;
349  playStyleText.Font = playStyleText.GetChild<GUITextBlock>().Font = GUIStyle.SmallFont;
350  }
351 
352  var allowSpectating = new GUITickBox(new RectTransform(new Vector2(1, elementHeight), content.RectTransform), TextManager.Get("ServerListAllowSpectating"))
353  {
354  CanBeFocused = false
355  };
356  allowSpectating.Selected = AllowSpectating;
357 
358  var allowRespawn = new GUITickBox(new RectTransform(new Vector2(1, elementHeight), content.RectTransform), TextManager.Get("ServerSettingsAllowRespawning"))
359  {
360  CanBeFocused = false
361  };
362  allowRespawn.Selected = AllowRespawn;
363 
364  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
365  TextManager.Get("ServerListContentPackages"), textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont);
366 
367  var contentPackageList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.3f), frame.RectTransform))
368  {
369  ScrollBarVisible = true,
370  OnSelected = (component, o) => false
371  };
372  if (ContentPackages.Length == 0)
373  {
374  new GUITextBlock(new RectTransform(Vector2.One, contentPackageList.Content.RectTransform), TextManager.Get("Unknown"), textAlignment: Alignment.Center)
375  {
376  CanBeFocused = false
377  };
378  }
379  else
380  {
381  foreach (var package in ContentPackages)
382  {
383  var packageText = new GUITickBox(
384  new RectTransform(new Vector2(1.0f, 0.15f), contentPackageList.Content.RectTransform)
385  { MinSize = new Point(0, 15) },
386  package.Name)
387  {
388  CanBeFocused = false
389  };
390  if (!string.IsNullOrEmpty(package.Hash))
391  {
392  if (ContentPackageManager.AllPackages.Any(contentPackage => contentPackage.Hash.StringRepresentation == package.Hash))
393  {
394  packageText.TextColor = GUIStyle.Green;
395  packageText.Selected = true;
396  }
397  //workshop download link found
398  else if (package.Id.TryUnwrap(out var ugcId) && ugcId is SteamWorkshopId)
399  {
400  packageText.ToolTip = TextManager.GetWithVariable("ServerListIncompatibleContentPackageWorkshopAvailable", "[contentpackage]", package.Name);
401  }
402  else //no package or workshop download link found
403  {
404  packageText.TextColor = GameMain.VanillaContent.NameMatches(package.Name) ? GUIStyle.Red : GUIStyle.Yellow;
405  packageText.ToolTip = TextManager.GetWithVariables("ServerListIncompatibleContentPackage",
406  ("[contentpackage]", package.Name), ("[hash]", package.Hash));
407  }
408  }
409  }
411  {
412  new GUITextBlock(
413  new RectTransform(new Vector2(1.0f, 0.15f), contentPackageList.Content.RectTransform) { MinSize = new Point(0, 15) },
414  TextManager.GetWithVariable("workshopitemdownloadprompttruncated", "[number]", (ContentPackageCount - ContentPackages.Length).ToString()))
415  {
416  CanBeFocused = false
417  };
418  }
419  }
420 
421  // -----------------------------------------------------------------------------
422 
423  foreach (GUIComponent c in content.Children)
424  {
425  if (c is GUITextBlock textBlock) { textBlock.Padding = Vector4.Zero; }
426  }
427  }
428 
429  public IEnumerable<Identifier> GetPlayStyleTags()
430  {
431  yield return $"Karma.{KarmaEnabled}".ToIdentifier();
432  yield return (TraitorProbability > 0.0f ? $"Traitors.True" : $"Traitors.False").ToIdentifier();
433  yield return $"VoIP.{VoipEnabled}".ToIdentifier();
434  yield return $"FriendlyFire.{FriendlyFireEnabled}".ToIdentifier();
435  yield return $"Modded.{IsModded}".ToIdentifier();
436  }
437 
438  public void UpdateInfo(Func<string, string?> valueGetter)
439  {
440  ServerMessage = valueGetter("message") ?? "";
441  if (Version.TryParse(valueGetter("version"), out var version))
442  {
443  GameVersion = version;
444  }
445  if (int.TryParse(valueGetter("playercount"), out int playerCount)) { PlayerCount = playerCount; }
446 
447  if (int.TryParse(valueGetter("maxplayers"), out int maxPlayers)) { MaxPlayers = maxPlayers; }
448  else if (int.TryParse(valueGetter("maxplayernum"), out maxPlayers)) { MaxPlayers = maxPlayers; }
449 
450  if (Enum.TryParse(valueGetter("modeselectionmode"), out SelectionMode modeSelectionMode)) { ModeSelectionMode = modeSelectionMode; }
451 
452  if (Enum.TryParse(valueGetter("subselectionmode"), out SelectionMode subSelectionMode)) { SubSelectionMode = subSelectionMode; }
453 
454  HasPassword = getBool("haspassword");
455  GameStarted = getBool("gamestarted");
456  KarmaEnabled = getBool("karmaenabled");
457  FriendlyFireEnabled = getBool("friendlyfireenabled");
458  AllowSpectating = getBool("allowspectating");
459  AllowRespawn = getBool("allowrespawn");
460  VoipEnabled = getBool("voicechatenabled");
461  EosCrossplay = getBool("eoscrossplay");
462 
463  GameMode = valueGetter("gamemode")?.ToIdentifier() ?? Identifier.Empty;
464  if (float.TryParse(valueGetter("traitors"), NumberStyles.Any, CultureInfo.InvariantCulture, out float traitorProbability)) { TraitorProbability = traitorProbability; }
465  if (Enum.TryParse(valueGetter("playstyle"), out PlayStyle playStyle)) { PlayStyle = playStyle; }
466  Language = valueGetter("language")?.ToLanguageIdentifier() ?? LanguageIdentifier.None;
467  SelectedSub = valueGetter("submarine") ?? string.Empty;
468 
469  ContentPackages = ExtractContentPackageInfo(ServerName, valueGetter).ToImmutableArray();
471  if (int.TryParse(valueGetter("packagecount"), out int packageCount)) { ContentPackageCount = packageCount; }
472 
473  bool getBool(string key)
474  {
475  string? data = valueGetter(key);
476  return bool.TryParse(data, out var result) && result;
477  }
478  }
479 
480  private static ServerListContentPackageInfo[] ExtractContentPackageInfo(string serverName, Func<string, string?> valueGetter)
481  {
482  //workaround to ServerRules queries truncating the values to 255 bytes
483  int individualPackageIndex = 0;
484  string? individualPackage = valueGetter($"contentpackage{individualPackageIndex}");
485  if (!individualPackage.IsNullOrEmpty())
486  {
487  List<ServerListContentPackageInfo> contentPackages = new List<ServerListContentPackageInfo>();
488  do
489  {
490  if (!ServerListContentPackageInfo.ParseSingleEntry(individualPackage).TryUnwrap(out var info))
491  {
492  return Array.Empty<ServerListContentPackageInfo>();
493  }
494  contentPackages.Add(info);
495 
496  individualPackageIndex++;
497  individualPackage = valueGetter($"contentpackage{individualPackageIndex}");
498  } while (!individualPackage.IsNullOrEmpty());
499  return contentPackages.ToArray();
500  }
501 
502  string? joinedNames = valueGetter("contentpackage");
503  string? joinedHashes = valueGetter("contentpackagehash");
504  string? joinedUgcIds = valueGetter("contentpackageid");
505 
506  var contentPackageNames = joinedNames.IsNullOrEmpty() ? Array.Empty<string>() : joinedNames.SplitEscaped(',');
507  var contentPackageHashes = joinedHashes.IsNullOrEmpty() ? Array.Empty<string>() : joinedHashes.SplitEscaped(',');
508  var contentPackageIds = joinedUgcIds.IsNullOrEmpty() ? new string[1] { string.Empty } : joinedUgcIds.SplitEscaped(',');
509 
510  if (contentPackageNames.Count != contentPackageHashes.Count || contentPackageHashes.Count != contentPackageIds.Count)
511  {
512  DebugConsole.Log(
513  $"The number of names, hashes and UGC IDs on server \"{serverName}\"" +
514  $" doesn't match: {contentPackageNames.Count} names ({string.Join(", ", contentPackageNames)}), {contentPackageHashes.Count} hashes, {contentPackageIds.Count} ids)");
515  return Array.Empty<ServerListContentPackageInfo>();
516  }
517 
518  return contentPackageNames
519  .Zip(contentPackageHashes, (name, hash) => (name, hash))
520  .Zip(contentPackageIds, (t1, id) =>
521  new ServerListContentPackageInfo(
522  t1.name,
523  t1.hash,
524  ContentPackageId.Parse(id)))
525  .ToArray();
526  }
527 
528  public static Option<ServerInfo> FromXElement(XElement element)
529  {
530  var endpoints = new List<Endpoint>();
531 
532  string endpointStr
533  = element.GetAttributeString("Endpoint", null)
534  ?? element.GetAttributeString("OwnerID", null)
535  ?? $"{element.GetAttributeString("IP", "")}:{element.GetAttributeInt("Port", 0)}";
536 
537  if (Endpoint.Parse(endpointStr).TryUnwrap(out var endpoint))
538  {
539  endpoints.Add(endpoint);
540  }
541  else
542  {
543  var multipleEndpointStrs
544  = element.GetAttributeStringArray("Endpoints", Array.Empty<string>());
545  endpoints.AddRange(
546  multipleEndpointStrs
548  .NotNone());
549  }
550 
551  if (endpoints.Count == 0) { return Option.None; }
552 
553  var gameVersionStr = element.GetAttributeString("GameVersion", "");
554  if (!Version.TryParse(gameVersionStr, out var gameVersion)) { gameVersion = GameMain.Version; }
555  var info = new ServerInfo(endpoints.ToImmutableArray())
556  {
557  GameVersion = gameVersion
558  };
560 
561  info.MetadataSource = DataSource.Parse(element);
562 
563  return Option.Some(info);
564  }
565 
566  public XElement ToXElement()
567  {
568  XElement element = new XElement(GetType().Name);
569 
570  element.SetAttributeValue("Endpoints", string.Join(",", Endpoints.Select(e => e.StringRepresentation)));
571  element.SetAttributeValue("GameVersion", GameVersion.ToString());
572 
573  SerializableProperty.SerializeProperties(this, element, saveIfDefault: true);
574 
575  if (MetadataSource.TryUnwrap(out var dataSource))
576  {
577  dataSource.Write(element);
578  }
579 
580  return element;
581  }
582 
583  public override bool Equals(object? obj)
584  {
585  return obj is ServerInfo other && Equals(other);
586  }
587 
588  public bool Equals(ServerInfo other)
589  => other.Endpoints.Any(e => Endpoints.Contains(e));
590 
591  public override int GetHashCode() => Endpoints.First().GetHashCode();
592 
593  string ISerializableEntity.Name => "ServerInfo";
594  public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; }
595  }
596 }
bool NameMatches(Identifier name)
Color GetAttributeColor(string key, in Color def)
GUIComponent GetChild(int index)
Definition: GUIComponent.cs:54
virtual void ClearChildren()
RectTransform RectTransform
IEnumerable< GUIComponent > Children
Definition: GUIComponent.cs:29
override GUIFont Font
Definition: GUITextBlock.cs:66
override bool Selected
Definition: GUITickBox.cs:18
static NetLobbyScreen NetLobbyScreen
Definition: GameMain.cs:55
static ContentPackage VanillaContent
Definition: GameMain.cs:84
static readonly Version Version
Definition: GameMain.cs:46
static GameClient Client
Definition: GameMain.cs:188
static ServerListScreen ServerListScreen
Definition: GameMain.cs:66
readonly Identifier Identifier
static Option< Endpoint > Parse(string str)
override IReadOnlyList< Client > ConnectedClients
Definition: GameClient.cs:133
abstract void Write(XElement element)
static Option< DataSource > Parse(XElement element)
ImmutableArray< Endpoint > Endpoints
Definition: ServerInfo.cs:25
IEnumerable< Identifier > GetPlayStyleTags()
Definition: ServerInfo.cs:429
void UpdateInfo(Func< string, string?> valueGetter)
Definition: ServerInfo.cs:438
ImmutableArray< ServerListContentPackageInfo > ContentPackages
Definition: ServerInfo.cs:89
ServerInfo(ImmutableArray< Endpoint > endpoints)
Definition: ServerInfo.cs:97
Option< DataSource > MetadataSource
Definition: ServerInfo.cs:27
static Option< ServerInfo > FromXElement(XElement element)
Definition: ServerInfo.cs:528
override bool Equals(object? obj)
Definition: ServerInfo.cs:583
LanguageIdentifier Language
Definition: ServerInfo.cs:76
void CreatePreviewWindow(GUIFrame frame)
Definition: ServerInfo.cs:154
bool Equals(ServerInfo other)
static ServerInfo FromServerEndpoints(ImmutableArray< Endpoint > endpoints, ServerSettings serverSettings)
Definition: ServerInfo.cs:104
Dictionary< Identifier, SerializableProperty > SerializableProperties
Definition: ServerInfo.cs:594
ServerInfo(params Endpoint[] endpoint)
Definition: ServerInfo.cs:95
Point?? MinSize
Min size in pixels. Does not affect scaling.
static Dictionary< Identifier, SerializableProperty > DeserializeProperties(object obj, XElement element=null)
static Dictionary< Identifier, SerializableProperty > GetProperties(object obj)
static void SerializeProperties(ISerializableEntity obj, XElement element, bool saveIfDefault=false, bool ignoreEditable=false)
void AddToFavoriteServers(ServerInfo info)
static void CreateFilterServerPrompt(ServerInfo info)
void RemoveFromFavoriteServers(ServerInfo info)
static void CreateReportPrompt(ServerInfo info)
ContentXElement SourceElement
Reference to the xml element from where the sprite was created. Can be null if the sprite was not def...
static readonly LanguageIdentifier None
Definition: TextPack.cs:12