using System; using System.Collections.Immutable; using System.Globalization; using System.Linq; using System.Numerics; using System.Threading.Tasks; using Barotrauma.Extensions; using Barotrauma.Networking; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Vector2 = Microsoft.Xna.Framework.Vector2; namespace Barotrauma; sealed class EpicFriendProvider : FriendProvider { private FriendInfo EgsFriendToFriendInfo(EosInterface.EgsFriend egsFriend) { return new FriendInfo( name: egsFriend.DisplayName, id: egsFriend.EpicAccountId, status: egsFriend.Status, serverName: egsFriend.ServerName, connectCommand: ConnectCommand.Parse(egsFriend.ConnectCommand), provider: this); } public override async Task> RetrieveFriend(AccountId id) { if (id is not EpicAccountId friendEaid) { return Option.None; } var selfEaidOption = Eos.EosAccount.SelfAccountIds.OfType().FirstOrNone(); if (!selfEaidOption.TryUnwrap(out var selfEaid)) { return Option.None; } var friendResult = await EosInterface.Friends.GetFriend(selfEaid, friendEaid); if (!friendResult.TryUnwrapSuccess(out var f)) { return Option.None; } return Option.Some(EgsFriendToFriendInfo(f)); } public override async Task> RetrieveFriends() { var epicAccountIdOption = Eos.EosAccount.SelfAccountIds.OfType().FirstOrNone(); if (!epicAccountIdOption.TryUnwrap(out var epicAccountId)) { return ImmutableArray.Empty; } var friendsResult = await EosInterface.Friends.GetFriends(epicAccountId); if (!friendsResult.TryUnwrapSuccess(out var friends)) { return ImmutableArray.Empty; } return friends.Select(EgsFriendToFriendInfo).ToImmutableArray(); } private static readonly ImmutableArray egsProfileColors = new[] { // Cyan new Color(0xfff0a950), // Dark green new Color(0xff2b9850), // Yellow-green new Color(0xff2ba08e), // Purple new Color(0xff951249), // Purple-red new Color(0xff9a0c71), // Red new Color(0xff3e29c6), // Orange new Color(0xff3875ed), // Yellow-orange new Color(0xff1ea5ed) }.ToImmutableArray(); public override Task> RetrieveAvatar(FriendInfo friend, int avatarSize) { if (friend.Id is not EpicAccountId epicAccount) { return Task.FromResult>(Option.None); } // EGS doesn't have profile pictures yet. // Instead, each player gets a color based on their account ID. // This is an educated guess of how Epic picks that color, and is likely incorrect for IDs nearing the boundaries of ranges: Color color = Color.Black; if (ulong.TryParse(epicAccount.EosStringRepresentation[..16], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var mostSignificant64Bits) && ulong.TryParse(epicAccount.EosStringRepresentation[16..], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var leastSignificant64Bits)) { BigInteger fullId = mostSignificant64Bits; fullId <<= 64; fullId |= leastSignificant64Bits; BigInteger idMaxValue = ulong.MaxValue; idMaxValue <<= 64; idMaxValue |= ulong.MaxValue; BigInteger middleRangeSize = idMaxValue / 7; BigInteger firstRangeSize = middleRangeSize / 2; if (fullId <= firstRangeSize) { color = egsProfileColors[0]; } else { color = egsProfileColors[(int)((fullId - firstRangeSize) / middleRangeSize) + 1]; } } char glyphChar = friend.Name.FallbackNullOrEmpty("?")[0]; var font = GUIStyle.UnscaledSmallFont.GetFontForStr(glyphChar.ToString()); Texture2D tex = null; if (font != null) { var (glyphData, glyphTexture) = font.GetGlyphDataAndTextureForChar(glyphChar); var glyphSize = new Vector2(glyphData.TexCoords.Width, glyphData.TexCoords.Height); int texSize = (int)Math.Max( MathUtils.RoundUpToPowerOfTwo((uint)(font.LineHeight * 1.5f)), MathUtils.RoundUpToPowerOfTwo((uint)(font.LineHeight * 1.5f))); if (glyphTexture is not null) { var glyphTextureData = new Color[(int)glyphSize.X * (int)glyphSize.Y]; glyphTexture.GetData( level: 0, rect: glyphData.TexCoords, data: glyphTextureData, startIndex: 0, elementCount: glyphTextureData.Length); var texData = Enumerable.Range(0, texSize * texSize).Select(_ => color).ToArray(); var start = (new Vector2(texSize, texSize) / 2 - glyphSize / 2).ToPoint(); var end = start + glyphSize.ToPoint(); for (int x = start.X; x < end.X; x++) { for (int y = start.Y; y < end.Y; y++) { texData[x + y * texSize] = Color.Lerp( color, Color.White, glyphTextureData[(x - start.X) + (y - start.Y) * (int)glyphSize.X].A / 255f); } } tex = new Texture2D(GameMain.GraphicsDeviceManager.GraphicsDevice, texSize, texSize); tex.SetData(texData); } } if (tex is null) { tex = new Texture2D(GameMain.GraphicsDeviceManager.GraphicsDevice, 2, 2); tex.SetData(new[] { color, color, color, color }); } var sprite = new Sprite(tex, null, null); return Task.FromResult(Option.Some(sprite)); } public override async Task GetSelfUserName() { var epicAccountIdOption = Eos.EosAccount.SelfAccountIds.OfType().FirstOrNone(); if (!epicAccountIdOption.TryUnwrap(out var epicAccountId)) { return ""; } var selfInfoResult = await EosInterface.Friends.GetSelfUserInfo(epicAccountId); if (!selfInfoResult.TryUnwrapSuccess(out var selfInfo)) { return ""; } return selfInfo.DisplayName; } }