6 using Microsoft.Xna.Framework;
7 using Microsoft.Xna.Framework.Graphics;
9 using System.Collections.Generic;
10 using System.Collections.Immutable;
12 using System.Threading.Tasks;
18 public static readonly LocalizedString
ShortcutBindText = TextManager.Get(
"SocialOverlayShortcutBind");
26 private sealed
class NotificationHandler
28 public record Notification(
30 GUIComponent GuiElement);
31 private readonly List<Notification> notifications =
new();
33 private static readonly TimeSpan notificationDuration = TimeSpan.FromSeconds(8);
34 private static readonly TimeSpan notificationEasingTimeSpan = TimeSpan.FromSeconds(0.5);
35 public readonly GUIFrame NotificationContainer =
36 new GUIFrame(
new RectTransform((0.4f, 0.15f), GUI.Canvas, Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight), style:
null)
43 var now = DateTime.Now;
44 float cumulativeNotificationOffset = 0;
46 for (
int i = notifications.Count - 1; i >= 0; i--)
48 var notification = notifications[i];
50 var expiryTime = notification.ReceiveTime + notificationDuration;
52 || notification.GuiElement.Parent is
null)
54 RemoveNotification(notification);
58 TimeSpan diffToStart = now - notification.ReceiveTime;
59 TimeSpan diffToEnd = expiryTime - now;
61 float offsetToAdd = 1f;
62 offsetToAdd = Math.Min(
64 (
float)diffToStart.TotalSeconds / (
float)notificationEasingTimeSpan.TotalSeconds);
65 offsetToAdd = Math.Min(
67 (
float)diffToEnd.TotalSeconds / (
float)notificationEasingTimeSpan.TotalSeconds);
69 offsetToAdd = Math.Max(offsetToAdd, 0f);
71 cumulativeNotificationOffset += offsetToAdd;
73 notification.GuiElement.RectTransform.RelativeOffset = (0, cumulativeNotificationOffset - 1f);
79 NotificationContainer.AddToGUIUpdateList();
82 public void AddNotification(Notification notification)
84 notifications.Add(notification);
87 public void RemoveNotification(Notification notification)
89 notifications.Remove(notification);
90 NotificationContainer.RemoveChild(notification.GuiElement);
94 private sealed
class InviteHandler : IDisposable
96 private readonly record
struct Invite(
99 Option<NotificationHandler.Notification> NotificationOption);
102 private readonly FriendProvider friendProvider;
103 private readonly NotificationHandler notificationHandler;
105 private readonly List<Invite> invites =
new List<Invite>();
106 private static readonly TimeSpan inviteDuration = TimeSpan.FromMinutes(5);
107 private readonly Identifier inviteReceivedEventIdentifier;
109 public InviteHandler(
111 FriendProvider inFriendProvider,
112 NotificationHandler inNotificationHandler)
114 socialOverlay = inSocialOverlay;
115 friendProvider = inFriendProvider;
116 notificationHandler = inNotificationHandler;
118 inviteReceivedEventIdentifier = GetHashCode().ToIdentifier();
119 EosInterface.Presence.OnInviteReceived.Register(
120 identifier: inviteReceivedEventIdentifier,
121 OnEosInviteReceived);
122 Steamworks.SteamFriends.OnChatMessage += OnSteamChatMsgReceived;
125 private void OnSteamChatMsgReceived(Steamworks.Friend steamFriend,
string msgType,
string msgContent)
127 if (!
string.Equals(msgType,
"InviteGame")) {
return; }
129 var friendId =
new SteamId(steamFriend.Id);
131 $
"ReceivedInviteFrom{friendId}",
132 friendProvider.RetrieveFriend(friendId),
135 if (!t.TryGetResult(out Option<FriendInfo> friendInfoOption)) { return; }
136 if (!friendInfoOption.TryUnwrap(out var friendInfo)) { return; }
137 RegisterInvite(friendInfo, showNotification:
false);
141 private void OnEosInviteReceived(EosInterface.Presence.ReceiveInviteInfo info)
144 $
"ReceivedInviteFrom{info.SenderId}",
145 friendProvider.RetrieveFriendWithAvatar(info.SenderId, notificationHandler.NotificationContainer.Rect.Height),
148 if (!t.TryGetResult(out Option<FriendInfo> friendInfoOption)) { return; }
149 if (!friendInfoOption.TryUnwrap(out var friendInfo)) { return; }
150 RegisterInvite(friendInfo, showNotification:
true);
154 public bool HasInviteFrom(AccountId sender)
155 => invites.Any(invite => invite.Sender.Id == sender);
157 public void ClearInvitesFrom(AccountId sender)
159 foreach (var invite
in invites)
161 if (invite.Sender.Id == sender && invite.NotificationOption.TryUnwrap(out var notification))
163 notificationHandler.RemoveNotification(notification);
166 invites.RemoveAll(invite => invite.Sender.Id == sender);
168 if (sender is not EpicAccountId friendEpicId) {
return; }
170 var selfEpicIds = EosInterface.IdQueries.GetLoggedInEpicIds();
171 if (selfEpicIds.Length == 0) {
return; }
173 var selfEpicId = selfEpicIds[0];
174 EosInterface.Presence.DeclineInvite(selfEpicId, friendEpicId);
179 var now = DateTime.Now;
181 for (
int i = invites.Count - 1; i >= 0; i--)
183 var invite = invites[i];
185 var expiryTime = invite.ReceiveTime + inviteDuration;
186 if (now > expiryTime)
188 if (invite.NotificationOption.TryUnwrap(out var notification))
190 notificationHandler.RemoveNotification(notification);
197 private void RegisterInvite(
FriendInfo senderInfo,
bool showNotification)
199 var now = DateTime.Now;
201 var invite =
new Invite(
204 NotificationOption: Option.None);
206 if (showNotification)
208 var baseButton =
new GUIButton(
209 new RectTransform(Vector2.One, notificationHandler.NotificationContainer.RectTransform,
Anchor.BottomRight)
211 RelativeOffset = (0, -1)
212 }, style:
"SocialOverlayPopup");
213 baseButton.Frame.OutlineThickness = 1f;
215 var topLayout =
new GUILayoutGroup(
new RectTransform(Vector2.One, baseButton.RectTransform), isHorizontal:
true)
218 RelativeSpacing = 0.05f
221 var avatarContainer =
new GUIFrame(
new RectTransform(Vector2.One, topLayout.RectTransform, scaleBasis:
ScaleBasis.BothHeight), style:
null);
223 var avatarComponent =
new GUICustomComponent(
226 avatarContainer.RectTransform,
229 onDraw: (sb, component) =>
231 if (!senderInfo.Avatar.TryUnwrap(out var avatar)) { return; }
233 var rect = component.Rect;
234 sb.Draw(avatar.Texture, rect, avatar.Texture.Bounds, Color.White);
237 var textLayout =
new GUILayoutGroup(
new RectTransform(Vector2.One, topLayout.RectTransform))
243 =>
new GUIFrame(
new RectTransform((1.0f, 0.2f), textLayout.RectTransform), style:
null);
245 void addText(LocalizedString text, GUIFont font)
246 =>
new GUITextBlock(
new RectTransform((1.0f, 0.2f), textLayout.RectTransform), text, font: font);
249 addText(senderInfo.
Name, GUIStyle.SubHeadingFont);
250 addText(TextManager.Get(
"InvitedYou"), GUIStyle.Font);
252 addText(TextManager.GetWithVariable(
"ClickHereOrPressSocialOverlayShortcut",
"[shortcut]", ShortcutBindText), GUIStyle.SmallFont);
255 var notification =
new NotificationHandler.Notification(
257 GuiElement: baseButton);
258 baseButton.OnClicked = (_, _) =>
260 socialOverlay.IsOpen =
true;
261 notificationHandler.RemoveNotification(notification);
264 baseButton.OnSecondaryClicked = (_, _) =>
266 notificationHandler.RemoveNotification(notification);
270 notificationHandler.AddNotification(notification);
272 invite = invite with { NotificationOption = Option.Some(notification) };
278 public void Dispose()
280 EosInterface.Presence.OnInviteReceived.Deregister(inviteReceivedEventIdentifier);
281 Steamworks.SteamFriends.OnChatMessage -= OnSteamChatMsgReceived;
285 private readonly NotificationHandler notificationHandler;
286 private readonly InviteHandler inviteHandler;
287 private readonly GUIFrame background;
288 private readonly GUIButton linkHint;
289 private readonly GUILayoutGroup contentLayout;
291 private readonly GUIFrame selectedFriendInfoFrame;
293 private const float WidthToHeightRatio = 7f;
295 private readonly TimeSpan refreshInterval = TimeSpan.FromSeconds(30);
296 private DateTime lastRefreshTime;
300 private static RectTransform CreateRowRectT(GUIComponent parent,
float heightScale = 1f)
301 =>
new RectTransform((1.0f, heightScale / WidthToHeightRatio), parent.RectTransform, scaleBasis: ScaleBasis.BothWidth);
303 private static GUILayoutGroup CreateRowLayout(GUIComponent parent,
float heightScale = 1f)
305 var rowLayout =
new GUILayoutGroup(CreateRowRectT(parent, heightScale), isHorizontal:
true)
310 new GUICustomComponent(
new RectTransform(Vector2.Zero, rowLayout.RectTransform),
311 onUpdate: (f, component) =>
313 rowLayout.RectTransform.NonScaledSize = calculateSize();
318 Point calculateSize() =>
new Point(parent.Rect.Width, (
int)((parent.Rect.Width * heightScale) / WidthToHeightRatio));
321 private readonly
struct PlayerRow
323 public readonly GUIFrame AvatarContainer;
324 public readonly GUIFrame InfoContainer;
327 internal PlayerRow(
FriendInfo friendInfo, GUILayoutGroup containerLayout,
bool invitedYou, IEnumerable<LocalizedString>? metadataText =
null)
330 AvatarContainer =
new GUIFrame(
new RectTransform(Vector2.One, containerLayout.RectTransform, scaleBasis:
ScaleBasis.BothHeight), style:
null);
331 InfoContainer =
new GUIFrame(
new RectTransform(Vector2.One, containerLayout.RectTransform, scaleBasis:
ScaleBasis.Normal), style:
null);
335 var avatarBackground =
new GUIFrame(
new RectTransform(Vector2.One * 0.9f, AvatarContainer.RectTransform,
Anchor.Center),
338 : $
"Friend{friendInfo.CurrentStatus}");
340 var textLayout =
new GUILayoutGroup(
new RectTransform(Vector2.One, InfoContainer.RectTransform)) { Stretch =
true };
341 var textBlocks =
new List<GUITextBlock>();
343 addTextLayoutPadding();
344 addTextBlock(friendInfo.
Name, font: GUIStyle.SubHeadingFont);
345 metadataText ??=
new[] { friendInfo.
StatusText };
346 foreach (var line
in metadataText)
348 addTextBlock(line, font: GUIStyle.Font);
350 addTextLayoutPadding();
352 new GUICustomComponent(
new RectTransform(Vector2.One, avatarBackground.RectTransform),
353 onUpdate: updateTextAlignments,
358 var inviteIcon =
new GUIImage(
new RectTransform(
new Vector2(0.5f), avatarBackground.RectTransform,
Anchor.TopRight,
Pivot.Center)
359 { RelativeOffset = Vector2.One * 0.15f }, style:
"InviteNotification")
361 ToolTip = TextManager.Get(
"InviteNotification")
363 inviteIcon.OnAddedToGUIUpdateList += (GUIComponent component) =>
365 if (component.FlashTimer <= 0.0f)
367 component.Flash(GUIStyle.Green, useCircularFlash:
true);
368 component.Pulsate(Vector2.One, Vector2.One * 1.5f, 0.5f);
373 void addTextLayoutPadding()
374 =>
new GUIFrame(
new RectTransform(Vector2.One, textLayout.RectTransform), style:
null);
376 void addTextBlock(LocalizedString text, GUIFont font)
377 => textBlocks.Add(
new GUITextBlock(
new RectTransform(Vector2.One, textLayout.RectTransform), text,
378 textColor: Color.White, font: font, textAlignment: Alignment.CenterLeft)
380 ForceUpperCase = ForceUpperCase.No,
381 TextColor = avatarBackground.Color,
382 HoverTextColor = avatarBackground.HoverColor,
383 SelectedTextColor = avatarBackground.SelectedColor,
384 PressedColor = avatarBackground.PressedColor,
387 void updateTextAlignments(
float deltaTime, GUICustomComponent component)
389 foreach (var textBlock
in textBlocks)
391 int height = (int)textBlock.Font.LineHeight + GUI.IntScale(2);
392 textBlock.RectTransform.NonScaledSize =
393 (textBlock.RectTransform.NonScaledSize.X, height);
395 textLayout.NeedsToRecalculate =
true;
398 void drawAvatar(SpriteBatch sb, GUICustomComponent component)
400 if (!friendInfo.
Avatar.TryUnwrap(out var avatar)) {
return; }
402 rect.Inflate(-GUI.IntScale(4f), -GUI.IntScale(4f));
403 sb.Draw(avatar.Texture, rect, Color.White);
408 private readonly FriendProvider friendProvider;
410 private readonly GUILayoutGroup selfPlayerRowLayout;
412 private readonly GUIButton? eosConfigButton;
413 private readonly GUILayoutGroup? eosStatusTextContainer;
414 private EosInterface.Core.Status eosLastKnownStatus;
416 private readonly GUIListBox friendPlayerListBox;
417 private readonly List<PlayerRow> friendPlayerRows =
new List<PlayerRow>();
419 private void RecreateSelfPlayerRow()
421 if (SteamManager.GetSteamId().TryUnwrap(out var steamId))
423 selfPlayerRowLayout.ClearChildren();
426 name: SteamManager.GetUsername(),
428 status: FriendStatus.PlayingBarotrauma,
430 connectCommand: Option.None,
431 provider: friendProvider),
435 else if (EosInterface.IdQueries.IsLoggedIntoEosConnect)
437 static async Task<Option<EosInterface.EgsFriend>> GetEpicAccountInfo()
439 if (!EosAccount.SelfAccountIds.OfType<EpicAccountId>().FirstOrNone().TryUnwrap(out var epicAccountId))
444 var selfUserInfoResult = await EosInterface.Friends.GetSelfUserInfo(epicAccountId);
446 if (!selfUserInfoResult.TryUnwrapSuccess(out var selfUserInfo))
451 return Option.Some(selfUserInfo);
455 "GetEpicAccountIdForSelfPlayerRow",
456 GetEpicAccountInfo(),
459 if (!t.TryGetResult(out Option<EosInterface.EgsFriend> userInfoOption)
460 || !userInfoOption.TryUnwrap(out var userInfo))
465 selfPlayerRowLayout.ClearChildren();
468 name: userInfo.DisplayName,
469 id: userInfo.EpicAccountId,
470 status: FriendStatus.PlayingBarotrauma,
472 connectCommand: Option.None,
473 provider: friendProvider),
483 new GUIFrame(
new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas,
Anchor.Center), style:
"SocialOverlayBackground");
484 var rightSideLayout =
486 new RectTransform((0.9f, 1.0f), background.RectTransform,
Anchor.CenterRight,
487 scaleBasis:
ScaleBasis.BothHeight), isHorizontal:
true, childAnchor:
Anchor.BottomLeft);
489 linkHint =
new GUIButton(
new RectTransform((0.5f, 0.9f / WidthToHeightRatio), rightSideLayout.RectTransform,
Anchor.BottomRight, scaleBasis:
ScaleBasis.BothWidth), style:
"FriendsButton")
491 OnClicked = (btn, _) =>
493 eosConfigButton?.Flash(GUIStyle.Green);
494 EosSteamPrimaryLogin.IsNewEosPlayer =
false;
500 _ =
new GUITextBlock(
new RectTransform(Vector2.One * 0.95f, linkHint.RectTransform,
Anchor.Center),
501 text: TextManager.Get(
"EosSettings.RecommendLinkingToEpicAccount"),
503 style:
"FriendsButton");
505 var content =
new GUIFrame(
506 new RectTransform((0.5f, 1.0f), rightSideLayout.RectTransform),
507 style:
"SocialOverlayFriendsList");
510 new RectTransform(Vector2.One * 0.08f, content.RectTransform,
Anchor.TopLeft,
Pivot.TopRight,
513 RelativeOffset = (-0.03f, 0.015f)
515 style:
"SocialOverlayCloseButton")
517 OnClicked = (_, _) =>
526 notificationHandler =
new NotificationHandler();
527 inviteHandler =
new InviteHandler(
528 inSocialOverlay:
this,
529 inFriendProvider: friendProvider,
530 inNotificationHandler: notificationHandler);
532 selectedFriendInfoFrame =
new GUIFrame(
new RectTransform((0.25f, 0.28f), background.RectTransform,
533 Anchor.TopRight, scaleBasis:
ScaleBasis.BothHeight), style:
"SocialOverlayPopup")
535 OutlineThickness = 1f,
539 contentLayout =
new GUILayoutGroup(
new RectTransform(Vector2.One, content.RectTransform)) { Stretch =
true };
541 selfPlayerRowLayout = CreateRowLayout(contentLayout);
542 RecreateSelfPlayerRow();
544 friendPlayerListBox =
545 new GUIListBox(
new RectTransform(Vector2.One, contentLayout.RectTransform), style:
null)
547 OnSelected = (component, userData) =>
549 if (userData is not
FriendInfo friendInfo) {
return false; }
550 selectedFriendInfoFrame.Visible =
true;
551 selectedFriendInfoFrame.RectTransform.AbsoluteOffset = (
552 X: background.Rect.Right - component.Rect.X,
554 value: component.Rect.Center.Y - selectedFriendInfoFrame.Rect.Height / 2,
556 max: background.Rect.Bottom - selectedFriendInfoFrame.Rect.Height));
557 PopulateSelectedFriendInfoFrame(friendInfo);
561 friendPlayerListBox.ScrollBar.OnMoved += (_, _) => { friendPlayerListBox.Deselect();
return true; };
563 if (SteamManager.IsInitialized)
565 var eosConfigRowLayout = CreateRowLayout(contentLayout, heightScale: 1.5f);
566 eosConfigRowLayout.ChildAnchor =
Anchor.CenterLeft;
568 eosConfigButton =
new GUIButton(
569 new RectTransform(Vector2.One * 0.8f, eosConfigRowLayout.RectTransform, scaleBasis:
ScaleBasis.BothHeight),
572 Enabled = GameMain.NetworkMember ==
null,
573 OnClicked = (_, _) => { ShowEosSettingsMenu();
return true; }
575 new GUIFrame(
new RectTransform(Vector2.One * 0.5f, eosConfigButton.RectTransform,
Anchor.Center), style:
"GUIButtonSettings")
580 eosStatusTextContainer =
new GUILayoutGroup(
new RectTransform(Vector2.One, eosConfigRowLayout.RectTransform));
581 RefreshEosStatusText();
589 if (IsOpen) {
return; }
591 var baseButton =
new GUIButton(
592 new RectTransform(Vector2.One, notificationHandler.NotificationContainer.RectTransform, Anchor.BottomRight)
594 RelativeOffset = (0, -1)
595 }, style:
"SocialOverlayPopup");
596 baseButton.Frame.OutlineThickness = 1f;
598 var notification =
new NotificationHandler.Notification(
599 ReceiveTime: DateTime.Now,
600 GuiElement: baseButton);
601 baseButton.OnClicked = (_, _) =>
604 notificationHandler.RemoveNotification(notification);
607 baseButton.OnSecondaryClicked = (_, _) =>
609 notificationHandler.RemoveNotification(notification);
613 _ =
new GUITextBlock(
614 new RectTransform(Vector2.One * 0.98f, baseButton.RectTransform, Anchor.Center),
615 text: TextManager.GetWithVariable(
"SocialOverlayShortcutHint",
"[shortcut]", ShortcutBindText),
616 textAlignment: Alignment.Center,
622 notificationHandler.AddNotification(notification);
625 private void ShowEosSettingsMenu()
627 bool hasEpicAccount = EosAccount.SelfAccountIds.OfType<EpicAccountId>().Any();
628 string manageAccountsText = hasEpicAccount
629 ?
"EosSettings.ManageConnectedAccounts"
630 :
"EosSettings.LinkToEpicAccount";
632 bool eosEnabled = EosInterface.Core.IsInitialized;
633 string enableButtonText = eosEnabled ?
"EosSettings.DisableEos" :
"EosSettings.EnableEos";
635 var msgBox =
new GUIMessageBox(TextManager.Get(
"EosSettings"),
string.Empty,
636 new LocalizedString[]
638 TextManager.Get(manageAccountsText),
639 TextManager.Get(enableButtonText),
640 TextManager.Get(
"EosSettings.RequestDeletion")
641 }, minSize:
new Point(GUI.IntScale(550), 0))
645 msgBox.Buttons[0].Enabled = eosEnabled;
646 msgBox.Buttons[0].ToolTip = TextManager.Get($
"{manageAccountsText}.Tooltip");
647 msgBox.Buttons[1].ToolTip = TextManager.Get($
"{enableButtonText}.Tooltip");
648 msgBox.Buttons[2].ToolTip = TextManager.Get(
"EosSettings.RequestDeletion.Tooltip");
650 var closeButton =
new GUIButton(
new RectTransform(
new Point(GUI.IntScale(35)), msgBox.InnerFrame.RectTransform,
Anchor.TopRight) { AbsoluteOffset = new Point(GUI.IntScale(8)) },
651 style:
"SocialOverlayCloseButton")
653 OnClicked = closeMsgBox(msgBox)
656 msgBox.Buttons[0].OnClicked += (_, _) =>
661 var loadingBox = GUIMessageBox.CreateLoadingBox(
662 text: TextManager.Get(
"EosLinkSteamToEpicLoadingText"),
663 new[] { (TextManager.Get(
"Cancel"),
new Action<GUIMessageBox>(msgBox => msgBox.Close())) },
664 relativeSize: (0.35f, 0.25f));
665 loadingBox.DrawOnTop =
true;
667 $
"LoginToEpicAccountAsSecondary",
668 EosEpicSecondaryLogin.LoginToLinkedEpicAccount(),
671 if (t.TryGetResult(out Result<Unit, EosEpicSecondaryLogin.LoginError>? result))
673 LocalizedString taskResultMsg;
674 if (result.IsSuccess)
676 taskResultMsg = TextManager.Get(
"EosLinkSuccess");
678 else if (result.TryUnwrapFailure(out var failure))
680 taskResultMsg = TextManager.GetWithVariable(
"EosLinkError",
"[error]", failure.ToString());
684 taskResultMsg = TextManager.GetWithVariable(
"EosLinkError",
"[error]", result.ToString());
687 var msgBox =
new GUIMessageBox(
688 TextManager.Get(
"EosSettings.LinkToEpicAccount"),
692 TextManager.Get(
"OK"),
697 msgBox.Buttons[0].OnClicked = closeMsgBox(msgBox);
706 const string url =
"https://www.epicgames.com/account/connections";
707 var prompt = GameMain.ShowOpenUriPrompt(url);
708 prompt.DrawOnTop =
true;
713 msgBox.Buttons[1].OnClicked += (btn, obj) =>
715 var crossplayChoice = eosEnabled
716 ? EosSteamPrimaryLogin.CrossplayChoice.Disabled
717 : EosSteamPrimaryLogin.CrossplayChoice.Enabled;
718 EosSteamPrimaryLogin.HandleCrossplayChoiceChange(crossplayChoice);
719 GameSettings.SetCurrentConfig(GameSettings.CurrentConfig with { CrossplayChoice = crossplayChoice });
720 GameSettings.SaveCurrentConfig();
721 closeMsgBox(msgBox)(btn, obj);
724 msgBox.Buttons[2].OnClicked += (btn, obj) =>
726 const string emailAddress =
"contact@barotraumagame.com";
727 const string subject =
"Requesting account information deletion";
728 string bodyText =
"I would like to delete all of my account information stored by Epic Games.";
730 bool epicAccountIdAvailable = EosAccount.SelfAccountIds.OfType<EpicAccountId>().
Any();
731 bool steamIdAvailable = SteamManager.GetSteamId().TryUnwrap(out SteamId? steamId);
732 if (!steamIdAvailable && !epicAccountIdAvailable)
734 new GUIMessageBox(TextManager.Get(
"Error"), TextManager.GetWithVariable(
735 "EosSettings.RequestDeletion.NoAccountId",
741 if (epicAccountIdAvailable)
743 bodyText += $
"\n\nMy Epic Account ID(s): {string.Join(",
", EosAccount.SelfAccountIds.OfType<EpicAccountId>().Select(id => id.StringRepresentation))}";
745 if (steamIdAvailable)
747 bodyText += $
"\n\nMy Steam ID: {steamId!.StringRepresentation}";
751 $
"mailto:{emailAddress}?" +
752 $
"subject={Uri.EscapeDataString(subject)}" +
753 $
"&body={Uri.EscapeDataString(bodyText)}";
754 var prompt = GameMain.ShowOpenUriPrompt(uri,
755 TextManager.GetWithVariables(
"OpenLinkInEmailClient",
756 (
"[recipient]", emailAddress),
757 (
"[message]", bodyText)));
761 prompt.DrawOnTop =
true;
764 closeMsgBox(msgBox)(btn, obj);
769 GUIButton.OnClickedHandler closeMsgBox(GUIMessageBox msgBox)
771 return (button, obj) =>
773 RefreshEosStatusText();
774 return msgBox.Close(button, obj);
779 private void PopulateSelectedFriendInfoFrame(
FriendInfo friendInfo)
781 selectedFriendInfoFrame.ClearChildren();
783 new GUILayoutGroup(
new RectTransform(Vector2.One * 0.9f, selectedFriendInfoFrame.RectTransform,
787 RelativeSpacing = 0.02f
792 new RectTransform((1.0f, 0.08f), layout.RectTransform),
793 text: friendInfo.
Name,
794 font: GUIStyle.SubHeadingFont,
795 textAlignment: Alignment.Center)
800 new RectTransform((1.0f, 0.08f), layout.RectTransform),
803 textAlignment: Alignment.TopCenter)
808 var viewProfileButton = addButton(friendInfo.
Id.ViewProfileLabel());
809 viewProfileButton.OnClicked = (_, _) =>
811 friendInfo.
Id.OpenProfile();
816 GameMain.Client is not { IsServerOwner:
true } &&
818 friendInfo.
ConnectCommand.TryUnwrap(out var command) && !command.IsClientConnectedToEndpoint())
820 var joinButton = addButton(TextManager.Get(
"ServerListJoin"));
821 joinButton.OnClicked = (_, _) =>
824 selectedFriendInfoFrame.Visible =
false;
829 if (inviteHandler.HasInviteFrom(friendInfo.
Id))
831 var declineButton = addButton(TextManager.Get(
"DeclineInvite"));
832 declineButton.OnClicked = (_, _) =>
834 inviteHandler.ClearInvitesFrom(friendInfo.
Id);
835 selectedFriendInfoFrame.Visible =
false;
839 if (GameMain.Client is not
null)
841 var inviteButton = addButton(TextManager.Get(
"InviteFriend"));
842 inviteButton.OnClicked = (_, _) =>
844 selectedFriendInfoFrame.Visible =
false;
845 var connectCommandOption = (GameMain.Client?.ClientPeer.ServerEndpoint)
switch
847 LidgrenEndpoint lidgrenEndpoint => Option.Some(
new ConnectCommand(GameMain.Client.Name, lidgrenEndpoint)),
848 P2PEndpoint or
PipeEndpoint => Option.Some(
new ConnectCommand(GameMain.Client.Name, GameMain.Client.ClientPeer.AllServerEndpoints.OfType<
P2PEndpoint>().ToImmutableArray())),
851 if (!connectCommandOption.TryUnwrap(out var connectCommand))
853 DebugConsole.AddWarning($
"Could not create an invite for the endpoint {GameMain.Client?.ClientPeer.ServerEndpoint}.");
857 if (friendInfo.
Id is SteamId friendSteamId && SteamManager.IsInitialized)
859 var steamFriend =
new Steamworks.Friend(friendSteamId.Value);
860 steamFriend.InviteToGame(connectCommand.ToString());
862 else if (friendInfo.
Id is EpicAccountId friendEpicId && EosInterface.Core.IsInitialized)
864 async Task sendEpicInvite()
866 var selfEpicIds = EosInterface.IdQueries.GetLoggedInEpicIds();
867 if (selfEpicIds.Length == 0) {
return; }
869 var selfEpicId = selfEpicIds[0];
870 await EosInterface.Presence.SendInvite(selfEpicId, friendEpicId);
874 $
"Invite{friendEpicId}",
884 =>
new GUIFrame(
new RectTransform((1.0f, 0.05f), layout.RectTransform), style:
null);
886 GUIButton addButton(LocalizedString label)
887 =>
new GUIButton(
new RectTransform((1.0f, 0.08f), layout.RectTransform), label, style:
"SocialOverlayButton");
890 private void RefreshEosStatusText()
892 if (eosStatusTextContainer is
null) {
return; }
894 eosStatusTextContainer.ClearChildren();
895 bool linkedToEpicAccount = EosAccount.SelfAccountIds.OfType<EpicAccountId>().
Any();
896 _ =
new GUITextBlock(
new RectTransform(Vector2.One, eosStatusTextContainer.RectTransform),
897 textAlignment: Alignment.CenterLeft,
899 text: TextManager.Get($
"EosStatus.{EosInterface.Core.CurrentStatus}")
901 + TextManager.Get(linkedToEpicAccount
902 ?
"EosSettings.LinkedToAccount"
903 :
"EosSettings.NotLinkedToAccount"));
905 linkHint.Visible = !linkedToEpicAccount && EosSteamPrimaryLogin.IsNewEosPlayer;
910 EosAccount.RefreshSelfAccountIds(onRefreshComplete: () =>
912 RefreshEosStatusText();
913 lastRefreshTime = DateTime.Now;
915 if (EosInterface.Core.CurrentStatus != EosInterface.Core.Status.Online
916 && !SteamManager.IsInitialized)
918 friendPlayerListBox.ClearChildren();
919 var offlineLabel = insertLabel(TextManager.Get(
"SocialOverlayOffline"), heightScale: 4.0f);
920 offlineLabel.Wrap =
true;
927 friendProvider.RetrieveFriends(),
930 if (!t.TryGetResult(out ImmutableArray<FriendInfo> friends))
935 friendPlayerListBox.ClearChildren();
936 friendPlayerRows.ForEach(f => f.FriendInfo.Dispose());
937 friendPlayerRows.Clear();
939 var friendsOrdered = friends
940 .OrderByDescending(f => f.CurrentStatus)
941 .ThenByDescending(f => inviteHandler.HasInviteFrom(f.Id))
944 bool prevWasOnline =
true;
945 if (friendsOrdered.Length > 0 && friendsOrdered[0].IsOnline)
947 insertLabel(TextManager.Get(
"Label.OnlineLabel"));
950 for (
int friendIndex = 0; friendIndex < friendsOrdered.Length; friendIndex++)
952 var
friend = friendsOrdered[friendIndex];
953 if (prevWasOnline && !
friend.IsOnline)
960 insertLabel(TextManager.Get(
"Label.OfflineLabel"));
963 var friendFrame =
new GUIFrame(CreateRowRectT(friendPlayerListBox.Content),
964 style:
"ListBoxElement")
968 GUILayoutGroup newRowLayout = CreateRowLayout(friendFrame);
969 newRowLayout.RectTransform.RelativeSize = Vector2.One;
970 newRowLayout.RectTransform.ScaleBasis = ScaleBasis.Normal;
971 var newRow =
new PlayerRow(
friend, newRowLayout,
972 invitedYou: inviteHandler.HasInviteFrom(
friend.Id));
973 friendPlayerRows.Add(newRow);
975 prevWasOnline =
friend.IsOnline;
978 contentLayout.Recalculate();
979 friendPlayerListBox.UpdateScrollBarSize();
983 GUITextBlock insertLabel(LocalizedString text,
float heightScale = 0.5f)
985 var labelContainer =
new GUIFrame(CreateRowRectT(friendPlayerListBox.Content), style:
null)
989 Vector2 oldRelativeSize = labelContainer.RectTransform.RelativeSize;
990 labelContainer.RectTransform.RelativeSize
991 = (oldRelativeSize.X, oldRelativeSize.Y * heightScale);
992 return new GUITextBlock(
new RectTransform(Vector2.One, labelContainer.RectTransform),
994 font: GUIStyle.SubHeadingFont);
1002 background.AddToGUIUpdateList();
1004 notificationHandler.AddToGuiUpdateList();
1009 inviteHandler.Update();
1010 notificationHandler.Update();
1012 if (!IsOpen) {
return; }
1014 if (selectedFriendInfoFrame.Visible)
1016 if (PlayerInput.PrimaryMouseButtonClicked()
1017 && selectedFriendInfoFrame.Visible
1018 && !GUI.IsMouseOn(friendPlayerListBox)
1019 && !GUI.IsMouseOn(selectedFriendInfoFrame))
1021 friendPlayerListBox.Deselect();
1024 if (GUI.IsMouseOn(friendPlayerListBox)
1025 && PlayerInput.ScrollWheelSpeed != 0)
1027 friendPlayerListBox.Deselect();
1030 if (!friendPlayerListBox.Selected)
1032 selectedFriendInfoFrame.Visible =
false;
1036 if (eosConfigButton !=
null)
1038 bool eosConfigAccessible = GameMain.NetworkMember ==
null;
1039 if (eosConfigAccessible != eosConfigButton.Enabled)
1041 eosConfigButton.Enabled = eosConfigAccessible;
1042 eosConfigButton.Children.ForEach(c => c.Enabled = eosConfigAccessible);
1043 eosConfigButton.ToolTip = eosConfigAccessible ?
string.Empty : TextManager.Get(
"CantAccessEOSSettingsInMP");
1047 var currentEosStatus = EosInterface.Core.CurrentStatus;
1048 if (currentEosStatus != eosLastKnownStatus)
1050 eosLastKnownStatus = currentEosStatus;
1051 RefreshEosStatusText();
1054 if (DateTime.Now < lastRefreshTime + refreshInterval) {
return; }
1056 RefreshFriendList();
1061 inviteHandler.Dispose();
readonly Option< ConnectCommand > ConnectCommand
LocalizedString StatusText
void RetrieveOrInheritAvatar(Option< Sprite > inheritableAvatar, int size)
static ? SocialOverlay Instance
void AddToGuiUpdateList()
void DisplayBindHintToPlayer()
static readonly LocalizedString ShortcutBindText