v1.3.0.1 (Epic Store release)

This commit is contained in:
Regalis11
2024-03-28 18:34:33 +02:00
parent 81ca8637be
commit 3791670c42
269 changed files with 13160 additions and 2966 deletions
@@ -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;
}
}
@@ -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 "";
}
}
@@ -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();
}
}
@@ -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