v1.3.0.1 (Epic Store release)
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
sealed class FriendInfo : IDisposable
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly AccountId Id;
|
||||
|
||||
public readonly FriendStatus CurrentStatus;
|
||||
public readonly string ServerName;
|
||||
public readonly Option<ConnectCommand> ConnectCommand;
|
||||
|
||||
public readonly FriendProvider Provider;
|
||||
public Option<Sprite> Avatar { get; set; }
|
||||
|
||||
public bool IsInServer
|
||||
=> CurrentStatus == FriendStatus.PlayingBarotrauma && ConnectCommand.IsSome();
|
||||
|
||||
public bool IsOnline
|
||||
=> CurrentStatus != FriendStatus.Offline;
|
||||
|
||||
public LocalizedString StatusText
|
||||
=> CurrentStatus switch
|
||||
{
|
||||
FriendStatus.Offline => "",
|
||||
_ when ConnectCommand.IsSome()
|
||||
=> TextManager.GetWithVariable("FriendPlayingOnServer", "[servername]", ServerName),
|
||||
_ => TextManager.Get($"Friend{CurrentStatus}")
|
||||
};
|
||||
|
||||
public FriendInfo(string name, AccountId id, FriendStatus status, string serverName, Option<ConnectCommand> connectCommand, FriendProvider provider)
|
||||
{
|
||||
Name = name;
|
||||
Id = id;
|
||||
CurrentStatus = status;
|
||||
ServerName = serverName;
|
||||
ConnectCommand = connectCommand;
|
||||
Provider = provider;
|
||||
Avatar = Option.None;
|
||||
}
|
||||
|
||||
public void RetrieveOrInheritAvatar(Option<Sprite> inheritableAvatar, int size)
|
||||
{
|
||||
if (Avatar.IsSome()) { return; }
|
||||
|
||||
if (inheritableAvatar.IsSome())
|
||||
{
|
||||
Avatar = inheritableAvatar;
|
||||
return;
|
||||
}
|
||||
|
||||
TaskPool.Add(
|
||||
"RetrieveAvatar",
|
||||
Provider.RetrieveAvatar(this, size),
|
||||
t =>
|
||||
{
|
||||
if (!t.TryGetResult(out Option<Sprite> spr)) { return; }
|
||||
Avatar = Avatar.Fallback(spr);
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Avatar.TryUnwrap(out var avatar))
|
||||
{
|
||||
avatar.Remove();
|
||||
}
|
||||
Avatar = Option.None;
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
sealed class CompositeFriendProvider : FriendProvider
|
||||
{
|
||||
private readonly ImmutableArray<FriendProvider> providers;
|
||||
|
||||
public CompositeFriendProvider(params FriendProvider[] providers)
|
||||
{
|
||||
this.providers = providers.ToImmutableArray();
|
||||
}
|
||||
|
||||
public override async Task<Option<FriendInfo>> RetrieveFriend(AccountId id)
|
||||
{
|
||||
return (await Task.WhenAll(providers
|
||||
.Select(p => p.RetrieveFriend(id))))
|
||||
.NotNone().FirstOrNone();
|
||||
}
|
||||
|
||||
public override async Task<ImmutableArray<FriendInfo>> RetrieveFriends()
|
||||
{
|
||||
var friends = await Task.WhenAll(providers.Select(p => p.RetrieveFriends()));
|
||||
return friends.SelectMany(a => a).ToImmutableArray();
|
||||
}
|
||||
|
||||
public override async Task<Option<Sprite>> RetrieveAvatar(FriendInfo friend, int avatarSize)
|
||||
{
|
||||
var subTasks = await Task.WhenAll(providers.Select(p => p.RetrieveAvatar(friend, avatarSize)));
|
||||
return subTasks.FirstOrDefault(t => t.IsSome());
|
||||
}
|
||||
|
||||
public override async Task<string> GetSelfUserName()
|
||||
{
|
||||
foreach (var provider in providers)
|
||||
{
|
||||
string userName = await provider.GetSelfUserName();
|
||||
if (userName is { Length: > 0 }) { return userName; }
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
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<Option<FriendInfo>> RetrieveFriend(AccountId id)
|
||||
{
|
||||
if (id is not EpicAccountId friendEaid) { return Option.None; }
|
||||
var selfEaidOption = Eos.EosAccount.SelfAccountIds.OfType<EpicAccountId>().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<ImmutableArray<FriendInfo>> RetrieveFriends()
|
||||
{
|
||||
var epicAccountIdOption = Eos.EosAccount.SelfAccountIds.OfType<EpicAccountId>().FirstOrNone();
|
||||
if (!epicAccountIdOption.TryUnwrap(out var epicAccountId)) { return ImmutableArray<FriendInfo>.Empty; }
|
||||
|
||||
var friendsResult = await EosInterface.Friends.GetFriends(epicAccountId);
|
||||
if (!friendsResult.TryUnwrapSuccess(out var friends)) { return ImmutableArray<FriendInfo>.Empty; }
|
||||
|
||||
return friends.Select(EgsFriendToFriendInfo).ToImmutableArray();
|
||||
}
|
||||
|
||||
private static readonly ImmutableArray<Color> 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<Option<Sprite>> RetrieveAvatar(FriendInfo friend, int avatarSize)
|
||||
{
|
||||
if (friend.Id is not EpicAccountId epicAccount) { return Task.FromResult<Option<Sprite>>(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<string> GetSelfUserName()
|
||||
{
|
||||
var epicAccountIdOption = Eos.EosAccount.SelfAccountIds.OfType<EpicAccountId>().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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
abstract class FriendProvider
|
||||
{
|
||||
public async Task<Option<FriendInfo>> RetrieveFriendWithAvatar(AccountId id, int size)
|
||||
{
|
||||
var friendOption = await RetrieveFriend(id);
|
||||
if (!friendOption.TryUnwrap(out var friend)) { return Option.None; }
|
||||
|
||||
friend.Avatar = await RetrieveAvatar(friend, size);
|
||||
return Option.Some(friend);
|
||||
}
|
||||
|
||||
public abstract Task<Option<FriendInfo>> RetrieveFriend(AccountId id);
|
||||
public abstract Task<ImmutableArray<FriendInfo>> RetrieveFriends();
|
||||
public abstract Task<Option<Sprite>> RetrieveAvatar(FriendInfo friend, int avatarSize);
|
||||
public abstract Task<string> GetSelfUserName();
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Steam;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
sealed class SteamFriendProvider : FriendProvider
|
||||
{
|
||||
private FriendInfo FromSteamFriend(Steamworks.Friend steamFriend)
|
||||
=> new FriendInfo(
|
||||
name: steamFriend.Name ?? "",
|
||||
id: new SteamId(steamFriend.Id),
|
||||
status: steamFriend.State switch
|
||||
{
|
||||
Steamworks.FriendState.Offline => FriendStatus.Offline,
|
||||
Steamworks.FriendState.Invisible => FriendStatus.Offline,
|
||||
_ when steamFriend.IsPlayingThisGame => FriendStatus.PlayingBarotrauma,
|
||||
_ when steamFriend.GameInfo is { GameID: > 0 } => FriendStatus.PlayingAnotherGame,
|
||||
_ => FriendStatus.NotPlaying
|
||||
},
|
||||
serverName: steamFriend.GetRichPresence("servername") ?? "",
|
||||
connectCommand: steamFriend.GetRichPresence("connect") is { } connectCmd
|
||||
? ConnectCommand.Parse(ToolBox.SplitCommand(connectCmd))
|
||||
: Option.None,
|
||||
this);
|
||||
|
||||
public override Task<Option<FriendInfo>> RetrieveFriend(AccountId id)
|
||||
=> Task.FromResult(id is SteamId steamId
|
||||
? Option.Some(FromSteamFriend(new Steamworks.Friend(steamId.Value)))
|
||||
: Option.None);
|
||||
|
||||
public override Task<ImmutableArray<FriendInfo>> RetrieveFriends()
|
||||
=> Task.FromResult(SteamManager.IsInitialized
|
||||
? Steamworks.SteamFriends.GetFriends().Select(FromSteamFriend).ToImmutableArray()
|
||||
: ImmutableArray<FriendInfo>.Empty);
|
||||
|
||||
public override async Task<Option<Sprite>> RetrieveAvatar(FriendInfo friend, int avatarSize)
|
||||
{
|
||||
if (friend.Id is not SteamId steamId) { return Option.None; }
|
||||
|
||||
Func<Steamworks.SteamId, Task<Steamworks.Data.Image?>> avatarFunc = avatarSize switch
|
||||
{
|
||||
<= 24 => Steamworks.SteamFriends.GetSmallAvatarAsync,
|
||||
<= 48 => Steamworks.SteamFriends.GetMediumAvatarAsync,
|
||||
_ => Steamworks.SteamFriends.GetLargeAvatarAsync
|
||||
};
|
||||
|
||||
var img = await avatarFunc(steamId.Value).ToOptionTask();
|
||||
if (!img.TryUnwrap(out var avatarImage)) { return Option.None; }
|
||||
|
||||
if (friend.Avatar.TryUnwrap(out var prevAvatar))
|
||||
{
|
||||
prevAvatar.Remove();
|
||||
}
|
||||
|
||||
var avatarTexture = new Texture2D(GameMain.Instance.GraphicsDevice, (int)avatarImage.Width, (int)avatarImage.Height);
|
||||
avatarTexture.SetData(avatarImage.Data);
|
||||
return Option.Some(new Sprite(texture: avatarTexture, sourceRectangle: null, newOffset: null));
|
||||
}
|
||||
|
||||
public override Task<string> GetSelfUserName()
|
||||
=> Task.FromResult(SteamManager.GetUsername());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Steam;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
static class SocialExtensions
|
||||
{
|
||||
public static LocalizedString ViewProfileLabel(this AccountId accountId)
|
||||
=> accountId switch
|
||||
{
|
||||
SteamId => TextManager.Get("ViewSteamProfile"),
|
||||
EpicAccountId => TextManager.Get("ViewEpicProfile"),
|
||||
_ => "View profile of unknown origin"
|
||||
};
|
||||
|
||||
public static void OpenProfile(this AccountId accountId)
|
||||
{
|
||||
string url = accountId switch
|
||||
{
|
||||
SteamId steamId => $"https://steamcommunity.com/profiles/{steamId.Value}",
|
||||
EpicAccountId epicAccountId => $"https://store.epicgames.com/u/{epicAccountId.EosStringRepresentation}",
|
||||
_ => ""
|
||||
};
|
||||
|
||||
if (SteamManager.IsInitialized)
|
||||
{
|
||||
SteamManager.OverlayCustomUrl(url);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.ShowOpenUriPrompt(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user