v1.3.0.1 (Epic Store release)
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Steam;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Eos;
|
||||
|
||||
internal static class EosAccount
|
||||
{
|
||||
/// <summary>
|
||||
/// The user can have several account IDs if they've linked their Steam account to an Epic Games account
|
||||
/// </summary>
|
||||
public static ImmutableHashSet<AccountId> SelfAccountIds { get; private set; } = ImmutableHashSet<AccountId>.Empty;
|
||||
|
||||
private static readonly Queue<Action> postLoginActions = new();
|
||||
private static bool isLoggedIn;
|
||||
|
||||
public static void RefreshSelfAccountIds(Action? onRefreshComplete = null)
|
||||
{
|
||||
SelfAccountIds = ImmutableHashSet<AccountId>.Empty;
|
||||
var selfPuids = EosInterface.IdQueries.GetLoggedInPuids();
|
||||
|
||||
if (selfPuids.Length == 0)
|
||||
{
|
||||
onRefreshComplete?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
var collectedIds = new Option<ImmutableArray<AccountId>>[selfPuids.Length];
|
||||
|
||||
Action<Task> taskDoneHandler(int index)
|
||||
{
|
||||
void countDoneTask(Task t)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!t.TryGetResult(out Result<ImmutableArray<AccountId>, EosInterface.IdQueries.GetSelfExternalIdError>? result)) { return; }
|
||||
if (!result.TryUnwrapSuccess(out var ids)) { return; }
|
||||
collectedIds[index] = Option.Some(ids);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// If we failed to get IDs from this query, fill in the relevant slot in the collectedIds array
|
||||
// to indicate that the task is done anyway
|
||||
collectedIds[index] = Option.Some(collectedIds[index].Fallback(ImmutableArray<AccountId>.Empty));
|
||||
|
||||
// If all of the tasks are done, merge all of the collected IDs into the hashset
|
||||
if (collectedIds.All(o => o.IsSome()))
|
||||
{
|
||||
SelfAccountIds = collectedIds.NotNone().SelectMany(a => a).ToImmutableHashSet();
|
||||
onRefreshComplete?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return countDoneTask;
|
||||
}
|
||||
|
||||
for (int i = 0; i < selfPuids.Length; i++)
|
||||
{
|
||||
TaskPool.Add($"SelfPlayerRowWithExternalAccountIds{i}",
|
||||
EosInterface.IdQueries.GetSelfExternalAccountIds(selfPuids[i]),
|
||||
taskDoneHandler(i));
|
||||
}
|
||||
}
|
||||
|
||||
#region Message box stuff
|
||||
private static GUIMessageBox? messageBox;
|
||||
|
||||
public static void ReplaceMessageBox(GUIMessageBox? newMessageBox)
|
||||
{
|
||||
messageBox?.Close();
|
||||
messageBox = newMessageBox;
|
||||
}
|
||||
|
||||
public static void CloseMessageBox()
|
||||
=> ReplaceMessageBox(null);
|
||||
|
||||
public static GUIMessageBox CreateLoadingMessageBox((Func<bool> CanCancel, Action Cancel)? actions = null)
|
||||
{
|
||||
var relativeSize = messageBox?.InnerFrame.RectTransform.RelativeSize ?? (0.35f, 0.3f);
|
||||
var newMessageBox = new GUIMessageBox(
|
||||
headerText: LocalizedString.EmptyString,
|
||||
text: LocalizedString.EmptyString,
|
||||
relativeSize: relativeSize,
|
||||
buttons: actions != null ? new[] { TextManager.Get("Cancel") } : Array.Empty<LocalizedString>());
|
||||
|
||||
if (actions != null)
|
||||
{
|
||||
newMessageBox.Buttons[0].Visible = false;
|
||||
newMessageBox.Buttons[0].OnClicked = (_, _) =>
|
||||
{
|
||||
actions.Value.Cancel.Invoke();
|
||||
return false;
|
||||
};
|
||||
new GUICustomComponent(new RectTransform(Vector2.Zero, newMessageBox.InnerFrame.RectTransform), onUpdate:
|
||||
(_, _) =>
|
||||
{
|
||||
bool canCancel = actions.Value.CanCancel.Invoke();
|
||||
newMessageBox.Buttons[0].Visible |= canCancel;
|
||||
newMessageBox.Buttons[0].Enabled = canCancel;
|
||||
});
|
||||
}
|
||||
|
||||
new GUICustomComponent(
|
||||
new RectTransform(Vector2.One * 0.25f, newMessageBox.InnerFrame.RectTransform, Anchor.Center, scaleBasis: ScaleBasis.Smallest),
|
||||
onDraw: static (sb, component) =>
|
||||
{
|
||||
GUIStyle.GenericThrobber.Draw(
|
||||
sb,
|
||||
spriteIndex: (int)(Timing.TotalTime * 20f) % GUIStyle.GenericThrobber.FrameCount,
|
||||
pos: component.Rect.Center.ToVector2(),
|
||||
color: Color.White,
|
||||
origin: GUIStyle.GenericThrobber.FrameSize.ToVector2() * 0.5f,
|
||||
rotate: 0f,
|
||||
scale: component.Rect.Size.ToVector2() / GUIStyle.GenericThrobber.FrameSize.ToVector2());
|
||||
});
|
||||
ReplaceMessageBox(newMessageBox);
|
||||
return newMessageBox;
|
||||
}
|
||||
|
||||
public static Action RetryAction(LocalizedString intro, LocalizedString reason, Action retryAction, Action cancelAction)
|
||||
{
|
||||
return () => GameMain.ExecuteAfterContentFinishedLoading(() => AskRetry(intro, reason, retryAction, cancelAction));
|
||||
}
|
||||
|
||||
private static void AskRetry(LocalizedString intro, LocalizedString failureReason, Action retryAction, Action cancelAction)
|
||||
{
|
||||
var options = new[]
|
||||
{
|
||||
TextManager.Get("Retry"),
|
||||
TextManager.Get("Cancel")
|
||||
};
|
||||
var askHowToProceed = TextManager.Get("AskHowToProceed");
|
||||
|
||||
GUIMessageBox msgBox = new GUIMessageBox(
|
||||
headerText: TextManager.Get("EosIntroHeader"),
|
||||
text: intro + "\n\n" + failureReason + "\n\n" + askHowToProceed,
|
||||
options,
|
||||
relativeSize: (0.4f, 0.4f));
|
||||
|
||||
msgBox.Buttons[0].OnClicked = delegate
|
||||
{
|
||||
retryAction();
|
||||
CloseMessageBox();
|
||||
return false;
|
||||
};
|
||||
|
||||
msgBox.Buttons[1].OnClicked = delegate
|
||||
{
|
||||
cancelAction();
|
||||
CloseMessageBox();
|
||||
return false;
|
||||
};
|
||||
|
||||
ReplaceMessageBox(msgBox);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public static void LoginPlatformSpecific()
|
||||
{
|
||||
if (GameMain.Instance.EgsExchangeCode.TryUnwrap(out var exchangeCode))
|
||||
{
|
||||
LoginEpic(exchangeCode);
|
||||
}
|
||||
else if (SteamManager.IsInitialized)
|
||||
{
|
||||
LoginSteam();
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoginSteam()
|
||||
=> EosSteamPrimaryLogin.Start();
|
||||
|
||||
private static void LoginEpic(string exchangeCode)
|
||||
=> EosEpicPrimaryLogin.Start(exchangeCode);
|
||||
|
||||
public static void OnLoginSuccess()
|
||||
{
|
||||
isLoggedIn = true;
|
||||
while (postLoginActions.TryDequeue(out var action))
|
||||
{
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
public static void ExecuteAfterLogin(Action action)
|
||||
{
|
||||
if (isLoggedIn)
|
||||
{
|
||||
action();
|
||||
return;
|
||||
}
|
||||
postLoginActions.Enqueue(action);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Barotrauma.Eos;
|
||||
|
||||
/// <summary>
|
||||
/// Handles a player that owns a copy of Barotrauma on Epic Games Store (therefore they
|
||||
/// will use their Epic Account ID as their primary identity) logging into EOS.
|
||||
/// </summary>
|
||||
static class EosEpicPrimaryLogin
|
||||
{
|
||||
public static void Start(string exchangeCode)
|
||||
{
|
||||
TaskPool.Add("Eos.Core.LoginEpic", Initialize(exchangeCode), t =>
|
||||
{
|
||||
if (!t.TryGetResult(out Action? action)) { return; }
|
||||
action();
|
||||
});
|
||||
}
|
||||
|
||||
private static void Success()
|
||||
{
|
||||
Eos.EosAccount.CloseMessageBox();
|
||||
Eos.EosAccount.RefreshSelfAccountIds();
|
||||
EosAccount.OnLoginSuccess();
|
||||
}
|
||||
|
||||
private static async Task<Action> Initialize(string exchangeCode)
|
||||
{
|
||||
void retry() => Start(exchangeCode);
|
||||
static void cancel() => EosInterface.Core.CleanupAndQuit();
|
||||
|
||||
var failedToInitializeIntro = TextManager.Get("EosFailedToInitialize");
|
||||
|
||||
var loginResult = await EosInterface.Login.LoginEpicExchangeCode(exchangeCode);
|
||||
if (!loginResult.TryUnwrapSuccess(out var either))
|
||||
{
|
||||
LocalizedString localizedError = $"Login failed with unknown error code.";
|
||||
if (loginResult.TryUnwrapFailure(out EosInterface.Login.LoginError errorCode))
|
||||
{
|
||||
localizedError = TextManager
|
||||
.Get($"EosInterface.Core.InitError.{errorCode}")
|
||||
.Fallback($"Failed to initialize Epic Online Services (error code {errorCode})");
|
||||
}
|
||||
return EosAccount.RetryAction(failedToInitializeIntro, localizedError, retry, cancel);
|
||||
}
|
||||
|
||||
if (either.TryGet(out EosInterface.EosConnectContinuanceToken eosContinuanceToken))
|
||||
{
|
||||
var createProductAccountResult = await EosInterface.Login.CreateProductAccount(eosContinuanceToken);
|
||||
if (!createProductAccountResult.TryUnwrapSuccess(out var puid))
|
||||
{
|
||||
return EosAccount.RetryAction(
|
||||
failedToInitializeIntro,
|
||||
$"Failed to create product user account: {(createProductAccountResult.TryUnwrapFailure(out var failure) ? failure : "unknown")}",
|
||||
retry, cancel);
|
||||
}
|
||||
DebugConsole.NewMessage($"Logged into EOS for the first time with Epic as primary external account ID: {puid}");
|
||||
return Success;
|
||||
}
|
||||
else if (either.TryGet(out EosInterface.ProductUserId puid))
|
||||
{
|
||||
DebugConsole.NewMessage($"Logged into EOS with Epic as primary external account ID: {puid}");
|
||||
return Success;
|
||||
}
|
||||
|
||||
throw new UnreachableCodeException();
|
||||
}
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
#nullable enable
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma.Eos;
|
||||
|
||||
/// <summary>
|
||||
/// Handles a player that owns a copy of Barotrauma on Steam,
|
||||
/// and wishes to link their Steam account to an Epic account
|
||||
/// to interact with friends on Epic Games' account system.
|
||||
/// </summary>
|
||||
static class EosEpicSecondaryLogin
|
||||
{
|
||||
public enum ProbeResult
|
||||
{
|
||||
NoAccount,
|
||||
LinkedExternalAccountsButNoPuid,
|
||||
LoggedIn
|
||||
}
|
||||
|
||||
public static async Task<ProbeResult> ProbeLinkedEpicAccount()
|
||||
{
|
||||
var loginResult = await EosInterface.Login.LoginEpicWithLinkedSteamAccount(EosInterface.Login.LoginEpicFlags.FailWithoutOpeningBrowser);
|
||||
if (!loginResult.TryUnwrapSuccess(out var success)) { return ProbeResult.NoAccount; }
|
||||
|
||||
if (success.TryGet(out EosInterface.ProductUserId _))
|
||||
{
|
||||
// Make Steam account the primary external account just in case
|
||||
await EosInterface.Login.LoginSteam();
|
||||
|
||||
return ProbeResult.LoggedIn;
|
||||
}
|
||||
if (success.TryGet(out EosInterface.EosConnectContinuanceToken? _))
|
||||
{
|
||||
return ProbeResult.LinkedExternalAccountsButNoPuid;
|
||||
}
|
||||
|
||||
return ProbeResult.NoAccount;
|
||||
}
|
||||
|
||||
public enum LoginErrorDesc
|
||||
{
|
||||
NoPrimaryPuid,
|
||||
|
||||
FailedToLogInViaLinkedEpicAccount,
|
||||
|
||||
FailedToForceSteamAsPrimaryExternalAccountId,
|
||||
SteamIsNoLongerLinkedToPuid,
|
||||
SteamPuidMismatchedPreviousPrimaryPuid,
|
||||
|
||||
FailedToLinkSteamAccountToEpicAccount,
|
||||
FailedToCreatePuidForEpicAccount,
|
||||
|
||||
UnhandledErrorCondition
|
||||
}
|
||||
|
||||
public readonly record struct LoginError(
|
||||
LoginErrorDesc ErrorDesc,
|
||||
Option<EosInterface.Login.LoginError> LoginEosConnectError = default,
|
||||
Option<EosInterface.Login.LinkExternalAccountToEpicAccountError> LinkExternalToEpicError = default,
|
||||
Option<EosInterface.Login.CreateProductAccountError> CreatePuidError = default)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
string error = $"LoginError ({ErrorDesc}";
|
||||
if (LoginEosConnectError.TryUnwrap(out var connectError))
|
||||
{
|
||||
error += $", {connectError}";
|
||||
}
|
||||
if (LinkExternalToEpicError.TryUnwrap(out var externalToEpicError))
|
||||
{
|
||||
error += $", {externalToEpicError}";
|
||||
}
|
||||
if (CreatePuidError.TryUnwrap(out var createPuidError))
|
||||
{
|
||||
error += $", {createPuidError}";
|
||||
}
|
||||
return error + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<Result<Unit, LoginError>> LoginToLinkedEpicAccount()
|
||||
{
|
||||
var primaryPuidOption = EosInterface.IdQueries.GetLoggedInPuids().FirstOrNone();
|
||||
if (!primaryPuidOption.TryUnwrap(out var primaryPuid)) { return Result.Failure(new LoginError(LoginErrorDesc.NoPrimaryPuid)); }
|
||||
|
||||
// No matter what happens, refresh account IDs when returning
|
||||
using var janitor = Janitor.Start();
|
||||
janitor.AddAction(static () => EosAccount.RefreshSelfAccountIds());
|
||||
|
||||
async Task<Result<Unit, LoginError>> makeSteamPrimaryExternalAccount()
|
||||
{
|
||||
// By logging into EOS connect via Steam, we make sure that it's
|
||||
// treated as the primary external account, which means that it's
|
||||
// prioritized over the Epic account ID in other EOS functions
|
||||
var loginSteamResult = await EosInterface.Login.LoginSteam();
|
||||
if (!loginSteamResult.TryUnwrapSuccess(out var loginSteamSuccess))
|
||||
{
|
||||
return Result.Failure(new LoginError(LoginErrorDesc.FailedToForceSteamAsPrimaryExternalAccountId));
|
||||
}
|
||||
|
||||
if (!loginSteamSuccess.TryGet(out EosInterface.ProductUserId primaryPuidAgain))
|
||||
{
|
||||
return Result.Failure(new LoginError(LoginErrorDesc.SteamIsNoLongerLinkedToPuid));
|
||||
}
|
||||
|
||||
if (primaryPuid != primaryPuidAgain)
|
||||
{
|
||||
return Result.Failure(new LoginError(LoginErrorDesc.SteamPuidMismatchedPreviousPrimaryPuid));
|
||||
}
|
||||
|
||||
return Result.Success(Unit.Value);
|
||||
}
|
||||
|
||||
const int MaxLoginPasses = 5;
|
||||
for (int loginPass = 0; loginPass < MaxLoginPasses; loginPass++)
|
||||
{
|
||||
// Try to log into EOS via Epic via Steam several times,
|
||||
// only stop once we get a PUID that's linked only to the Epic account
|
||||
|
||||
if (EosInterface.IdQueries.GetLoggedInEpicIds() is { Length: > 0 } loggedInEpicIds)
|
||||
{
|
||||
// Log out of any Epic accounts to reduce chances of ending up in an inconsistent state
|
||||
await Task.WhenAll(loggedInEpicIds.Select(EosInterface.Login.LogoutEpicAccount));
|
||||
}
|
||||
|
||||
var loginResult = await EosInterface.Login.LoginEpicWithLinkedSteamAccount(
|
||||
loginPass == 0
|
||||
? EosInterface.Login.LoginEpicFlags.None
|
||||
: EosInterface.Login.LoginEpicFlags.FailWithoutOpeningBrowser);
|
||||
|
||||
if (loginResult.TryUnwrapFailure(out var loginEpicFailure))
|
||||
{
|
||||
return Result.Failure(new LoginError(LoginErrorDesc.FailedToLogInViaLinkedEpicAccount, LoginEosConnectError: Option.Some(loginEpicFailure)));
|
||||
}
|
||||
if (!loginResult.TryUnwrapSuccess(out var loginEpicSuccess))
|
||||
{
|
||||
throw new UnreachableCodeException();
|
||||
}
|
||||
|
||||
if (loginEpicSuccess.TryGet(out EosInterface.ProductUserId secondPuid))
|
||||
{
|
||||
if (primaryPuid == secondPuid)
|
||||
{
|
||||
// Somehow we've got two external accounts linked
|
||||
// to the same PUID, let's yank them apart
|
||||
|
||||
// Given that the latest ID used to log into this account was
|
||||
// the Epic account, this call to UnlinkExternalAccount will
|
||||
// keep the SteamID linked to this PUID and only unlink the
|
||||
// Epic account, which is what we want.
|
||||
await EosInterface.Login.UnlinkExternalAccount(secondPuid);
|
||||
|
||||
// Once that's done, log back into EOS Connect with
|
||||
// the SteamID as the primary ID
|
||||
if ((await makeSteamPrimaryExternalAccount()).TryUnwrapFailure(out var loginSteamError))
|
||||
{
|
||||
return Result.Failure(loginSteamError);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We already have a PUID for this Epic account. We're done here!
|
||||
|
||||
return Result.Success(Unit.Value);
|
||||
}
|
||||
}
|
||||
else if (loginEpicSuccess.TryGet(out EosInterface.EgsAuthContinuanceToken? egsAuthContinuanceToken))
|
||||
{
|
||||
// We got an EGS Auth continuance token, which means that the player
|
||||
// has provided an Epic account to link the current Steam account to.
|
||||
var linkExternalToEpicResult = await EosInterface.Login.LinkExternalAccountToEpicAccount(egsAuthContinuanceToken);
|
||||
if (linkExternalToEpicResult.TryUnwrapFailure(out var linkExternalToEpicError))
|
||||
{
|
||||
return Result.Failure(new LoginError(LoginErrorDesc.FailedToLinkSteamAccountToEpicAccount, LinkExternalToEpicError: Option.Some(linkExternalToEpicError)));
|
||||
}
|
||||
}
|
||||
else if (loginEpicSuccess.TryGet(out EosInterface.EosConnectContinuanceToken? eosConnectContinuanceToken))
|
||||
{
|
||||
// We got an EOS Connect continuance token, we need
|
||||
// a new Product User ID for the given Epic account.
|
||||
|
||||
var createPuidResult = await EosInterface.Login.CreateProductAccount(eosConnectContinuanceToken);
|
||||
if (createPuidResult.TryUnwrapFailure(out var createPuidError))
|
||||
{
|
||||
return Result.Failure(new LoginError(LoginErrorDesc.FailedToCreatePuidForEpicAccount, CreatePuidError: Option.Some(createPuidError)));
|
||||
}
|
||||
|
||||
// PUID has been created! We're done here!
|
||||
return Result.Success(Unit.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new UnreachableCodeException();
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Failure(new LoginError(LoginErrorDesc.UnhandledErrorCondition));
|
||||
}
|
||||
}
|
||||
+321
@@ -0,0 +1,321 @@
|
||||
#nullable enable
|
||||
using Barotrauma.Steam;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Barotrauma.Eos;
|
||||
|
||||
/// <summary>
|
||||
/// Handles a player that owns a copy of Barotrauma on Steam (therefore they
|
||||
/// will use their SteamID as their primary identity) logging into EOS.
|
||||
/// </summary>
|
||||
public static class EosSteamPrimaryLogin
|
||||
{
|
||||
public static bool IsNewEosPlayer = false;
|
||||
|
||||
public enum CrossplayChoice
|
||||
{
|
||||
Unknown,
|
||||
Enabled,
|
||||
Disabled
|
||||
}
|
||||
|
||||
public static CrossplayChoice EnableCrossplay
|
||||
{
|
||||
get => GameSettings.CurrentConfig.CrossplayChoice;
|
||||
set
|
||||
{
|
||||
GameSettings.SetCurrentConfig(GameSettings.CurrentConfig with { CrossplayChoice = value });
|
||||
GameAnalyticsManager.AddDesignEvent("Crossplay:" + value);
|
||||
GameSettings.SaveCurrentConfig();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Start()
|
||||
{
|
||||
TaskPool.Add(
|
||||
"EosSteamPrimaryLogin",
|
||||
Initialize(),
|
||||
OnTaskComplete);
|
||||
}
|
||||
|
||||
private static void OnTaskComplete(Task t)
|
||||
{
|
||||
if (t.Exception?.GetInnermost() is { } exception)
|
||||
{
|
||||
DebugConsole.ThrowError($"{nameof(EosSteamPrimaryLogin)}.{nameof(Initialize)} failed with exception {exception.Message} {exception.StackTrace.CleanupStackTrace()}");
|
||||
}
|
||||
if (!t.TryGetResult(out Action? action)) { return; }
|
||||
action();
|
||||
}
|
||||
|
||||
private static void Success()
|
||||
{
|
||||
Eos.EosAccount.CloseMessageBox();
|
||||
Eos.EosAccount.RefreshSelfAccountIds();
|
||||
EosAccount.OnLoginSuccess();
|
||||
}
|
||||
|
||||
private static async Task<Action> Initialize()
|
||||
{
|
||||
static void retry() => Start();
|
||||
static void cancel() => EosInterface.Core.CleanupAndQuit();
|
||||
var failedToInitializeIntro = TextManager.Get("EosFailedToInitialize");
|
||||
|
||||
if (EnableCrossplay is CrossplayChoice.Unknown)
|
||||
{
|
||||
// Don't even try to initialize EOS until we get the user's consent
|
||||
return SteamAccountHasNoLinkedPuid();
|
||||
}
|
||||
if (EnableCrossplay is CrossplayChoice.Disabled)
|
||||
{
|
||||
// Crossplay is disabled, return immediately
|
||||
return Success;
|
||||
}
|
||||
|
||||
if (!SteamManager.IsInitialized)
|
||||
{
|
||||
return EosAccount.RetryAction(failedToInitializeIntro, "Steamworks is not initialized", retry, cancel);
|
||||
}
|
||||
|
||||
Result<Unit, EosInterface.Core.InitError> initResult = Result.Failure(EosInterface.Core.InitError.UnhandledErrorCondition);
|
||||
CrossThread.RequestExecutionOnMainThread(() => initResult = EosInterface.Core.Init(EosInterface.ApplicationCredentials.Client, enableOverlay: false));
|
||||
if (initResult.TryUnwrapFailure(out var initError))
|
||||
{
|
||||
return EosAccount.RetryAction(failedToInitializeIntro, GetErrorMessage(initError), retry, cancel);
|
||||
}
|
||||
|
||||
var steamPuidResult = await EosInterface.Login.LoginSteam();
|
||||
|
||||
if (!steamPuidResult.TryUnwrapSuccess(out var steamPuidOrContToken))
|
||||
{
|
||||
return EosAccount.RetryAction(failedToInitializeIntro, $"Failed to log into EOS with Steam account: {steamPuidResult}", retry, cancel);
|
||||
}
|
||||
|
||||
if (steamPuidOrContToken.TryGet(out EosInterface.ProductUserId puid))
|
||||
{
|
||||
return await SteamAccountHasLinkedPuid(puid);
|
||||
}
|
||||
else if (steamPuidOrContToken.TryGet(out EosInterface.EosConnectContinuanceToken _))
|
||||
{
|
||||
return SteamAccountHasNoLinkedPuid();
|
||||
}
|
||||
throw new UnreachableCodeException();
|
||||
}
|
||||
|
||||
private static async Task<Action> SteamAccountHasLinkedPuid(EosInterface.ProductUserId _)
|
||||
{
|
||||
await EosEpicSecondaryLogin.ProbeLinkedEpicAccount();
|
||||
return Success;
|
||||
}
|
||||
|
||||
private static Action SteamAccountHasNoLinkedPuid()
|
||||
{
|
||||
return () => GameMain.ExecuteAfterContentFinishedLoading(AskPlayerToEnableCrossplay);
|
||||
}
|
||||
|
||||
private static void AskPlayerToEnableCrossplay()
|
||||
{
|
||||
LocalizedString[] options =
|
||||
{
|
||||
TextManager.Get("EnableCrossplay"),
|
||||
TextManager.Get("DisableCrossplay")
|
||||
};
|
||||
|
||||
var introText = "\n" + LocalizedString.Join(
|
||||
"\n\n",
|
||||
Enumerable.Range(0, 3).Select(static i => TextManager.Get($"EosIntro{i}"))) + "\n";
|
||||
|
||||
GUIMessageBox msgBox = new GUIMessageBox(
|
||||
headerText: TextManager.Get("EosIntroHeader"),
|
||||
text: introText,
|
||||
Array.Empty<LocalizedString>(),
|
||||
relativeSize: (0.8f, 0.5f));
|
||||
msgBox.Content.ChildAnchor = Anchor.TopCenter;
|
||||
msgBox.Content.Stretch = true;
|
||||
msgBox.InnerFrame.RectTransform.ScaleBasis = ScaleBasis.Smallest;
|
||||
|
||||
int? selectedRadioButton = null;
|
||||
var radioButtonLayout = new GUILayoutGroup(new RectTransform(Vector2.One, msgBox.Content.RectTransform)) { Stretch = true };
|
||||
var radioButtonGroup = new GUIRadioButtonGroup();
|
||||
for (int i = 0; i < options.Length; i++)
|
||||
{
|
||||
var radioButton = new GUITickBox(
|
||||
new RectTransform(Vector2.One, radioButtonLayout.RectTransform),
|
||||
label: options[i],
|
||||
style: "GUIRadioButton");
|
||||
radioButtonGroup.AddRadioButton(
|
||||
key: i,
|
||||
radioButton: radioButton);
|
||||
radioButton.RectTransform.MinSize = Point.Zero;
|
||||
radioButton.RectTransform.MaxSize = new Point(int.MaxValue);
|
||||
radioButton.RectTransform.ScaleBasis = ScaleBasis.Normal;
|
||||
radioButton.RectTransform.RelativeSize = Vector2.One;
|
||||
}
|
||||
|
||||
//spacing
|
||||
new GUIFrame(new RectTransform(new Point(0), radioButtonLayout.RectTransform) { MinSize = new Point(0, GUI.IntScale(30)) }, style: null);
|
||||
|
||||
var submitButton = new GUIButton(new RectTransform(Vector2.One, radioButtonLayout.RectTransform),
|
||||
TextManager.Get("Submit").Fallback("Submit")) { Enabled = false };
|
||||
|
||||
radioButtonGroup.OnSelect = (rbg, val) =>
|
||||
{
|
||||
selectedRadioButton = val;
|
||||
submitButton.Enabled = true;
|
||||
};
|
||||
msgBox.ForceLayoutRecalculation();
|
||||
var maxOptionWidth = options.Select(o => GUIStyle.Font.MeasureString(o).X).Max();
|
||||
int extraWidth = (int)(GUIStyle.Font.LineHeight * 4f);
|
||||
radioButtonLayout.RectTransform.IsFixedSize = true;
|
||||
radioButtonLayout.RectTransform.NonScaledSize = new Point((int)maxOptionWidth + extraWidth, (int)(GUIStyle.Font.LineHeight * options.Length * 1.5f) + submitButton.Rect.Height * 2);
|
||||
msgBox.ForceLayoutRecalculation();
|
||||
|
||||
static void textSizeFixHack(GUITextBlock textBlock, int width)
|
||||
{
|
||||
textBlock.RectTransform.IsFixedSize = true;
|
||||
textBlock.RectTransform.MinSize = Point.Zero;
|
||||
textBlock.RectTransform.MaxSize = new Point(int.MaxValue);
|
||||
textBlock.RectTransform.NonScaledSize = new Point(width, 0);
|
||||
textBlock.CalculateHeightFromText();
|
||||
}
|
||||
textSizeFixHack(msgBox.Header, (int)(msgBox.InnerFrame.Rect.Width * 0.9f));
|
||||
textSizeFixHack(msgBox.Text, (int)(msgBox.InnerFrame.Rect.Width * 0.9f));
|
||||
msgBox.ForceLayoutRecalculation();
|
||||
|
||||
msgBox.InnerFrame.RectTransform.IsFixedSize = true;
|
||||
msgBox.InnerFrame.RectTransform.NonScaledSize = new Point(
|
||||
msgBox.InnerFrame.Rect.Width,
|
||||
(int)((msgBox.Content.Children.Select(c => c.Rect.Height + GUI.IntScale(5)).Sum() + GUIStyle.Font.LineHeight) / 0.9f));
|
||||
|
||||
submitButton.OnClicked = delegate
|
||||
{
|
||||
switch (selectedRadioButton)
|
||||
{
|
||||
case 0:
|
||||
PlayerWantsToEnableCrossplay();
|
||||
return false;
|
||||
case 1:
|
||||
PlayerWantsToDisableCrossplay();
|
||||
return false;
|
||||
default:
|
||||
throw new UnreachableCodeException();
|
||||
}
|
||||
};
|
||||
|
||||
Eos.EosAccount.ReplaceMessageBox(msgBox);
|
||||
}
|
||||
|
||||
private static void PlayerWantsToEnableCrossplay()
|
||||
{
|
||||
Eos.EosAccount.CreateLoadingMessageBox();
|
||||
TaskPool.Add(
|
||||
nameof(EnableCrossplayAndCreatePuidWithOneToken),
|
||||
EnableCrossplayAndCreatePuidWithOneToken(),
|
||||
OnTaskComplete);
|
||||
}
|
||||
|
||||
private static void PlayerWantsToDisableCrossplay()
|
||||
{
|
||||
EosInterface.Core.CleanupAndQuit();
|
||||
var action = DisableCrossplay();
|
||||
action();
|
||||
}
|
||||
|
||||
private static async Task<Action> EnableCrossplayAndCreatePuidWithOneToken()
|
||||
{
|
||||
void retry() => PlayerWantsToEnableCrossplay();
|
||||
static void cancel() => EosInterface.Core.CleanupAndQuit();
|
||||
var failedToCreatePuidIntro = TextManager.Get("FailedToCreatePuid");
|
||||
|
||||
EnableCrossplay = CrossplayChoice.Enabled;
|
||||
|
||||
if (!SteamManager.IsInitialized)
|
||||
{
|
||||
return EosAccount.RetryAction(failedToCreatePuidIntro, "Steamworks is not initialized", retry, cancel);
|
||||
}
|
||||
|
||||
Result<Unit, EosInterface.Core.InitError> initResult = Result.Failure(EosInterface.Core.InitError.UnhandledErrorCondition);
|
||||
CrossThread.RequestExecutionOnMainThread(() => initResult = EosInterface.Core.Init(EosInterface.ApplicationCredentials.Client, enableOverlay: false));
|
||||
if (initResult.TryUnwrapFailure(out var initError))
|
||||
{
|
||||
return EosAccount.RetryAction(failedToCreatePuidIntro, GetErrorMessage(initError), retry, cancel);
|
||||
}
|
||||
|
||||
EosInterface.EosConnectContinuanceToken steamEosContinuanceToken;
|
||||
var steamLoginResult = await EosInterface.Login.LoginSteam();
|
||||
if (steamLoginResult.TryUnwrapSuccess(out var either))
|
||||
{
|
||||
if (either.TryGet(out EosInterface.EosConnectContinuanceToken newSteamCt))
|
||||
{
|
||||
steamEosContinuanceToken = newSteamCt;
|
||||
}
|
||||
else
|
||||
{
|
||||
await EosEpicSecondaryLogin.ProbeLinkedEpicAccount();
|
||||
SocialOverlay.Instance?.DisplayBindHintToPlayer();
|
||||
return Success;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return EosAccount.RetryAction(failedToCreatePuidIntro, $"Failed to refresh continuance token: {steamLoginResult}", retry, cancel);
|
||||
}
|
||||
|
||||
var newPuidResult = await EosInterface.Login.CreateProductAccount(steamEosContinuanceToken);
|
||||
if (newPuidResult.IsFailure)
|
||||
{
|
||||
return EosAccount.RetryAction(failedToCreatePuidIntro, $"Failed to create PUID: {newPuidResult}", retry, cancel);
|
||||
}
|
||||
|
||||
IsNewEosPlayer = true;
|
||||
SocialOverlay.Instance?.DisplayBindHintToPlayer();
|
||||
return Success;
|
||||
}
|
||||
|
||||
private static LocalizedString GetErrorMessage(EosInterface.Core.InitError errorCode)
|
||||
{
|
||||
return TextManager.Get($"EosInterface.Core.InitError.{errorCode}").Fallback($"Failed to initialize Epic Online Services (error code {errorCode})");
|
||||
}
|
||||
|
||||
private static Action DisableCrossplay()
|
||||
{
|
||||
EnableCrossplay = CrossplayChoice.Disabled;
|
||||
|
||||
return Success;
|
||||
}
|
||||
|
||||
public static void HandleCrossplayChoiceChange(CrossplayChoice newChoice)
|
||||
{
|
||||
if (StoreIntegration.CurrentStore != StoreIntegration.Store.Steam) { return; }
|
||||
if (GameSettings.CurrentConfig.CrossplayChoice == newChoice) { return; }
|
||||
|
||||
switch (newChoice)
|
||||
{
|
||||
case CrossplayChoice.Disabled:
|
||||
EosInterface.Core.CleanupAndQuit();
|
||||
break;
|
||||
case CrossplayChoice.Enabled:
|
||||
if (EosInterface.Core.CurrentStatus == EosInterface.Core.Status.ShutDown)
|
||||
{
|
||||
var msgBox = new GUIMessageBox(TextManager.Get("EosAllowCrossplay"),
|
||||
TextManager.Get("RestartRequiredGeneric"), new[] { TextManager.Get("ok") })
|
||||
{
|
||||
DrawOnTop = true
|
||||
};
|
||||
msgBox.Buttons[0].OnClicked = (_, _) =>
|
||||
{
|
||||
msgBox.Close();
|
||||
return true;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayerWantsToEnableCrossplay();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user