2685 lines
91 KiB
C++
2685 lines
91 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "CommonUserSubsystem.h"
|
|
#include "Engine/GameInstance.h"
|
|
#include "Engine/LocalPlayer.h"
|
|
#include "GameFramework/PlayerController.h"
|
|
#include "GameFramework/PlayerState.h"
|
|
#include "InputKeyEventArgs.h"
|
|
#include "NativeGameplayTags.h"
|
|
#include "TimerManager.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(CommonUserSubsystem)
|
|
|
|
#if COMMONUSER_OSSV1
|
|
#include "OnlineSubsystemNames.h"
|
|
#include "OnlineSubsystemUtils.h"
|
|
#else
|
|
#include "Online/Auth.h"
|
|
#include "Online/ExternalUI.h"
|
|
#include "Online/OnlineResult.h"
|
|
#include "Online/OnlineServices.h"
|
|
#include "Online/OnlineServicesEngineUtils.h"
|
|
#include "Online/Privileges.h"
|
|
|
|
using namespace UE::Online;
|
|
#endif
|
|
|
|
DECLARE_LOG_CATEGORY_EXTERN(LogCommonUser, Log, All);
|
|
DEFINE_LOG_CATEGORY(LogCommonUser);
|
|
|
|
UE_DEFINE_GAMEPLAY_TAG(FCommonUserTags::SystemMessage_Error, "SystemMessage.Error");
|
|
UE_DEFINE_GAMEPLAY_TAG(FCommonUserTags::SystemMessage_Warning, "SystemMessage.Warning");
|
|
UE_DEFINE_GAMEPLAY_TAG(FCommonUserTags::SystemMessage_Display, "SystemMessage.Display");
|
|
UE_DEFINE_GAMEPLAY_TAG(FCommonUserTags::SystemMessage_Error_InitializeLocalPlayerFailed, "SystemMessage.Error.InitializeLocalPlayerFailed");
|
|
|
|
UE_DEFINE_GAMEPLAY_TAG(FCommonUserTags::Platform_Trait_RequiresStrictControllerMapping, "Platform.Trait.RequiresStrictControllerMapping");
|
|
UE_DEFINE_GAMEPLAY_TAG(FCommonUserTags::Platform_Trait_SingleOnlineUser, "Platform.Trait.SingleOnlineUser");
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// UCommonUserInfo
|
|
|
|
UCommonUserInfo::FCachedData* UCommonUserInfo::GetCachedData(ECommonUserOnlineContext Context)
|
|
{
|
|
// Look up directly, game has a separate cache than default
|
|
FCachedData* FoundData = CachedDataMap.Find(Context);
|
|
if (FoundData)
|
|
{
|
|
return FoundData;
|
|
}
|
|
|
|
// Now try system resolution
|
|
UCommonUserSubsystem* Subsystem = GetSubsystem();
|
|
|
|
ECommonUserOnlineContext ResolvedContext = Subsystem->ResolveOnlineContext(Context);
|
|
return CachedDataMap.Find(ResolvedContext);
|
|
}
|
|
|
|
const UCommonUserInfo::FCachedData* UCommonUserInfo::GetCachedData(ECommonUserOnlineContext Context) const
|
|
{
|
|
return const_cast<UCommonUserInfo*>(this)->GetCachedData(Context);
|
|
}
|
|
|
|
void UCommonUserInfo::UpdateCachedPrivilegeResult(ECommonUserPrivilege Privilege, ECommonUserPrivilegeResult Result, ECommonUserOnlineContext Context)
|
|
{
|
|
// This should only be called with a resolved and valid type
|
|
FCachedData* GameCache = GetCachedData(ECommonUserOnlineContext::Game);
|
|
FCachedData* ContextCache = GetCachedData(Context);
|
|
|
|
if (!ensure(GameCache && ContextCache))
|
|
{
|
|
// Should always be valid
|
|
return;
|
|
}
|
|
|
|
// Update direct cache first
|
|
ContextCache->CachedPrivileges.Add(Privilege, Result);
|
|
|
|
if (GameCache != ContextCache)
|
|
{
|
|
// Look for another context to merge into game
|
|
ECommonUserPrivilegeResult GameContextResult = Result;
|
|
ECommonUserPrivilegeResult OtherContextResult = ECommonUserPrivilegeResult::Available;
|
|
for (TPair<ECommonUserOnlineContext, FCachedData>& Pair : CachedDataMap)
|
|
{
|
|
if (&Pair.Value != ContextCache && &Pair.Value != GameCache)
|
|
{
|
|
ECommonUserPrivilegeResult* FoundResult = Pair.Value.CachedPrivileges.Find(Privilege);
|
|
if (FoundResult)
|
|
{
|
|
OtherContextResult = *FoundResult;
|
|
}
|
|
else
|
|
{
|
|
OtherContextResult = ECommonUserPrivilegeResult::Unknown;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (GameContextResult == ECommonUserPrivilegeResult::Available && OtherContextResult != ECommonUserPrivilegeResult::Available)
|
|
{
|
|
// Other context is worse, use that
|
|
GameContextResult = OtherContextResult;
|
|
}
|
|
|
|
GameCache->CachedPrivileges.Add(Privilege, GameContextResult);
|
|
}
|
|
}
|
|
|
|
void UCommonUserInfo::UpdateCachedNetId(const FUniqueNetIdRepl& NewId, ECommonUserOnlineContext Context)
|
|
{
|
|
FCachedData* ContextCache = GetCachedData(Context);
|
|
|
|
if (ensure(ContextCache))
|
|
{
|
|
ContextCache->CachedNetId = NewId;
|
|
|
|
// Update the nickname
|
|
const UCommonUserSubsystem* Subsystem = GetSubsystem();
|
|
if (ensure(Subsystem))
|
|
{
|
|
if (bIsGuest)
|
|
{
|
|
if (ContextCache->CachedNickname.IsEmpty())
|
|
{
|
|
// Set a default guest name if it is empty, can be changed with SetNickname
|
|
ContextCache->CachedNickname = NSLOCTEXT("CommonUser", "GuestNickname", "Guest").ToString();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Refresh with the system nickname, overrides SetNickname
|
|
ContextCache->CachedNickname = Subsystem->GetLocalUserNickname(GetPlatformUserId(), Context);
|
|
}
|
|
}
|
|
}
|
|
|
|
// We don't merge the ids because of how guests work
|
|
}
|
|
|
|
class UCommonUserSubsystem* UCommonUserInfo::GetSubsystem() const
|
|
{
|
|
return Cast<UCommonUserSubsystem>(GetOuter());
|
|
}
|
|
|
|
bool UCommonUserInfo::IsLoggedIn() const
|
|
{
|
|
return (InitializationState == ECommonUserInitializationState::LoggedInLocalOnly || InitializationState == ECommonUserInitializationState::LoggedInOnline);
|
|
}
|
|
|
|
bool UCommonUserInfo::IsDoingLogin() const
|
|
{
|
|
return (InitializationState == ECommonUserInitializationState::DoingInitialLogin || InitializationState == ECommonUserInitializationState::DoingNetworkLogin);
|
|
}
|
|
|
|
ECommonUserPrivilegeResult UCommonUserInfo::GetCachedPrivilegeResult(ECommonUserPrivilege Privilege, ECommonUserOnlineContext Context) const
|
|
{
|
|
const FCachedData* FoundCached = GetCachedData(Context);
|
|
|
|
if (FoundCached)
|
|
{
|
|
const ECommonUserPrivilegeResult* FoundResult = FoundCached->CachedPrivileges.Find(Privilege);
|
|
if (FoundResult)
|
|
{
|
|
return *FoundResult;
|
|
}
|
|
}
|
|
return ECommonUserPrivilegeResult::Unknown;
|
|
}
|
|
|
|
ECommonUserAvailability UCommonUserInfo::GetPrivilegeAvailability(ECommonUserPrivilege Privilege) const
|
|
{
|
|
// Bad feature or user
|
|
if ((int32)Privilege < 0 || (int32)Privilege >= (int32)ECommonUserPrivilege::Invalid_Count || InitializationState == ECommonUserInitializationState::Invalid)
|
|
{
|
|
return ECommonUserAvailability::Invalid;
|
|
}
|
|
|
|
ECommonUserPrivilegeResult CachedResult = GetCachedPrivilegeResult(Privilege, ECommonUserOnlineContext::Game);
|
|
|
|
// First handle explicit failures
|
|
switch (CachedResult)
|
|
{
|
|
case ECommonUserPrivilegeResult::LicenseInvalid:
|
|
case ECommonUserPrivilegeResult::VersionOutdated:
|
|
case ECommonUserPrivilegeResult::AgeRestricted:
|
|
return ECommonUserAvailability::AlwaysUnavailable;
|
|
|
|
case ECommonUserPrivilegeResult::NetworkConnectionUnavailable:
|
|
case ECommonUserPrivilegeResult::AccountTypeRestricted:
|
|
case ECommonUserPrivilegeResult::AccountUseRestricted:
|
|
case ECommonUserPrivilegeResult::PlatformFailure:
|
|
return ECommonUserAvailability::CurrentlyUnavailable;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (bIsGuest)
|
|
{
|
|
// Guests can only play, cannot use online features
|
|
if (Privilege == ECommonUserPrivilege::CanPlay)
|
|
{
|
|
return ECommonUserAvailability::NowAvailable;
|
|
}
|
|
else
|
|
{
|
|
return ECommonUserAvailability::AlwaysUnavailable;
|
|
}
|
|
}
|
|
|
|
// Check network status
|
|
if (Privilege == ECommonUserPrivilege::CanPlayOnline ||
|
|
Privilege == ECommonUserPrivilege::CanUseCrossPlay ||
|
|
Privilege == ECommonUserPrivilege::CanCommunicateViaTextOnline ||
|
|
Privilege == ECommonUserPrivilege::CanCommunicateViaVoiceOnline)
|
|
{
|
|
UCommonUserSubsystem* Subsystem = GetSubsystem();
|
|
if (ensure(Subsystem) && !Subsystem->HasOnlineConnection(ECommonUserOnlineContext::Game))
|
|
{
|
|
return ECommonUserAvailability::CurrentlyUnavailable;
|
|
}
|
|
}
|
|
|
|
if (InitializationState == ECommonUserInitializationState::FailedtoLogin)
|
|
{
|
|
// Failed a prior login attempt
|
|
return ECommonUserAvailability::CurrentlyUnavailable;
|
|
}
|
|
else if (InitializationState == ECommonUserInitializationState::Unknown || InitializationState == ECommonUserInitializationState::DoingInitialLogin)
|
|
{
|
|
// Haven't logged in yet
|
|
return ECommonUserAvailability::PossiblyAvailable;
|
|
}
|
|
else if (InitializationState == ECommonUserInitializationState::LoggedInLocalOnly || InitializationState == ECommonUserInitializationState::DoingNetworkLogin)
|
|
{
|
|
// Local login succeeded so play checks are valid
|
|
if (Privilege == ECommonUserPrivilege::CanPlay && CachedResult == ECommonUserPrivilegeResult::Available)
|
|
{
|
|
return ECommonUserAvailability::NowAvailable;
|
|
}
|
|
|
|
// Haven't logged in online yet
|
|
return ECommonUserAvailability::PossiblyAvailable;
|
|
}
|
|
else if (InitializationState == ECommonUserInitializationState::LoggedInOnline)
|
|
{
|
|
// Fully logged in
|
|
if (CachedResult == ECommonUserPrivilegeResult::Available)
|
|
{
|
|
return ECommonUserAvailability::NowAvailable;
|
|
}
|
|
|
|
// Failed for other reason
|
|
return ECommonUserAvailability::CurrentlyUnavailable;
|
|
}
|
|
|
|
return ECommonUserAvailability::Unknown;
|
|
}
|
|
|
|
FUniqueNetIdRepl UCommonUserInfo::GetNetId(ECommonUserOnlineContext Context) const
|
|
{
|
|
const FCachedData* FoundCached = GetCachedData(Context);
|
|
|
|
if (FoundCached)
|
|
{
|
|
return FoundCached->CachedNetId;
|
|
}
|
|
|
|
return FUniqueNetIdRepl();
|
|
}
|
|
|
|
FString UCommonUserInfo::GetNickname(ECommonUserOnlineContext Context) const
|
|
{
|
|
const FCachedData* FoundCached = GetCachedData(Context);
|
|
|
|
if (FoundCached)
|
|
{
|
|
return FoundCached->CachedNickname;
|
|
}
|
|
|
|
// TODO maybe return unknown user here?
|
|
return FString();
|
|
}
|
|
|
|
void UCommonUserInfo::SetNickname(const FString& NewNickname, ECommonUserOnlineContext Context)
|
|
{
|
|
FCachedData* ContextCache = GetCachedData(Context);
|
|
|
|
if (ensure(ContextCache))
|
|
{
|
|
ContextCache->CachedNickname = NewNickname;
|
|
}
|
|
}
|
|
|
|
FString UCommonUserInfo::GetDebugString() const
|
|
{
|
|
FUniqueNetIdRepl NetId = GetNetId();
|
|
return NetId.ToDebugString();
|
|
}
|
|
|
|
FPlatformUserId UCommonUserInfo::GetPlatformUserId() const
|
|
{
|
|
return PlatformUser;
|
|
}
|
|
|
|
int32 UCommonUserInfo::GetPlatformUserIndex() const
|
|
{
|
|
// Convert our platform id to index
|
|
const UCommonUserSubsystem* Subsystem = GetSubsystem();
|
|
|
|
if (ensure(Subsystem))
|
|
{
|
|
return Subsystem->GetPlatformUserIndexForId(PlatformUser);
|
|
}
|
|
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// UCommonUserSubsystem
|
|
|
|
void UCommonUserSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
|
{
|
|
Super::Initialize(Collection);
|
|
|
|
// Create our OSS wrappers
|
|
CreateOnlineContexts();
|
|
|
|
BindOnlineDelegates();
|
|
|
|
IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get();
|
|
DeviceMapper.GetOnInputDeviceConnectionChange().AddUObject(this, &ThisClass::HandleInputDeviceConnectionChanged);
|
|
|
|
// Matches the engine default
|
|
SetMaxLocalPlayers(4);
|
|
|
|
ResetUserState();
|
|
|
|
UGameInstance* GameInstance = GetGameInstance();
|
|
bIsDedicatedServer = GameInstance->IsDedicatedServerInstance();
|
|
}
|
|
|
|
void UCommonUserSubsystem::CreateOnlineContexts()
|
|
{
|
|
// First initialize default
|
|
DefaultContextInternal = new FOnlineContextCache();
|
|
#if COMMONUSER_OSSV1
|
|
DefaultContextInternal->OnlineSubsystem = Online::GetSubsystem(GetWorld());
|
|
check(DefaultContextInternal->OnlineSubsystem);
|
|
DefaultContextInternal->IdentityInterface = DefaultContextInternal->OnlineSubsystem->GetIdentityInterface();
|
|
check(DefaultContextInternal->IdentityInterface.IsValid());
|
|
|
|
IOnlineSubsystem* PlatformSub = IOnlineSubsystem::GetByPlatform();
|
|
|
|
if (PlatformSub && DefaultContextInternal->OnlineSubsystem != PlatformSub)
|
|
{
|
|
// Set up the optional platform service if it exists
|
|
PlatformContextInternal = new FOnlineContextCache();
|
|
PlatformContextInternal->OnlineSubsystem = PlatformSub;
|
|
PlatformContextInternal->IdentityInterface = PlatformSub->GetIdentityInterface();
|
|
check(PlatformContextInternal->IdentityInterface.IsValid());
|
|
}
|
|
#else
|
|
DefaultContextInternal->OnlineServices = GetServices(GetWorld(), EOnlineServices::Default);
|
|
check(DefaultContextInternal->OnlineServices);
|
|
DefaultContextInternal->AuthService = DefaultContextInternal->OnlineServices->GetAuthInterface();
|
|
check(DefaultContextInternal->AuthService);
|
|
|
|
UE::Online::IOnlineServicesPtr PlatformServices = GetServices(GetWorld(), EOnlineServices::Platform);
|
|
if (PlatformServices && DefaultContextInternal->OnlineServices != PlatformServices)
|
|
{
|
|
PlatformContextInternal = new FOnlineContextCache();
|
|
PlatformContextInternal->OnlineServices = PlatformServices;
|
|
PlatformContextInternal->AuthService = PlatformContextInternal->OnlineServices->GetAuthInterface();
|
|
check(PlatformContextInternal->AuthService);
|
|
}
|
|
#endif
|
|
|
|
// Explicit external services can be set up after if needed
|
|
}
|
|
|
|
void UCommonUserSubsystem::Deinitialize()
|
|
{
|
|
DestroyOnlineContexts();
|
|
|
|
IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get();
|
|
DeviceMapper.GetOnInputDeviceConnectionChange().RemoveAll(this);
|
|
|
|
LocalUserInfos.Reset();
|
|
ActiveLoginRequests.Reset();
|
|
|
|
Super::Deinitialize();
|
|
}
|
|
|
|
void UCommonUserSubsystem::DestroyOnlineContexts()
|
|
{
|
|
// All cached shared ptrs must be cleared here
|
|
if (ServiceContextInternal && ServiceContextInternal != DefaultContextInternal)
|
|
{
|
|
delete ServiceContextInternal;
|
|
}
|
|
if (PlatformContextInternal && PlatformContextInternal != DefaultContextInternal)
|
|
{
|
|
delete PlatformContextInternal;
|
|
}
|
|
if (DefaultContextInternal)
|
|
{
|
|
delete DefaultContextInternal;
|
|
}
|
|
|
|
ServiceContextInternal = PlatformContextInternal = DefaultContextInternal = nullptr;
|
|
}
|
|
|
|
UCommonUserInfo* UCommonUserSubsystem::CreateLocalUserInfo(int32 LocalPlayerIndex)
|
|
{
|
|
UCommonUserInfo* NewUser = nullptr;
|
|
if (ensure(!LocalUserInfos.Contains(LocalPlayerIndex)))
|
|
{
|
|
NewUser = NewObject<UCommonUserInfo>(this);
|
|
NewUser->LocalPlayerIndex = LocalPlayerIndex;
|
|
NewUser->InitializationState = ECommonUserInitializationState::Unknown;
|
|
|
|
// Always create game and default cache
|
|
NewUser->CachedDataMap.Add(ECommonUserOnlineContext::Game, UCommonUserInfo::FCachedData());
|
|
NewUser->CachedDataMap.Add(ECommonUserOnlineContext::Default, UCommonUserInfo::FCachedData());
|
|
|
|
// Add platform if needed
|
|
if (HasSeparatePlatformContext())
|
|
{
|
|
NewUser->CachedDataMap.Add(ECommonUserOnlineContext::Platform, UCommonUserInfo::FCachedData());
|
|
}
|
|
|
|
LocalUserInfos.Add(LocalPlayerIndex, NewUser);
|
|
}
|
|
return NewUser;
|
|
}
|
|
|
|
bool UCommonUserSubsystem::ShouldCreateSubsystem(UObject* Outer) const
|
|
{
|
|
TArray<UClass*> ChildClasses;
|
|
GetDerivedClasses(GetClass(), ChildClasses, false);
|
|
|
|
// Only create an instance if there is not a game-specific subclass
|
|
return ChildClasses.Num() == 0;
|
|
}
|
|
|
|
void UCommonUserSubsystem::BindOnlineDelegates()
|
|
{
|
|
#if COMMONUSER_OSSV1
|
|
return BindOnlineDelegatesOSSv1();
|
|
#else
|
|
return BindOnlineDelegatesOSSv2();
|
|
#endif
|
|
}
|
|
|
|
void UCommonUserSubsystem::LogOutLocalUser(FPlatformUserId PlatformUser)
|
|
{
|
|
UCommonUserInfo* UserInfo = ModifyInfo(GetUserInfoForPlatformUser(PlatformUser));
|
|
|
|
// Don't need to do anything if the user has never logged in fully or is in the process of logging in
|
|
if (UserInfo && (UserInfo->InitializationState == ECommonUserInitializationState::LoggedInLocalOnly || UserInfo->InitializationState == ECommonUserInitializationState::LoggedInOnline))
|
|
{
|
|
ECommonUserAvailability OldAvailablity = UserInfo->GetPrivilegeAvailability(ECommonUserPrivilege::CanPlay);
|
|
|
|
UserInfo->InitializationState = ECommonUserInitializationState::FailedtoLogin;
|
|
|
|
// This will broadcast the game delegate
|
|
HandleChangedAvailability(UserInfo, ECommonUserPrivilege::CanPlay, OldAvailablity);
|
|
}
|
|
}
|
|
|
|
bool UCommonUserSubsystem::TransferPlatformAuth(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser)
|
|
{
|
|
#if COMMONUSER_OSSV1
|
|
// Not supported in V1 path
|
|
return false;
|
|
#else
|
|
return TransferPlatformAuthOSSv2(System, Request, PlatformUser);
|
|
#endif
|
|
}
|
|
|
|
bool UCommonUserSubsystem::AutoLogin(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser)
|
|
{
|
|
UE_LOG(LogCommonUser, Log, TEXT("Player AutoLogin requested - UserIdx:%d, Privilege:%d, Context:%d"),
|
|
PlatformUser.GetInternalId(),
|
|
(int32)Request->DesiredPrivilege,
|
|
(int32)Request->DesiredContext);
|
|
|
|
#if COMMONUSER_OSSV1
|
|
return AutoLoginOSSv1(System, Request, PlatformUser);
|
|
#else
|
|
return AutoLoginOSSv2(System, Request, PlatformUser);
|
|
#endif
|
|
}
|
|
|
|
bool UCommonUserSubsystem::ShowLoginUI(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser)
|
|
{
|
|
UE_LOG(LogCommonUser, Log, TEXT("Player LoginUI requested - UserIdx:%d, Privilege:%d, Context:%d"),
|
|
PlatformUser.GetInternalId(),
|
|
(int32)Request->DesiredPrivilege,
|
|
(int32)Request->DesiredContext);
|
|
|
|
#if COMMONUSER_OSSV1
|
|
return ShowLoginUIOSSv1(System, Request, PlatformUser);
|
|
#else
|
|
return ShowLoginUIOSSv2(System, Request, PlatformUser);
|
|
#endif
|
|
}
|
|
|
|
bool UCommonUserSubsystem::QueryUserPrivilege(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser)
|
|
{
|
|
#if COMMONUSER_OSSV1
|
|
return QueryUserPrivilegeOSSv1(System, Request, PlatformUser);
|
|
#else
|
|
return QueryUserPrivilegeOSSv2(System, Request, PlatformUser);
|
|
#endif
|
|
}
|
|
|
|
|
|
#if COMMONUSER_OSSV1
|
|
IOnlineSubsystem* UCommonUserSubsystem::GetOnlineSubsystem(ECommonUserOnlineContext Context) const
|
|
{
|
|
const FOnlineContextCache* System = GetContextCache(Context);
|
|
|
|
if (System)
|
|
{
|
|
return System->OnlineSubsystem;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
IOnlineIdentity* UCommonUserSubsystem::GetOnlineIdentity(ECommonUserOnlineContext Context) const
|
|
{
|
|
const FOnlineContextCache* System = GetContextCache(Context);
|
|
if (System)
|
|
{
|
|
return System->IdentityInterface.Get();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
FName UCommonUserSubsystem::GetOnlineSubsystemName(ECommonUserOnlineContext Context) const
|
|
{
|
|
IOnlineSubsystem* SubSystem = GetOnlineSubsystem(Context);
|
|
if (SubSystem)
|
|
{
|
|
return SubSystem->GetSubsystemName();
|
|
}
|
|
|
|
return NAME_None;
|
|
}
|
|
|
|
EOnlineServerConnectionStatus::Type UCommonUserSubsystem::GetConnectionStatus(ECommonUserOnlineContext Context) const
|
|
{
|
|
const FOnlineContextCache* System = GetContextCache(Context);
|
|
if (System)
|
|
{
|
|
return System->CurrentConnectionStatus;
|
|
}
|
|
|
|
return EOnlineServerConnectionStatus::ServiceUnavailable;
|
|
}
|
|
|
|
void UCommonUserSubsystem::BindOnlineDelegatesOSSv1()
|
|
{
|
|
ECommonUserOnlineContext ServiceType = ResolveOnlineContext(ECommonUserOnlineContext::ServiceOrDefault);
|
|
ECommonUserOnlineContext PlatformType = ResolveOnlineContext(ECommonUserOnlineContext::PlatformOrDefault);
|
|
FOnlineContextCache* ServiceContext = GetContextCache(ServiceType);
|
|
FOnlineContextCache* PlatformContext = GetContextCache(PlatformType);
|
|
check(ServiceContext && ServiceContext->OnlineSubsystem && PlatformContext && PlatformContext->OnlineSubsystem);
|
|
// Connection delegates need to listen for both systems
|
|
|
|
ServiceContext->OnlineSubsystem->AddOnConnectionStatusChangedDelegate_Handle(FOnConnectionStatusChangedDelegate::CreateUObject(this, &ThisClass::HandleNetworkConnectionStatusChanged, ServiceType));
|
|
ServiceContext->CurrentConnectionStatus = EOnlineServerConnectionStatus::Normal;
|
|
|
|
for (int32 PlayerIdx = 0; PlayerIdx < MAX_LOCAL_PLAYERS; PlayerIdx++)
|
|
{
|
|
ServiceContext->IdentityInterface->AddOnLoginStatusChangedDelegate_Handle(PlayerIdx, FOnLoginStatusChangedDelegate::CreateUObject(this, &ThisClass::HandleIdentityLoginStatusChanged, ServiceType));
|
|
ServiceContext->IdentityInterface->AddOnLoginCompleteDelegate_Handle(PlayerIdx, FOnLoginCompleteDelegate::CreateUObject(this, &ThisClass::HandleUserLoginCompleted, ServiceType));
|
|
}
|
|
|
|
if (ServiceType != PlatformType)
|
|
{
|
|
PlatformContext->OnlineSubsystem->AddOnConnectionStatusChangedDelegate_Handle(FOnConnectionStatusChangedDelegate::CreateUObject(this, &ThisClass::HandleNetworkConnectionStatusChanged, PlatformType));
|
|
PlatformContext->CurrentConnectionStatus = EOnlineServerConnectionStatus::Normal;
|
|
|
|
for (int32 PlayerIdx = 0; PlayerIdx < MAX_LOCAL_PLAYERS; PlayerIdx++)
|
|
{
|
|
PlatformContext->IdentityInterface->AddOnLoginStatusChangedDelegate_Handle(PlayerIdx, FOnLoginStatusChangedDelegate::CreateUObject(this, &ThisClass::HandleIdentityLoginStatusChanged, PlatformType));
|
|
PlatformContext->IdentityInterface->AddOnLoginCompleteDelegate_Handle(PlayerIdx, FOnLoginCompleteDelegate::CreateUObject(this, &ThisClass::HandleUserLoginCompleted, PlatformType));
|
|
}
|
|
}
|
|
|
|
// Hardware change delegates only listen to platform
|
|
PlatformContext->IdentityInterface->AddOnControllerPairingChangedDelegate_Handle(FOnControllerPairingChangedDelegate::CreateUObject(this, &ThisClass::HandleControllerPairingChanged));
|
|
}
|
|
|
|
bool UCommonUserSubsystem::AutoLoginOSSv1(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser)
|
|
{
|
|
return System->IdentityInterface->AutoLogin(GetPlatformUserIndexForId(PlatformUser));
|
|
}
|
|
|
|
bool UCommonUserSubsystem::ShowLoginUIOSSv1(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser)
|
|
{
|
|
IOnlineExternalUIPtr ExternalUI = System->OnlineSubsystem->GetExternalUIInterface();
|
|
if (ExternalUI.IsValid())
|
|
{
|
|
// TODO Unclear which flags should be set
|
|
return ExternalUI->ShowLoginUI(GetPlatformUserIndexForId(PlatformUser), false, false, FOnLoginUIClosedDelegate::CreateUObject(this, &ThisClass::HandleOnLoginUIClosed, Request->CurrentContext));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool UCommonUserSubsystem::QueryUserPrivilegeOSSv1(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser)
|
|
{
|
|
// Start query on unknown or failure
|
|
EUserPrivileges::Type OSSPrivilege = ConvertOSSPrivilege(Request->DesiredPrivilege);
|
|
|
|
FUniqueNetIdRepl CurrentId = GetLocalUserNetId(PlatformUser, Request->CurrentContext);
|
|
check(CurrentId.IsValid());
|
|
IOnlineIdentity::FOnGetUserPrivilegeCompleteDelegate Delegate = IOnlineIdentity::FOnGetUserPrivilegeCompleteDelegate::CreateUObject(this, &UCommonUserSubsystem::HandleCheckPrivilegesComplete, Request->DesiredPrivilege, Request->UserInfo, Request->CurrentContext);
|
|
System->IdentityInterface->GetUserPrivilege(*CurrentId, OSSPrivilege, Delegate);
|
|
|
|
// This may immediately succeed and reenter this function, so we have to return
|
|
return true;
|
|
}
|
|
|
|
#else
|
|
|
|
UE::Online::EOnlineServices UCommonUserSubsystem::GetOnlineServicesProvider(ECommonUserOnlineContext Context) const
|
|
{
|
|
if (const FOnlineContextCache* System = GetContextCache(Context))
|
|
{
|
|
return System->OnlineServices->GetServicesProvider();
|
|
}
|
|
return UE::Online::EOnlineServices::None;
|
|
}
|
|
|
|
UE::Online::IAuthPtr UCommonUserSubsystem::GetOnlineAuth(ECommonUserOnlineContext Context) const
|
|
{
|
|
if (const FOnlineContextCache* System = GetContextCache(Context))
|
|
{
|
|
return System->AuthService;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
UE::Online::EOnlineServicesConnectionStatus UCommonUserSubsystem::GetConnectionStatus(ECommonUserOnlineContext Context) const
|
|
{
|
|
if (const FOnlineContextCache* System = GetContextCache(Context))
|
|
{
|
|
return System->CurrentConnectionStatus;
|
|
}
|
|
return UE::Online::EOnlineServicesConnectionStatus::NotConnected;
|
|
}
|
|
|
|
void UCommonUserSubsystem::BindOnlineDelegatesOSSv2()
|
|
{
|
|
ECommonUserOnlineContext ServiceType = ResolveOnlineContext(ECommonUserOnlineContext::ServiceOrDefault);
|
|
ECommonUserOnlineContext PlatformType = ResolveOnlineContext(ECommonUserOnlineContext::PlatformOrDefault);
|
|
FOnlineContextCache* ServiceContext = GetContextCache(ServiceType);
|
|
FOnlineContextCache* PlatformContext = GetContextCache(PlatformType);
|
|
check(ServiceContext && ServiceContext->OnlineServices && PlatformContext && PlatformContext->OnlineServices);
|
|
|
|
ServiceContext->LoginStatusChangedHandle = ServiceContext->AuthService->OnLoginStatusChanged().Add(this, &ThisClass::HandleAuthLoginStatusChanged, ServiceType);
|
|
if (IConnectivityPtr ConnectivityInterface = ServiceContext->OnlineServices->GetConnectivityInterface())
|
|
{
|
|
ServiceContext->ConnectionStatusChangedHandle = ConnectivityInterface->OnConnectionStatusChanged().Add(this, &ThisClass::HandleNetworkConnectionStatusChanged, ServiceType);
|
|
}
|
|
CacheConnectionStatus(ServiceType);
|
|
|
|
if (ServiceType != PlatformType)
|
|
{
|
|
PlatformContext->LoginStatusChangedHandle = PlatformContext->AuthService->OnLoginStatusChanged().Add(this, &ThisClass::HandleAuthLoginStatusChanged, PlatformType);
|
|
if (IConnectivityPtr ConnectivityInterface = PlatformContext->OnlineServices->GetConnectivityInterface())
|
|
{
|
|
PlatformContext->ConnectionStatusChangedHandle = ConnectivityInterface->OnConnectionStatusChanged().Add(this, &ThisClass::HandleNetworkConnectionStatusChanged, PlatformType);
|
|
}
|
|
CacheConnectionStatus(PlatformType);
|
|
}
|
|
// TODO: Controller Pairing Changed - move out of OSS and listen to CoreDelegate directly?
|
|
}
|
|
|
|
void UCommonUserSubsystem::CacheConnectionStatus(ECommonUserOnlineContext Context)
|
|
{
|
|
FOnlineContextCache* ContextCache = GetContextCache(Context);
|
|
check(ContextCache);
|
|
|
|
EOnlineServicesConnectionStatus ConnectionStatus = EOnlineServicesConnectionStatus::NotConnected;
|
|
if (IConnectivityPtr ConnectivityInterface = ContextCache->OnlineServices->GetConnectivityInterface())
|
|
{
|
|
const TOnlineResult<FGetConnectionStatus> Result = ConnectivityInterface->GetConnectionStatus(FGetConnectionStatus::Params());
|
|
if (Result.IsOk())
|
|
{
|
|
ConnectionStatus = Result.GetOkValue().Status;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ConnectionStatus = EOnlineServicesConnectionStatus::Connected;
|
|
}
|
|
|
|
UE::Online::FConnectionStatusChanged EventParams;
|
|
EventParams.PreviousStatus = ContextCache->CurrentConnectionStatus;
|
|
EventParams.CurrentStatus = ConnectionStatus;
|
|
HandleNetworkConnectionStatusChanged(EventParams, Context);
|
|
}
|
|
|
|
bool UCommonUserSubsystem::TransferPlatformAuthOSSv2(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser)
|
|
{
|
|
IAuthPtr PlatformAuthInterface = GetOnlineAuth(ECommonUserOnlineContext::Platform);
|
|
if (Request->CurrentContext != ECommonUserOnlineContext::Platform
|
|
&& PlatformAuthInterface)
|
|
{
|
|
FAuthQueryExternalAuthToken::Params Params;
|
|
Params.LocalAccountId = GetLocalUserNetId(PlatformUser, ECommonUserOnlineContext::Platform).GetV2();
|
|
|
|
PlatformAuthInterface->QueryExternalAuthToken(MoveTemp(Params))
|
|
.OnComplete(this, [this, Request](const TOnlineResult<FAuthQueryExternalAuthToken>& Result)
|
|
{
|
|
UCommonUserInfo* UserInfo = Request->UserInfo.Get();
|
|
if (!UserInfo)
|
|
{
|
|
// User is gone, just delete this request
|
|
ActiveLoginRequests.Remove(Request);
|
|
return;
|
|
}
|
|
|
|
if (Result.IsOk())
|
|
{
|
|
const FAuthQueryExternalAuthToken::Result& GenerateAuthTokenResult = Result.GetOkValue();
|
|
FAuthLogin::Params Params;
|
|
Params.PlatformUserId = UserInfo->GetPlatformUserId();
|
|
Params.CredentialsType = LoginCredentialsType::ExternalAuth;
|
|
Params.CredentialsToken.Emplace<FExternalAuthToken>(GenerateAuthTokenResult.ExternalAuthToken);
|
|
|
|
IAuthPtr PrimaryAuthInterface = GetOnlineAuth(Request->CurrentContext);
|
|
PrimaryAuthInterface->Login(MoveTemp(Params))
|
|
.OnComplete(this, [this, Request](const TOnlineResult<FAuthLogin>& Result)
|
|
{
|
|
UCommonUserInfo* UserInfo = Request->UserInfo.Get();
|
|
if (!UserInfo)
|
|
{
|
|
// User is gone, just delete this request
|
|
ActiveLoginRequests.Remove(Request);
|
|
return;
|
|
}
|
|
|
|
if (Result.IsOk())
|
|
{
|
|
Request->TransferPlatformAuthState = ECommonUserAsyncTaskState::Done;
|
|
Request->Error.Reset();
|
|
}
|
|
else
|
|
{
|
|
Request->TransferPlatformAuthState = ECommonUserAsyncTaskState::Failed;
|
|
Request->Error = Result.GetErrorValue();
|
|
}
|
|
ProcessLoginRequest(Request);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
Request->TransferPlatformAuthState = ECommonUserAsyncTaskState::Failed;
|
|
Request->Error = Result.GetErrorValue();
|
|
ProcessLoginRequest(Request);
|
|
}
|
|
});
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool UCommonUserSubsystem::AutoLoginOSSv2(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser)
|
|
{
|
|
FAuthLogin::Params LoginParameters;
|
|
LoginParameters.PlatformUserId = PlatformUser;
|
|
LoginParameters.CredentialsType = LoginCredentialsType::Auto;
|
|
// Leave other LoginParameters as default to allow the online service to determine how to try to automatically log in the user
|
|
TOnlineAsyncOpHandle<FAuthLogin> LoginHandle = System->AuthService->Login(MoveTemp(LoginParameters));
|
|
LoginHandle.OnComplete(this, &ThisClass::HandleUserLoginCompletedV2, PlatformUser, Request->CurrentContext);
|
|
return true;
|
|
}
|
|
|
|
bool UCommonUserSubsystem::ShowLoginUIOSSv2(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser)
|
|
{
|
|
IExternalUIPtr ExternalUI = System->OnlineServices->GetExternalUIInterface();
|
|
if (ExternalUI.IsValid())
|
|
{
|
|
FExternalUIShowLoginUI::Params ShowLoginUIParameters;
|
|
ShowLoginUIParameters.PlatformUserId = PlatformUser;
|
|
TOnlineAsyncOpHandle<FExternalUIShowLoginUI> LoginHandle = ExternalUI->ShowLoginUI(MoveTemp(ShowLoginUIParameters));
|
|
LoginHandle.OnComplete(this, &ThisClass::HandleOnLoginUIClosedV2, PlatformUser, Request->CurrentContext);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool UCommonUserSubsystem::QueryUserPrivilegeOSSv2(FOnlineContextCache* System, TSharedRef<FUserLoginRequest> Request, FPlatformUserId PlatformUser)
|
|
{
|
|
UCommonUserInfo* UserInfo = Request->UserInfo.Get();
|
|
|
|
if (IPrivilegesPtr PrivilegesInterface = System->OnlineServices->GetPrivilegesInterface())
|
|
{
|
|
const EUserPrivileges DesiredPrivilege = ConvertOnlineServicesPrivilege(Request->DesiredPrivilege);
|
|
|
|
FQueryUserPrivilege::Params Params;
|
|
Params.LocalAccountId = GetLocalUserNetId(PlatformUser, Request->CurrentContext).GetV2();
|
|
Params.Privilege = DesiredPrivilege;
|
|
TOnlineAsyncOpHandle<FQueryUserPrivilege> QueryHandle = PrivilegesInterface->QueryUserPrivilege(MoveTemp(Params));
|
|
QueryHandle.OnComplete(this, &ThisClass::HandleCheckPrivilegesComplete, Request->UserInfo, DesiredPrivilege, Request->CurrentContext);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
UpdateUserPrivilegeResult(UserInfo, Request->DesiredPrivilege, ECommonUserPrivilegeResult::Available, Request->CurrentContext);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
TSharedPtr<FAccountInfo> UCommonUserSubsystem::GetOnlineServiceAccountInfo(IAuthPtr AuthService, FPlatformUserId InUserId) const
|
|
{
|
|
TSharedPtr<FAccountInfo> AccountInfo;
|
|
FAuthGetLocalOnlineUserByPlatformUserId::Params GetAccountParams = { InUserId };
|
|
TOnlineResult<FAuthGetLocalOnlineUserByPlatformUserId> GetAccountResult = AuthService->GetLocalOnlineUserByPlatformUserId(MoveTemp(GetAccountParams));
|
|
if (GetAccountResult.IsOk())
|
|
{
|
|
AccountInfo = GetAccountResult.GetOkValue().AccountInfo;
|
|
}
|
|
return AccountInfo;
|
|
}
|
|
|
|
#endif
|
|
|
|
bool UCommonUserSubsystem::HasOnlineConnection(ECommonUserOnlineContext Context) const
|
|
{
|
|
#if COMMONUSER_OSSV1
|
|
EOnlineServerConnectionStatus::Type ConnectionType = GetConnectionStatus(Context);
|
|
|
|
if (ConnectionType == EOnlineServerConnectionStatus::Normal || ConnectionType == EOnlineServerConnectionStatus::Connected)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
#else
|
|
return GetConnectionStatus(Context) == UE::Online::EOnlineServicesConnectionStatus::Connected;
|
|
#endif
|
|
}
|
|
|
|
ELoginStatusType UCommonUserSubsystem::GetLocalUserLoginStatus(FPlatformUserId PlatformUser, ECommonUserOnlineContext Context) const
|
|
{
|
|
if (!IsRealPlatformUser(PlatformUser))
|
|
{
|
|
return ELoginStatusType::NotLoggedIn;
|
|
}
|
|
|
|
const FOnlineContextCache* System = GetContextCache(Context);
|
|
if (System)
|
|
{
|
|
#if COMMONUSER_OSSV1
|
|
return System->IdentityInterface->GetLoginStatus(GetPlatformUserIndexForId(PlatformUser));
|
|
#else
|
|
if (TSharedPtr<FAccountInfo> AccountInfo = GetOnlineServiceAccountInfo(System->AuthService, PlatformUser))
|
|
{
|
|
return AccountInfo->LoginStatus;
|
|
}
|
|
#endif
|
|
}
|
|
return ELoginStatusType::NotLoggedIn;
|
|
}
|
|
|
|
FUniqueNetIdRepl UCommonUserSubsystem::GetLocalUserNetId(FPlatformUserId PlatformUser, ECommonUserOnlineContext Context) const
|
|
{
|
|
if (!IsRealPlatformUser(PlatformUser))
|
|
{
|
|
return FUniqueNetIdRepl();
|
|
}
|
|
|
|
const FOnlineContextCache* System = GetContextCache(Context);
|
|
if (System)
|
|
{
|
|
#if COMMONUSER_OSSV1
|
|
return FUniqueNetIdRepl(System->IdentityInterface->GetUniquePlayerId(GetPlatformUserIndexForId(PlatformUser)));
|
|
#else
|
|
// TODO: OSSv2 FUniqueNetIdRepl wrapping FAccountId is in progress
|
|
if (TSharedPtr<FAccountInfo> AccountInfo = GetOnlineServiceAccountInfo(System->AuthService, PlatformUser))
|
|
{
|
|
return FUniqueNetIdRepl(AccountInfo->AccountId);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return FUniqueNetIdRepl();
|
|
}
|
|
|
|
FString UCommonUserSubsystem::GetLocalUserNickname(FPlatformUserId PlatformUser, ECommonUserOnlineContext Context) const
|
|
{
|
|
#if COMMONUSER_OSSV1
|
|
IOnlineIdentity* Identity = GetOnlineIdentity(Context);
|
|
if (ensure(Identity))
|
|
{
|
|
return Identity->GetPlayerNickname(GetPlatformUserIndexForId(PlatformUser));
|
|
}
|
|
#else
|
|
if (IAuthPtr AuthService = GetOnlineAuth(Context))
|
|
{
|
|
if (TSharedPtr<FAccountInfo> AccountInfo = GetOnlineServiceAccountInfo(AuthService, PlatformUser))
|
|
{
|
|
if (const FSchemaVariant* DisplayName = AccountInfo->Attributes.Find(AccountAttributeData::DisplayName))
|
|
{
|
|
return DisplayName->GetString();
|
|
}
|
|
}
|
|
}
|
|
#endif // COMMONUSER_OSSV1
|
|
|
|
return FString();
|
|
}
|
|
|
|
void UCommonUserSubsystem::SendSystemMessage(FGameplayTag MessageType, FText TitleText, FText BodyText)
|
|
{
|
|
OnHandleSystemMessage.Broadcast(MessageType, TitleText, BodyText);
|
|
}
|
|
|
|
void UCommonUserSubsystem::SetMaxLocalPlayers(int32 InMaxLocalPlayers)
|
|
{
|
|
if (ensure(InMaxLocalPlayers >= 1))
|
|
{
|
|
// We can have more local players than MAX_LOCAL_PLAYERS, the rest are treated as guests
|
|
MaxNumberOfLocalPlayers = InMaxLocalPlayers;
|
|
|
|
UGameInstance* GameInstance = GetGameInstance();
|
|
UGameViewportClient* ViewportClient = GameInstance ? GameInstance->GetGameViewportClient() : nullptr;
|
|
|
|
if (ViewportClient)
|
|
{
|
|
ViewportClient->MaxSplitscreenPlayers = MaxNumberOfLocalPlayers;
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 UCommonUserSubsystem::GetMaxLocalPlayers() const
|
|
{
|
|
return MaxNumberOfLocalPlayers;
|
|
}
|
|
|
|
int32 UCommonUserSubsystem::GetNumLocalPlayers() const
|
|
{
|
|
UGameInstance* GameInstance = GetGameInstance();
|
|
if (ensure(GameInstance))
|
|
{
|
|
return GameInstance->GetNumLocalPlayers();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
ECommonUserInitializationState UCommonUserSubsystem::GetLocalPlayerInitializationState(int32 LocalPlayerIndex) const
|
|
{
|
|
const UCommonUserInfo* UserInfo = GetUserInfoForLocalPlayerIndex(LocalPlayerIndex);
|
|
if (UserInfo)
|
|
{
|
|
return UserInfo->InitializationState;
|
|
}
|
|
|
|
if (LocalPlayerIndex < 0 || LocalPlayerIndex >= GetMaxLocalPlayers())
|
|
{
|
|
return ECommonUserInitializationState::Invalid;
|
|
}
|
|
|
|
return ECommonUserInitializationState::Unknown;
|
|
}
|
|
|
|
bool UCommonUserSubsystem::TryToInitializeForLocalPlay(int32 LocalPlayerIndex, FInputDeviceId PrimaryInputDevice, bool bCanUseGuestLogin)
|
|
{
|
|
if (!PrimaryInputDevice.IsValid())
|
|
{
|
|
// Set to default device
|
|
PrimaryInputDevice = IPlatformInputDeviceMapper::Get().GetDefaultInputDevice();
|
|
}
|
|
|
|
FCommonUserInitializeParams Params;
|
|
Params.LocalPlayerIndex = LocalPlayerIndex;
|
|
Params.PrimaryInputDevice = PrimaryInputDevice;
|
|
Params.bCanUseGuestLogin = bCanUseGuestLogin;
|
|
Params.bCanCreateNewLocalPlayer = true;
|
|
Params.RequestedPrivilege = ECommonUserPrivilege::CanPlay;
|
|
|
|
return TryToInitializeUser(Params);
|
|
}
|
|
|
|
bool UCommonUserSubsystem::TryToLoginForOnlinePlay(int32 LocalPlayerIndex)
|
|
{
|
|
FCommonUserInitializeParams Params;
|
|
Params.LocalPlayerIndex = LocalPlayerIndex;
|
|
Params.bCanCreateNewLocalPlayer = false;
|
|
Params.RequestedPrivilege = ECommonUserPrivilege::CanPlayOnline;
|
|
|
|
return TryToInitializeUser(Params);
|
|
}
|
|
|
|
bool UCommonUserSubsystem::TryToInitializeUser(FCommonUserInitializeParams Params)
|
|
{
|
|
if (Params.LocalPlayerIndex < 0 || (!Params.bCanCreateNewLocalPlayer && Params.LocalPlayerIndex >= GetNumLocalPlayers()))
|
|
{
|
|
if (!bIsDedicatedServer)
|
|
{
|
|
UE_LOG(LogCommonUser, Error, TEXT("TryToInitializeUser %d failed with current %d and max %d, invalid index"),
|
|
Params.LocalPlayerIndex, GetNumLocalPlayers(), GetMaxLocalPlayers());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (Params.LocalPlayerIndex > GetNumLocalPlayers() || Params.LocalPlayerIndex >= GetMaxLocalPlayers())
|
|
{
|
|
UE_LOG(LogCommonUser, Error, TEXT("TryToInitializeUser %d failed with current %d and max %d, can only create in order up to max players"),
|
|
Params.LocalPlayerIndex, GetNumLocalPlayers(), GetMaxLocalPlayers());
|
|
return false;
|
|
}
|
|
|
|
// Fill in platform user and input device if needed
|
|
if (Params.ControllerId != INDEX_NONE && (!Params.PrimaryInputDevice.IsValid() || !Params.PlatformUser.IsValid()))
|
|
{
|
|
IPlatformInputDeviceMapper::Get().RemapControllerIdToPlatformUserAndDevice(Params.ControllerId, Params.PlatformUser, Params.PrimaryInputDevice);
|
|
}
|
|
|
|
if (Params.PrimaryInputDevice.IsValid() && !Params.PlatformUser.IsValid())
|
|
{
|
|
Params.PlatformUser = GetPlatformUserIdForInputDevice(Params.PrimaryInputDevice);
|
|
}
|
|
else if (Params.PlatformUser.IsValid() && !Params.PrimaryInputDevice.IsValid())
|
|
{
|
|
Params.PrimaryInputDevice = GetPrimaryInputDeviceForPlatformUser(Params.PlatformUser);
|
|
}
|
|
|
|
UCommonUserInfo* LocalUserInfo = ModifyInfo(GetUserInfoForLocalPlayerIndex(Params.LocalPlayerIndex));
|
|
UCommonUserInfo* LocalUserInfoForController = ModifyInfo(GetUserInfoForInputDevice(Params.PrimaryInputDevice));
|
|
|
|
if (LocalUserInfoForController && LocalUserInfo && LocalUserInfoForController != LocalUserInfo)
|
|
{
|
|
UE_LOG(LogCommonUser, Error, TEXT("TryToInitializeUser %d failed because controller %d is already assigned to player %d"),
|
|
Params.LocalPlayerIndex, Params.PrimaryInputDevice.GetId(), LocalUserInfoForController->LocalPlayerIndex);
|
|
return false;
|
|
}
|
|
|
|
if (Params.LocalPlayerIndex == 0 && Params.bCanUseGuestLogin)
|
|
{
|
|
UE_LOG(LogCommonUser, Error, TEXT("TryToInitializeUser failed because player 0 cannot be a guest"));
|
|
return false;
|
|
}
|
|
|
|
if (!LocalUserInfo)
|
|
{
|
|
LocalUserInfo = CreateLocalUserInfo(Params.LocalPlayerIndex);
|
|
}
|
|
else
|
|
{
|
|
// Copy from existing user info
|
|
if (!Params.PrimaryInputDevice.IsValid())
|
|
{
|
|
Params.PrimaryInputDevice = LocalUserInfo->PrimaryInputDevice;
|
|
}
|
|
|
|
if (!Params.PlatformUser.IsValid())
|
|
{
|
|
Params.PlatformUser = LocalUserInfo->PlatformUser;
|
|
}
|
|
}
|
|
|
|
if (LocalUserInfo->InitializationState != ECommonUserInitializationState::Unknown && LocalUserInfo->InitializationState != ECommonUserInitializationState::FailedtoLogin)
|
|
{
|
|
// Not allowed to change parameters during login
|
|
if (LocalUserInfo->PrimaryInputDevice != Params.PrimaryInputDevice || LocalUserInfo->PlatformUser != Params.PlatformUser || LocalUserInfo->bCanBeGuest != Params.bCanUseGuestLogin)
|
|
{
|
|
UE_LOG(LogCommonUser, Error, TEXT("TryToInitializeUser failed because player %d has already started the login process with diffrent settings!"), Params.LocalPlayerIndex);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Set desired index now so if it creates a player it knows what controller to use
|
|
LocalUserInfo->PrimaryInputDevice = Params.PrimaryInputDevice;
|
|
LocalUserInfo->PlatformUser = Params.PlatformUser;
|
|
LocalUserInfo->bCanBeGuest = Params.bCanUseGuestLogin;
|
|
RefreshLocalUserInfo(LocalUserInfo);
|
|
|
|
// Either doing an initial or network login
|
|
if (LocalUserInfo->GetPrivilegeAvailability(ECommonUserPrivilege::CanPlay) == ECommonUserAvailability::NowAvailable && Params.RequestedPrivilege == ECommonUserPrivilege::CanPlayOnline)
|
|
{
|
|
LocalUserInfo->InitializationState = ECommonUserInitializationState::DoingNetworkLogin;
|
|
}
|
|
else
|
|
{
|
|
LocalUserInfo->InitializationState = ECommonUserInitializationState::DoingInitialLogin;
|
|
}
|
|
|
|
LoginLocalUser(LocalUserInfo, Params.RequestedPrivilege, Params.OnlineContext, FOnLocalUserLoginCompleteDelegate::CreateUObject(this, &ThisClass::HandleLoginForUserInitialize, Params));
|
|
|
|
return true;
|
|
}
|
|
|
|
void UCommonUserSubsystem::ListenForLoginKeyInput(TArray<FKey> AnyUserKeys, TArray<FKey> NewUserKeys, FCommonUserInitializeParams Params)
|
|
{
|
|
UGameViewportClient* ViewportClient = GetGameInstance()->GetGameViewportClient();
|
|
if (ensure(ViewportClient))
|
|
{
|
|
const bool bIsMapped = LoginKeysForAnyUser.Num() > 0 || LoginKeysForNewUser.Num() > 0;
|
|
const bool bShouldBeMapped = AnyUserKeys.Num() > 0 || NewUserKeys.Num() > 0;
|
|
|
|
if (bIsMapped && !bShouldBeMapped)
|
|
{
|
|
// Set it back to wrapped handler
|
|
ViewportClient->OnOverrideInputKey() = WrappedInputKeyHandler;
|
|
WrappedInputKeyHandler.Unbind();
|
|
}
|
|
else if (!bIsMapped && bShouldBeMapped)
|
|
{
|
|
// Set up a wrapped handler
|
|
WrappedInputKeyHandler = ViewportClient->OnOverrideInputKey();
|
|
ViewportClient->OnOverrideInputKey().BindUObject(this, &UCommonUserSubsystem::OverrideInputKeyForLogin);
|
|
}
|
|
|
|
LoginKeysForAnyUser = AnyUserKeys;
|
|
LoginKeysForNewUser = NewUserKeys;
|
|
|
|
if (bShouldBeMapped)
|
|
{
|
|
ParamsForLoginKey = Params;
|
|
}
|
|
else
|
|
{
|
|
ParamsForLoginKey = FCommonUserInitializeParams();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UCommonUserSubsystem::CancelUserInitialization(int32 LocalPlayerIndex)
|
|
{
|
|
UCommonUserInfo* LocalUserInfo = ModifyInfo(GetUserInfoForLocalPlayerIndex(LocalPlayerIndex));
|
|
if (!LocalUserInfo)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!LocalUserInfo->IsDoingLogin())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Remove from login queue
|
|
TArray<TSharedRef<FUserLoginRequest>> RequestsCopy = ActiveLoginRequests;
|
|
for (TSharedRef<FUserLoginRequest>& Request : RequestsCopy)
|
|
{
|
|
if (Request->UserInfo.IsValid() && Request->UserInfo->LocalPlayerIndex == LocalPlayerIndex)
|
|
{
|
|
ActiveLoginRequests.Remove(Request);
|
|
}
|
|
}
|
|
|
|
// Set state with best guess
|
|
if (LocalUserInfo->InitializationState == ECommonUserInitializationState::DoingNetworkLogin)
|
|
{
|
|
LocalUserInfo->InitializationState = ECommonUserInitializationState::LoggedInLocalOnly;
|
|
}
|
|
else
|
|
{
|
|
LocalUserInfo->InitializationState = ECommonUserInitializationState::FailedtoLogin;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UCommonUserSubsystem::TryToLogOutUser(int32 LocalPlayerIndex, bool bDestroyPlayer)
|
|
{
|
|
UGameInstance* GameInstance = GetGameInstance();
|
|
|
|
if (!ensure(GameInstance))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (LocalPlayerIndex == 0 && bDestroyPlayer)
|
|
{
|
|
UE_LOG(LogCommonUser, Error, TEXT("TryToLogOutUser cannot destroy player 0"));
|
|
return false;
|
|
}
|
|
|
|
CancelUserInitialization(LocalPlayerIndex);
|
|
|
|
UCommonUserInfo* LocalUserInfo = ModifyInfo(GetUserInfoForLocalPlayerIndex(LocalPlayerIndex));
|
|
if (!LocalUserInfo)
|
|
{
|
|
UE_LOG(LogCommonUser, Warning, TEXT("TryToLogOutUser failed to log out user %i because they are not logged in"), LocalPlayerIndex);
|
|
return false;
|
|
}
|
|
|
|
FPlatformUserId UserId = LocalUserInfo->PlatformUser;
|
|
if (IsRealPlatformUser(UserId))
|
|
{
|
|
// Currently this does not do platform logout in case they want to log back in immediately after
|
|
UE_LOG(LogCommonUser, Log, TEXT("TryToLogOutUser succeeded for real platform user %d"), UserId.GetInternalId());
|
|
|
|
LogOutLocalUser(UserId);
|
|
}
|
|
else if (ensure(LocalUserInfo->bIsGuest))
|
|
{
|
|
// For guest users just delete it
|
|
UE_LOG(LogCommonUser, Log, TEXT("TryToLogOutUser succeeded for guest player index %d"), LocalPlayerIndex);
|
|
|
|
LocalUserInfos.Remove(LocalPlayerIndex);
|
|
}
|
|
|
|
if (bDestroyPlayer)
|
|
{
|
|
ULocalPlayer* ExistingPlayer = GameInstance->FindLocalPlayerFromPlatformUserId(UserId);
|
|
|
|
if (ExistingPlayer)
|
|
{
|
|
GameInstance->RemoveLocalPlayer(ExistingPlayer);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void UCommonUserSubsystem::ResetUserState()
|
|
{
|
|
// Manually purge existing info objects
|
|
for (TPair<int32, UCommonUserInfo*> Pair : LocalUserInfos)
|
|
{
|
|
if (Pair.Value)
|
|
{
|
|
Pair.Value->MarkAsGarbage();
|
|
}
|
|
}
|
|
|
|
LocalUserInfos.Reset();
|
|
|
|
// Cancel in-progress logins
|
|
ActiveLoginRequests.Reset();
|
|
|
|
// Create player info for id 0
|
|
UCommonUserInfo* FirstUser = CreateLocalUserInfo(0);
|
|
|
|
FirstUser->PlatformUser = IPlatformInputDeviceMapper::Get().GetPrimaryPlatformUser();
|
|
FirstUser->PrimaryInputDevice = IPlatformInputDeviceMapper::Get().GetPrimaryInputDeviceForUser(FirstUser->PlatformUser);
|
|
|
|
// TODO: Schedule a refresh of player 0 for next frame?
|
|
RefreshLocalUserInfo(FirstUser);
|
|
}
|
|
|
|
bool UCommonUserSubsystem::OverrideInputKeyForLogin(FInputKeyEventArgs& EventArgs)
|
|
{
|
|
int32 NextLocalPlayerIndex = INDEX_NONE;
|
|
|
|
const UCommonUserInfo* MappedUser = GetUserInfoForInputDevice(EventArgs.InputDevice);
|
|
if (EventArgs.Event == IE_Pressed)
|
|
{
|
|
if (MappedUser == nullptr || !MappedUser->IsLoggedIn())
|
|
{
|
|
if (MappedUser)
|
|
{
|
|
NextLocalPlayerIndex = MappedUser->LocalPlayerIndex;
|
|
}
|
|
else
|
|
{
|
|
// Find next player
|
|
for (int32 i = 0; i < MaxNumberOfLocalPlayers; i++)
|
|
{
|
|
if (GetLocalPlayerInitializationState(i) == ECommonUserInitializationState::Unknown)
|
|
{
|
|
NextLocalPlayerIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NextLocalPlayerIndex != INDEX_NONE)
|
|
{
|
|
if (LoginKeysForAnyUser.Contains(EventArgs.Key))
|
|
{
|
|
// If we're in the middle of logging in just return true to ignore platform-specific input
|
|
if (MappedUser && MappedUser->IsDoingLogin())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Press start screen
|
|
FCommonUserInitializeParams NewParams = ParamsForLoginKey;
|
|
NewParams.LocalPlayerIndex = NextLocalPlayerIndex;
|
|
NewParams.PrimaryInputDevice = EventArgs.InputDevice;
|
|
|
|
return TryToInitializeUser(NewParams);
|
|
}
|
|
|
|
// See if this controller id is mapped
|
|
MappedUser = GetUserInfoForInputDevice(EventArgs.InputDevice);
|
|
|
|
if (!MappedUser || MappedUser->LocalPlayerIndex == INDEX_NONE)
|
|
{
|
|
if (LoginKeysForNewUser.Contains(EventArgs.Key))
|
|
{
|
|
// If we're in the middle of logging in just return true to ignore platform-specific input
|
|
if (MappedUser && MappedUser->IsDoingLogin())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Local multiplayer
|
|
FCommonUserInitializeParams NewParams = ParamsForLoginKey;
|
|
NewParams.LocalPlayerIndex = NextLocalPlayerIndex;
|
|
NewParams.PrimaryInputDevice = EventArgs.InputDevice;
|
|
|
|
return TryToInitializeUser(NewParams);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (WrappedInputKeyHandler.IsBound())
|
|
{
|
|
return WrappedInputKeyHandler.Execute(EventArgs);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static inline FText GetErrorText(const FOnlineErrorType& InOnlineError)
|
|
{
|
|
#if COMMONUSER_OSSV1
|
|
return InOnlineError.GetErrorMessage();
|
|
#else
|
|
return InOnlineError.GetText();
|
|
#endif
|
|
}
|
|
|
|
void UCommonUserSubsystem::HandleLoginForUserInitialize(const UCommonUserInfo* UserInfo, ELoginStatusType NewStatus, FUniqueNetIdRepl NetId, const TOptional<FOnlineErrorType>& InError, ECommonUserOnlineContext Context, FCommonUserInitializeParams Params)
|
|
{
|
|
UGameInstance* GameInstance = GetGameInstance();
|
|
check(GameInstance);
|
|
FTimerManager& TimerManager = GameInstance->GetTimerManager();
|
|
TOptional<FOnlineErrorType> Error = InError; // Copy so we can reset on handled errors
|
|
|
|
UCommonUserInfo* LocalUserInfo = ModifyInfo(UserInfo);
|
|
UCommonUserInfo* FirstUserInfo = ModifyInfo(GetUserInfoForLocalPlayerIndex(0));
|
|
|
|
if (!ensure(LocalUserInfo && FirstUserInfo))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check the hard platform/service ids
|
|
RefreshLocalUserInfo(LocalUserInfo);
|
|
|
|
FUniqueNetIdRepl FirstPlayerId = FirstUserInfo->GetNetId(ECommonUserOnlineContext::PlatformOrDefault);
|
|
|
|
// Check to see if we should make a guest after a login failure. Some platforms return success but reuse the first player's id, count this as a failure
|
|
if (LocalUserInfo != FirstUserInfo && LocalUserInfo->bCanBeGuest && (NewStatus == ELoginStatusType::NotLoggedIn || NetId == FirstPlayerId))
|
|
{
|
|
#if COMMONUSER_OSSV1
|
|
NetId = (FUniqueNetIdRef)FUniqueNetIdString::Create(FString::Printf(TEXT("GuestPlayer%d"), LocalUserInfo->LocalPlayerIndex), NULL_SUBSYSTEM);
|
|
#else
|
|
// TODO: OSSv2 FUniqueNetIdRepl wrapping FAccountId is in progress
|
|
// TODO: OSSv2 - How to handle guest accounts?
|
|
#endif
|
|
LocalUserInfo->bIsGuest = true;
|
|
NewStatus = ELoginStatusType::UsingLocalProfile;
|
|
Error.Reset();
|
|
UE_LOG(LogCommonUser, Log, TEXT("HandleLoginForUserInitialize created guest id %s for local player %d"), *NetId.ToString(), LocalUserInfo->LocalPlayerIndex);
|
|
}
|
|
else
|
|
{
|
|
LocalUserInfo->bIsGuest = false;
|
|
}
|
|
|
|
ensure(LocalUserInfo->IsDoingLogin());
|
|
|
|
if (Error.IsSet())
|
|
{
|
|
FText ErrorText = GetErrorText(Error.GetValue());
|
|
TimerManager.SetTimerForNextTick(FTimerDelegate::CreateUObject(this, &UCommonUserSubsystem::HandleUserInitializeFailed, Params, ErrorText));
|
|
return;
|
|
}
|
|
|
|
if (Context == ECommonUserOnlineContext::Game)
|
|
{
|
|
LocalUserInfo->UpdateCachedNetId(NetId, ECommonUserOnlineContext::Game);
|
|
}
|
|
|
|
ULocalPlayer* CurrentPlayer = GameInstance->GetLocalPlayerByIndex(LocalUserInfo->LocalPlayerIndex);
|
|
if (!CurrentPlayer && Params.bCanCreateNewLocalPlayer)
|
|
{
|
|
FString ErrorString;
|
|
CurrentPlayer = GameInstance->CreateLocalPlayer(LocalUserInfo->PlatformUser, ErrorString, true);
|
|
|
|
if (!CurrentPlayer)
|
|
{
|
|
TimerManager.SetTimerForNextTick(FTimerDelegate::CreateUObject(this, &UCommonUserSubsystem::HandleUserInitializeFailed, Params, FText::AsCultureInvariant(ErrorString)));
|
|
return;
|
|
}
|
|
ensure(GameInstance->GetLocalPlayerByIndex(LocalUserInfo->LocalPlayerIndex) == CurrentPlayer);
|
|
}
|
|
|
|
// Updates controller and net id if needed
|
|
SetLocalPlayerUserInfo(CurrentPlayer, LocalUserInfo);
|
|
|
|
// Set a delayed callback
|
|
TimerManager.SetTimerForNextTick(FTimerDelegate::CreateUObject(this, &UCommonUserSubsystem::HandleUserInitializeSucceeded, Params));
|
|
}
|
|
|
|
void UCommonUserSubsystem::HandleUserInitializeFailed(FCommonUserInitializeParams Params, FText Error)
|
|
{
|
|
UCommonUserInfo* LocalUserInfo = ModifyInfo(GetUserInfoForLocalPlayerIndex(Params.LocalPlayerIndex));
|
|
|
|
if (!LocalUserInfo)
|
|
{
|
|
// The user info was reset since this was scheduled
|
|
return;
|
|
}
|
|
|
|
UE_LOG(LogCommonUser, Warning, TEXT("TryToInitializeUser %d failed with error %s"), LocalUserInfo->LocalPlayerIndex, *Error.ToString());
|
|
|
|
// If state is wrong, abort as we might have gotten canceled
|
|
if (!ensure(LocalUserInfo->IsDoingLogin()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If initial login failed or we ended up totally logged out, set to complete failure
|
|
ELoginStatusType NewStatus = GetLocalUserLoginStatus(Params.PlatformUser, Params.OnlineContext);
|
|
if (NewStatus == ELoginStatusType::NotLoggedIn || LocalUserInfo->InitializationState == ECommonUserInitializationState::DoingInitialLogin)
|
|
{
|
|
LocalUserInfo->InitializationState = ECommonUserInitializationState::FailedtoLogin;
|
|
}
|
|
else
|
|
{
|
|
LocalUserInfo->InitializationState = ECommonUserInitializationState::LoggedInLocalOnly;
|
|
}
|
|
|
|
FText TitleText = NSLOCTEXT("CommonUser", "LoginFailedTitle", "Login Failure");
|
|
|
|
if (!Params.bSuppressLoginErrors)
|
|
{
|
|
SendSystemMessage(FCommonUserTags::SystemMessage_Error_InitializeLocalPlayerFailed, TitleText, Error);
|
|
}
|
|
|
|
// Call callbacks
|
|
Params.OnUserInitializeComplete.ExecuteIfBound(LocalUserInfo, false, Error, Params.RequestedPrivilege, Params.OnlineContext);
|
|
OnUserInitializeComplete.Broadcast(LocalUserInfo, false, Error, Params.RequestedPrivilege, Params.OnlineContext);
|
|
}
|
|
|
|
void UCommonUserSubsystem::HandleUserInitializeSucceeded(FCommonUserInitializeParams Params)
|
|
{
|
|
UCommonUserInfo* LocalUserInfo = ModifyInfo(GetUserInfoForLocalPlayerIndex(Params.LocalPlayerIndex));
|
|
|
|
if (!LocalUserInfo)
|
|
{
|
|
// The user info was reset since this was scheduled
|
|
return;
|
|
}
|
|
|
|
// If state is wrong, abort as we might have gotten cancelled
|
|
if (!ensure(LocalUserInfo->IsDoingLogin()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Fix up state
|
|
if (Params.RequestedPrivilege == ECommonUserPrivilege::CanPlayOnline)
|
|
{
|
|
LocalUserInfo->InitializationState = ECommonUserInitializationState::LoggedInOnline;
|
|
}
|
|
else
|
|
{
|
|
LocalUserInfo->InitializationState = ECommonUserInitializationState::LoggedInLocalOnly;
|
|
}
|
|
|
|
ensure(LocalUserInfo->GetPrivilegeAvailability(Params.RequestedPrivilege) == ECommonUserAvailability::NowAvailable);
|
|
|
|
// Call callbacks
|
|
Params.OnUserInitializeComplete.ExecuteIfBound(LocalUserInfo, true, FText(), Params.RequestedPrivilege, Params.OnlineContext);
|
|
OnUserInitializeComplete.Broadcast(LocalUserInfo, true, FText(), Params.RequestedPrivilege, Params.OnlineContext);
|
|
}
|
|
|
|
bool UCommonUserSubsystem::LoginLocalUser(const UCommonUserInfo* UserInfo, ECommonUserPrivilege RequestedPrivilege, ECommonUserOnlineContext Context, FOnLocalUserLoginCompleteDelegate OnComplete)
|
|
{
|
|
UCommonUserInfo* LocalUserInfo = ModifyInfo(UserInfo);
|
|
if (!ensure(UserInfo))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TSharedRef<FUserLoginRequest> NewRequest = MakeShared<FUserLoginRequest>(LocalUserInfo, RequestedPrivilege, Context, MoveTemp(OnComplete));
|
|
ActiveLoginRequests.Add(NewRequest);
|
|
|
|
// This will execute callback or start login process
|
|
ProcessLoginRequest(NewRequest);
|
|
|
|
return true;
|
|
}
|
|
|
|
void UCommonUserSubsystem::ProcessLoginRequest(TSharedRef<FUserLoginRequest> Request)
|
|
{
|
|
// First, see if we've fully logged in
|
|
UCommonUserInfo* UserInfo = Request->UserInfo.Get();
|
|
|
|
if (!UserInfo)
|
|
{
|
|
// User is gone, just delete this request
|
|
ActiveLoginRequests.Remove(Request);
|
|
|
|
return;
|
|
}
|
|
|
|
const FPlatformUserId PlatformUser = UserInfo->GetPlatformUserId();
|
|
|
|
// If the platform user id is invalid because this is a guest, skip right to failure
|
|
if (!IsRealPlatformUser(PlatformUser))
|
|
{
|
|
#if COMMONUSER_OSSV1
|
|
Request->Error = FOnlineError(NSLOCTEXT("CommonUser", "InvalidPlatformUser", "Invalid Platform User"));
|
|
#else
|
|
Request->Error = UE::Online::Errors::InvalidUser();
|
|
#endif
|
|
// Remove from active array
|
|
ActiveLoginRequests.Remove(Request);
|
|
|
|
// Execute delegate if bound
|
|
Request->Delegate.ExecuteIfBound(UserInfo, ELoginStatusType::NotLoggedIn, FUniqueNetIdRepl(), Request->Error, Request->DesiredContext);
|
|
|
|
return;
|
|
}
|
|
|
|
// Figure out what context to process first
|
|
if (Request->CurrentContext == ECommonUserOnlineContext::Invalid)
|
|
{
|
|
// First start with platform context if this is a game login
|
|
if (Request->DesiredContext == ECommonUserOnlineContext::Game)
|
|
{
|
|
Request->CurrentContext = ResolveOnlineContext(ECommonUserOnlineContext::PlatformOrDefault);
|
|
}
|
|
else
|
|
{
|
|
Request->CurrentContext = ResolveOnlineContext(Request->DesiredContext);
|
|
}
|
|
}
|
|
|
|
ELoginStatusType CurrentStatus = GetLocalUserLoginStatus(PlatformUser, Request->CurrentContext);
|
|
FUniqueNetIdRepl CurrentId = GetLocalUserNetId(PlatformUser, Request->CurrentContext);
|
|
FOnlineContextCache* System = GetContextCache(Request->CurrentContext);
|
|
|
|
if (!ensure(System))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Starting a new request
|
|
if (Request->OverallLoginState == ECommonUserAsyncTaskState::NotStarted)
|
|
{
|
|
Request->OverallLoginState = ECommonUserAsyncTaskState::InProgress;
|
|
}
|
|
|
|
bool bHasRequiredStatus = (CurrentStatus == ELoginStatusType::LoggedIn);
|
|
if (Request->DesiredPrivilege == ECommonUserPrivilege::CanPlay)
|
|
{
|
|
// If this is not an online required login, allow local profile to count as fully logged in
|
|
bHasRequiredStatus |= (CurrentStatus == ELoginStatusType::UsingLocalProfile);
|
|
}
|
|
|
|
// Check for overall success
|
|
if (bHasRequiredStatus && CurrentId.IsValid())
|
|
{
|
|
// Stall if we're waiting for the login UI to close
|
|
if (Request->LoginUIState == ECommonUserAsyncTaskState::InProgress)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Request->OverallLoginState = ECommonUserAsyncTaskState::Done;
|
|
}
|
|
else
|
|
{
|
|
// Try using platform auth to login
|
|
if (Request->TransferPlatformAuthState == ECommonUserAsyncTaskState::NotStarted)
|
|
{
|
|
Request->TransferPlatformAuthState = ECommonUserAsyncTaskState::InProgress;
|
|
|
|
if (TransferPlatformAuth(System, Request, PlatformUser))
|
|
{
|
|
return;
|
|
}
|
|
// We didn't start a login attempt, so set failure
|
|
Request->TransferPlatformAuthState = ECommonUserAsyncTaskState::Failed;
|
|
}
|
|
|
|
// Next check AutoLogin
|
|
if (Request->AutoLoginState == ECommonUserAsyncTaskState::NotStarted)
|
|
{
|
|
if (Request->TransferPlatformAuthState == ECommonUserAsyncTaskState::Done || Request->TransferPlatformAuthState == ECommonUserAsyncTaskState::Failed)
|
|
{
|
|
Request->AutoLoginState = ECommonUserAsyncTaskState::InProgress;
|
|
|
|
// Try an auto login with default credentials, this will work on many platforms
|
|
if (AutoLogin(System, Request, PlatformUser))
|
|
{
|
|
return;
|
|
}
|
|
// We didn't start an autologin attempt, so set failure
|
|
Request->AutoLoginState = ECommonUserAsyncTaskState::Failed;
|
|
}
|
|
}
|
|
|
|
// Next check login UI
|
|
if (Request->LoginUIState == ECommonUserAsyncTaskState::NotStarted)
|
|
{
|
|
if ((Request->TransferPlatformAuthState == ECommonUserAsyncTaskState::Done || Request->TransferPlatformAuthState == ECommonUserAsyncTaskState::Failed)
|
|
&& (Request->AutoLoginState == ECommonUserAsyncTaskState::Done || Request->AutoLoginState == ECommonUserAsyncTaskState::Failed))
|
|
{
|
|
Request->LoginUIState = ECommonUserAsyncTaskState::InProgress;
|
|
|
|
if (ShowLoginUI(System, Request, PlatformUser))
|
|
{
|
|
return;
|
|
}
|
|
// We didn't show a UI, so set failure
|
|
Request->LoginUIState = ECommonUserAsyncTaskState::Failed;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for overall failure
|
|
if (Request->LoginUIState == ECommonUserAsyncTaskState::Failed &&
|
|
Request->AutoLoginState == ECommonUserAsyncTaskState::Failed &&
|
|
Request->TransferPlatformAuthState == ECommonUserAsyncTaskState::Failed)
|
|
{
|
|
Request->OverallLoginState = ECommonUserAsyncTaskState::Failed;
|
|
}
|
|
else if (Request->OverallLoginState == ECommonUserAsyncTaskState::InProgress &&
|
|
Request->LoginUIState != ECommonUserAsyncTaskState::InProgress &&
|
|
Request->AutoLoginState != ECommonUserAsyncTaskState::InProgress &&
|
|
Request->TransferPlatformAuthState != ECommonUserAsyncTaskState::InProgress)
|
|
{
|
|
// If none of the substates are still in progress but we haven't successfully logged in, mark this as a failure to avoid stalling forever
|
|
Request->OverallLoginState = ECommonUserAsyncTaskState::Failed;
|
|
}
|
|
|
|
if (Request->OverallLoginState == ECommonUserAsyncTaskState::Done)
|
|
{
|
|
// Do the permissions check if needed
|
|
if (Request->PrivilegeCheckState == ECommonUserAsyncTaskState::NotStarted)
|
|
{
|
|
Request->PrivilegeCheckState = ECommonUserAsyncTaskState::InProgress;
|
|
|
|
ECommonUserPrivilegeResult CachedResult = UserInfo->GetCachedPrivilegeResult(Request->DesiredPrivilege, Request->CurrentContext);
|
|
if (CachedResult == ECommonUserPrivilegeResult::Available)
|
|
{
|
|
// Use cached success value
|
|
Request->PrivilegeCheckState = ECommonUserAsyncTaskState::Done;
|
|
}
|
|
else
|
|
{
|
|
if (QueryUserPrivilege(System, Request, PlatformUser))
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
#if !COMMONUSER_OSSV1
|
|
// Temp while OSSv2 gets privileges implemented
|
|
CachedResult = ECommonUserPrivilegeResult::Available;
|
|
Request->PrivilegeCheckState = ECommonUserAsyncTaskState::Done;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Request->PrivilegeCheckState == ECommonUserAsyncTaskState::Failed)
|
|
{
|
|
// Count a privilege failure as a login failure
|
|
Request->OverallLoginState = ECommonUserAsyncTaskState::Failed;
|
|
}
|
|
else if (Request->PrivilegeCheckState == ECommonUserAsyncTaskState::Done)
|
|
{
|
|
// If platform context done but still need to do service context, do that next
|
|
ECommonUserOnlineContext ResolvedDesiredContext = ResolveOnlineContext(Request->DesiredContext);
|
|
|
|
if (Request->OverallLoginState == ECommonUserAsyncTaskState::Done && Request->CurrentContext != ResolvedDesiredContext)
|
|
{
|
|
Request->CurrentContext = ResolvedDesiredContext;
|
|
Request->OverallLoginState = ECommonUserAsyncTaskState::NotStarted;
|
|
Request->PrivilegeCheckState = ECommonUserAsyncTaskState::NotStarted;
|
|
Request->TransferPlatformAuthState = ECommonUserAsyncTaskState::NotStarted;
|
|
Request->AutoLoginState = ECommonUserAsyncTaskState::NotStarted;
|
|
Request->LoginUIState = ECommonUserAsyncTaskState::NotStarted;
|
|
|
|
// Reprocess and immediately return
|
|
ProcessLoginRequest(Request);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Request->PrivilegeCheckState == ECommonUserAsyncTaskState::InProgress)
|
|
{
|
|
// Stall to wait for it to finish
|
|
return;
|
|
}
|
|
|
|
// If done, remove and do callback
|
|
if (Request->OverallLoginState == ECommonUserAsyncTaskState::Done || Request->OverallLoginState == ECommonUserAsyncTaskState::Failed)
|
|
{
|
|
// Skip if this already happened in a nested function
|
|
if (ActiveLoginRequests.Contains(Request))
|
|
{
|
|
// Add a generic error if none is set
|
|
if (Request->OverallLoginState == ECommonUserAsyncTaskState::Failed && !Request->Error.IsSet())
|
|
{
|
|
#if COMMONUSER_OSSV1
|
|
Request->Error = FOnlineError(NSLOCTEXT("CommonUser", "FailedToRequest", "Failed to Request Login"));
|
|
#else
|
|
Request->Error = UE::Online::Errors::RequestFailure();
|
|
#endif
|
|
}
|
|
|
|
// Remove from active array
|
|
ActiveLoginRequests.Remove(Request);
|
|
|
|
// Execute delegate if bound
|
|
Request->Delegate.ExecuteIfBound(UserInfo, CurrentStatus, CurrentId, Request->Error, Request->DesiredContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if COMMONUSER_OSSV1
|
|
void UCommonUserSubsystem::HandleUserLoginCompleted(int32 PlatformUserIndex, bool bWasSuccessful, const FUniqueNetId& NetId, const FString& ErrorString, ECommonUserOnlineContext Context)
|
|
{
|
|
FPlatformUserId PlatformUser = GetPlatformUserIdForIndex(PlatformUserIndex);
|
|
ELoginStatusType NewStatus = GetLocalUserLoginStatus(PlatformUser, Context);
|
|
FUniqueNetIdRepl NewId = FUniqueNetIdRepl(NetId);
|
|
UE_LOG(LogCommonUser, Log, TEXT("Player login Completed - System:%s, UserIdx:%d, Successful:%d, NewStatus:%s, NewId:%s, ErrorIfAny:%s"),
|
|
*GetOnlineSubsystemName(Context).ToString(),
|
|
PlatformUserIndex,
|
|
(int32)bWasSuccessful,
|
|
ELoginStatus::ToString(NewStatus),
|
|
*NewId.ToString(),
|
|
*ErrorString);
|
|
|
|
// Update any waiting login requests
|
|
TArray<TSharedRef<FUserLoginRequest>> RequestsCopy = ActiveLoginRequests;
|
|
for (TSharedRef<FUserLoginRequest>& Request : RequestsCopy)
|
|
{
|
|
UCommonUserInfo* UserInfo = Request->UserInfo.Get();
|
|
|
|
if (!UserInfo)
|
|
{
|
|
// User is gone, just delete this request
|
|
ActiveLoginRequests.Remove(Request);
|
|
|
|
continue;
|
|
}
|
|
|
|
if (UserInfo->PlatformUser == PlatformUser && Request->CurrentContext == Context)
|
|
{
|
|
// On some platforms this gets called from the login UI with a failure
|
|
if (Request->AutoLoginState == ECommonUserAsyncTaskState::InProgress)
|
|
{
|
|
Request->AutoLoginState = bWasSuccessful ? ECommonUserAsyncTaskState::Done : ECommonUserAsyncTaskState::Failed;
|
|
}
|
|
|
|
if (!bWasSuccessful)
|
|
{
|
|
Request->Error = FOnlineError(FText::FromString(ErrorString));
|
|
}
|
|
|
|
ProcessLoginRequest(Request);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UCommonUserSubsystem::HandleOnLoginUIClosed(TSharedPtr<const FUniqueNetId> LoggedInNetId, const int PlatformUserIndex, const FOnlineError& Error, ECommonUserOnlineContext Context)
|
|
{
|
|
FPlatformUserId PlatformUser = GetPlatformUserIdForIndex(PlatformUserIndex);
|
|
|
|
// Update any waiting login requests
|
|
TArray<TSharedRef<FUserLoginRequest>> RequestsCopy = ActiveLoginRequests;
|
|
for (TSharedRef<FUserLoginRequest>& Request : RequestsCopy)
|
|
{
|
|
UCommonUserInfo* UserInfo = Request->UserInfo.Get();
|
|
|
|
if (!UserInfo)
|
|
{
|
|
// User is gone, just delete this request
|
|
ActiveLoginRequests.Remove(Request);
|
|
|
|
continue;
|
|
}
|
|
|
|
// Look for first user trying to log in on this context
|
|
if (Request->CurrentContext == Context && Request->LoginUIState == ECommonUserAsyncTaskState::InProgress)
|
|
{
|
|
if (LoggedInNetId.IsValid() && LoggedInNetId->IsValid() && Error.WasSuccessful())
|
|
{
|
|
// The platform user id that actually logged in may not be the same one who requested the UI,
|
|
// so swap it if the returned id is actually valid
|
|
if (UserInfo->PlatformUser != PlatformUser && PlatformUser != PLATFORMUSERID_NONE)
|
|
{
|
|
UserInfo->PlatformUser = PlatformUser;
|
|
}
|
|
|
|
Request->LoginUIState = ECommonUserAsyncTaskState::Done;
|
|
Request->Error.Reset();
|
|
}
|
|
else
|
|
{
|
|
Request->LoginUIState = ECommonUserAsyncTaskState::Failed;
|
|
Request->Error = Error;
|
|
}
|
|
|
|
ProcessLoginRequest(Request);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UCommonUserSubsystem::HandleCheckPrivilegesComplete(const FUniqueNetId& UserId, EUserPrivileges::Type Privilege, uint32 PrivilegeResults, ECommonUserPrivilege UserPrivilege, TWeakObjectPtr<UCommonUserInfo> CommonUserInfo, ECommonUserOnlineContext Context)
|
|
{
|
|
// Only handle if user still exists
|
|
UCommonUserInfo* UserInfo = CommonUserInfo.Get();
|
|
|
|
if (!UserInfo)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ECommonUserPrivilegeResult UserResult = ConvertOSSPrivilegeResult(Privilege, PrivilegeResults);
|
|
|
|
// Update the user cached value
|
|
UpdateUserPrivilegeResult(UserInfo, UserPrivilege, UserResult, Context);
|
|
|
|
FOnlineContextCache* ContextCache = GetContextCache(Context);
|
|
check(ContextCache);
|
|
|
|
// If this returns disconnected, update the connection status
|
|
if (UserResult == ECommonUserPrivilegeResult::NetworkConnectionUnavailable)
|
|
{
|
|
ContextCache->CurrentConnectionStatus = EOnlineServerConnectionStatus::NoNetworkConnection;
|
|
}
|
|
else if (UserResult == ECommonUserPrivilegeResult::Available && UserPrivilege == ECommonUserPrivilege::CanPlayOnline)
|
|
{
|
|
if (ContextCache->CurrentConnectionStatus == EOnlineServerConnectionStatus::NoNetworkConnection)
|
|
{
|
|
ContextCache->CurrentConnectionStatus = EOnlineServerConnectionStatus::Normal;
|
|
}
|
|
}
|
|
|
|
// See if a login request is waiting on this
|
|
TArray<TSharedRef<FUserLoginRequest>> RequestsCopy = ActiveLoginRequests;
|
|
for (TSharedRef<FUserLoginRequest>& Request : RequestsCopy)
|
|
{
|
|
if (Request->UserInfo.Get() == UserInfo && Request->CurrentContext == Context && Request->DesiredPrivilege == UserPrivilege && Request->PrivilegeCheckState == ECommonUserAsyncTaskState::InProgress)
|
|
{
|
|
if (UserResult == ECommonUserPrivilegeResult::Available)
|
|
{
|
|
Request->PrivilegeCheckState = ECommonUserAsyncTaskState::Done;
|
|
}
|
|
else
|
|
{
|
|
Request->PrivilegeCheckState = ECommonUserAsyncTaskState::Failed;
|
|
|
|
// Forms strings in english like "(The user is not allowed) to (play the game)"
|
|
Request->Error = FOnlineError(FText::Format(NSLOCTEXT("CommonUser", "PrivilegeFailureFormat", "{0} to {1}"), GetPrivilegeResultDescription(UserResult), GetPrivilegeDescription(UserPrivilege)));
|
|
}
|
|
|
|
ProcessLoginRequest(Request);
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
|
|
void UCommonUserSubsystem::HandleUserLoginCompletedV2(const UE::Online::TOnlineResult<UE::Online::FAuthLogin>& Result, FPlatformUserId PlatformUser, ECommonUserOnlineContext Context)
|
|
{
|
|
const bool bWasSuccessful = Result.IsOk();
|
|
FAccountId NewId;
|
|
if (bWasSuccessful)
|
|
{
|
|
NewId = Result.GetOkValue().AccountInfo->AccountId;
|
|
}
|
|
|
|
ELoginStatusType NewStatus = GetLocalUserLoginStatus(PlatformUser, Context);
|
|
UE_LOG(LogCommonUser, Log, TEXT("Player login Completed - System:%d, UserIdx:%d, Successful:%d, NewId:%s, ErrorIfAny:%s"),
|
|
(int32)Context,
|
|
PlatformUser.GetInternalId(),
|
|
(int32)Result.IsOk(),
|
|
*ToLogString(NewId),
|
|
Result.IsError() ? *Result.GetErrorValue().GetLogString() : TEXT(""));
|
|
|
|
// Update any waiting login requests
|
|
TArray<TSharedRef<FUserLoginRequest>> RequestsCopy = ActiveLoginRequests;
|
|
for (TSharedRef<FUserLoginRequest>& Request : RequestsCopy)
|
|
{
|
|
UCommonUserInfo* UserInfo = Request->UserInfo.Get();
|
|
|
|
if (!UserInfo)
|
|
{
|
|
// User is gone, just delete this request
|
|
ActiveLoginRequests.Remove(Request);
|
|
|
|
continue;
|
|
}
|
|
|
|
if (UserInfo->PlatformUser == PlatformUser && Request->CurrentContext == Context)
|
|
{
|
|
// On some platforms this gets called from the login UI with a failure
|
|
if (Request->AutoLoginState == ECommonUserAsyncTaskState::InProgress)
|
|
{
|
|
Request->AutoLoginState = bWasSuccessful ? ECommonUserAsyncTaskState::Done : ECommonUserAsyncTaskState::Failed;
|
|
}
|
|
|
|
if (bWasSuccessful)
|
|
{
|
|
Request->Error.Reset();
|
|
}
|
|
else
|
|
{
|
|
Request->Error = Result.GetErrorValue();
|
|
}
|
|
|
|
ProcessLoginRequest(Request);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UCommonUserSubsystem::HandleOnLoginUIClosedV2(const UE::Online::TOnlineResult<UE::Online::FExternalUIShowLoginUI>& Result, FPlatformUserId PlatformUser, ECommonUserOnlineContext Context)
|
|
{
|
|
// Update any waiting login requests
|
|
TArray<TSharedRef<FUserLoginRequest>> RequestsCopy = ActiveLoginRequests;
|
|
for (TSharedRef<FUserLoginRequest>& Request : RequestsCopy)
|
|
{
|
|
UCommonUserInfo* UserInfo = Request->UserInfo.Get();
|
|
|
|
if (!UserInfo)
|
|
{
|
|
// User is gone, just delete this request
|
|
ActiveLoginRequests.Remove(Request);
|
|
|
|
continue;
|
|
}
|
|
|
|
// Look for first user trying to log in on this context
|
|
if (Request->CurrentContext == Context && Request->LoginUIState == ECommonUserAsyncTaskState::InProgress)
|
|
{
|
|
if (Result.IsOk())
|
|
{
|
|
// The platform user id that actually logged in may not be the same one who requested the UI,
|
|
// so swap it if the returned id is actually valid
|
|
if (UserInfo->PlatformUser != PlatformUser && PlatformUser != PLATFORMUSERID_NONE)
|
|
{
|
|
UserInfo->PlatformUser = PlatformUser;
|
|
}
|
|
|
|
Request->LoginUIState = ECommonUserAsyncTaskState::Done;
|
|
Request->Error.Reset();
|
|
}
|
|
else
|
|
{
|
|
Request->LoginUIState = ECommonUserAsyncTaskState::Failed;
|
|
Request->Error = Result.GetErrorValue();
|
|
}
|
|
|
|
ProcessLoginRequest(Request);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UCommonUserSubsystem::HandleCheckPrivilegesComplete(const UE::Online::TOnlineResult<UE::Online::FQueryUserPrivilege>& Result, TWeakObjectPtr<UCommonUserInfo> CommonUserInfo, EUserPrivileges DesiredPrivilege, ECommonUserOnlineContext Context)
|
|
{
|
|
// Only handle if user still exists
|
|
UCommonUserInfo* UserInfo = CommonUserInfo.Get();
|
|
if (!UserInfo)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ECommonUserPrivilege UserPrivilege = ConvertOnlineServicesPrivilege(DesiredPrivilege);
|
|
ECommonUserPrivilegeResult UserResult = ECommonUserPrivilegeResult::PlatformFailure;
|
|
if (const FQueryUserPrivilege::Result* OkResult = Result.TryGetOkValue())
|
|
{
|
|
UserResult = ConvertOnlineServicesPrivilegeResult(DesiredPrivilege, OkResult->PrivilegeResult);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogCommonUser, Warning, TEXT("QueryUserPrivilege failed: %s"), *Result.GetErrorValue().GetLogString());
|
|
}
|
|
|
|
// Update the user cached value
|
|
UserInfo->UpdateCachedPrivilegeResult(UserPrivilege, UserResult, Context);
|
|
|
|
// See if a login request is waiting on this
|
|
TArray<TSharedRef<FUserLoginRequest>> RequestsCopy = ActiveLoginRequests;
|
|
for (TSharedRef<FUserLoginRequest>& Request : RequestsCopy)
|
|
{
|
|
if (Request->UserInfo.Get() == UserInfo && Request->CurrentContext == Context && Request->DesiredPrivilege == UserPrivilege && Request->PrivilegeCheckState == ECommonUserAsyncTaskState::InProgress)
|
|
{
|
|
if (UserResult == ECommonUserPrivilegeResult::Available)
|
|
{
|
|
Request->PrivilegeCheckState = ECommonUserAsyncTaskState::Done;
|
|
}
|
|
else
|
|
{
|
|
Request->PrivilegeCheckState = ECommonUserAsyncTaskState::Failed;
|
|
Request->Error = Result.IsError() ? Result.GetErrorValue() : UE::Online::Errors::Unknown();
|
|
}
|
|
|
|
ProcessLoginRequest(Request);
|
|
}
|
|
}
|
|
}
|
|
#endif // COMMONUSER_OSSV1
|
|
|
|
void UCommonUserSubsystem::RefreshLocalUserInfo(UCommonUserInfo* UserInfo)
|
|
{
|
|
if (ensure(UserInfo))
|
|
{
|
|
// Always update default
|
|
UserInfo->UpdateCachedNetId(GetLocalUserNetId(UserInfo->PlatformUser, ECommonUserOnlineContext::Default), ECommonUserOnlineContext::Default);
|
|
|
|
if (HasSeparatePlatformContext())
|
|
{
|
|
// Also update platform
|
|
UserInfo->UpdateCachedNetId(GetLocalUserNetId(UserInfo->PlatformUser, ECommonUserOnlineContext::Platform), ECommonUserOnlineContext::Platform);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UCommonUserSubsystem::HandleChangedAvailability(UCommonUserInfo* UserInfo, ECommonUserPrivilege Privilege, ECommonUserAvailability OldAvailability)
|
|
{
|
|
ECommonUserAvailability NewAvailability = UserInfo->GetPrivilegeAvailability(Privilege);
|
|
|
|
if (OldAvailability != NewAvailability)
|
|
{
|
|
OnUserPrivilegeChanged.Broadcast(UserInfo, Privilege, OldAvailability, NewAvailability);
|
|
}
|
|
}
|
|
|
|
void UCommonUserSubsystem::UpdateUserPrivilegeResult(UCommonUserInfo* UserInfo, ECommonUserPrivilege Privilege, ECommonUserPrivilegeResult Result, ECommonUserOnlineContext Context)
|
|
{
|
|
check(UserInfo);
|
|
|
|
ECommonUserAvailability OldAvailability = UserInfo->GetPrivilegeAvailability(Privilege);
|
|
|
|
UserInfo->UpdateCachedPrivilegeResult(Privilege, Result, Context);
|
|
|
|
HandleChangedAvailability(UserInfo, Privilege, OldAvailability);
|
|
}
|
|
|
|
#if COMMONUSER_OSSV1
|
|
ECommonUserPrivilege UCommonUserSubsystem::ConvertOSSPrivilege(EUserPrivileges::Type Privilege) const
|
|
{
|
|
switch (Privilege)
|
|
{
|
|
case EUserPrivileges::CanPlay:
|
|
return ECommonUserPrivilege::CanPlay;
|
|
case EUserPrivileges::CanPlayOnline:
|
|
return ECommonUserPrivilege::CanPlayOnline;
|
|
case EUserPrivileges::CanCommunicateOnline:
|
|
return ECommonUserPrivilege::CanCommunicateViaTextOnline; // No good thing to do here, just mapping to text.
|
|
case EUserPrivileges::CanUseUserGeneratedContent:
|
|
return ECommonUserPrivilege::CanUseUserGeneratedContent;
|
|
case EUserPrivileges::CanUserCrossPlay:
|
|
return ECommonUserPrivilege::CanUseCrossPlay;
|
|
default:
|
|
return ECommonUserPrivilege::Invalid_Count;
|
|
}
|
|
}
|
|
|
|
EUserPrivileges::Type UCommonUserSubsystem::ConvertOSSPrivilege(ECommonUserPrivilege Privilege) const
|
|
{
|
|
switch (Privilege)
|
|
{
|
|
case ECommonUserPrivilege::CanPlay:
|
|
return EUserPrivileges::CanPlay;
|
|
case ECommonUserPrivilege::CanPlayOnline:
|
|
return EUserPrivileges::CanPlayOnline;
|
|
case ECommonUserPrivilege::CanCommunicateViaTextOnline:
|
|
case ECommonUserPrivilege::CanCommunicateViaVoiceOnline:
|
|
return EUserPrivileges::CanCommunicateOnline;
|
|
case ECommonUserPrivilege::CanUseUserGeneratedContent:
|
|
return EUserPrivileges::CanUseUserGeneratedContent;
|
|
case ECommonUserPrivilege::CanUseCrossPlay:
|
|
return EUserPrivileges::CanUserCrossPlay;
|
|
default:
|
|
// No failure type, return CanPlay
|
|
return EUserPrivileges::CanPlay;
|
|
}
|
|
}
|
|
|
|
ECommonUserPrivilegeResult UCommonUserSubsystem::ConvertOSSPrivilegeResult(EUserPrivileges::Type Privilege, uint32 Results) const
|
|
{
|
|
// The V1 results enum is a bitfield where each platform behaves a bit differently
|
|
if (Results == (uint32)IOnlineIdentity::EPrivilegeResults::NoFailures)
|
|
{
|
|
return ECommonUserPrivilegeResult::Available;
|
|
}
|
|
if ((Results & (uint32)IOnlineIdentity::EPrivilegeResults::UserNotFound) || (Results & (uint32)IOnlineIdentity::EPrivilegeResults::UserNotLoggedIn))
|
|
{
|
|
return ECommonUserPrivilegeResult::UserNotLoggedIn;
|
|
}
|
|
if ((Results & (uint32)IOnlineIdentity::EPrivilegeResults::RequiredPatchAvailable) || (Results & (uint32)IOnlineIdentity::EPrivilegeResults::RequiredSystemUpdate))
|
|
{
|
|
return ECommonUserPrivilegeResult::VersionOutdated;
|
|
}
|
|
if (Results & (uint32)IOnlineIdentity::EPrivilegeResults::AgeRestrictionFailure)
|
|
{
|
|
return ECommonUserPrivilegeResult::AgeRestricted;
|
|
}
|
|
if (Results & (uint32)IOnlineIdentity::EPrivilegeResults::AccountTypeFailure)
|
|
{
|
|
return ECommonUserPrivilegeResult::AccountTypeRestricted;
|
|
}
|
|
if (Results & (uint32)IOnlineIdentity::EPrivilegeResults::NetworkConnectionUnavailable)
|
|
{
|
|
return ECommonUserPrivilegeResult::NetworkConnectionUnavailable;
|
|
}
|
|
|
|
// Bucket other account failures together
|
|
uint32 AccountUseFailures = (uint32)IOnlineIdentity::EPrivilegeResults::OnlinePlayRestricted
|
|
| (uint32)IOnlineIdentity::EPrivilegeResults::UGCRestriction
|
|
| (uint32)IOnlineIdentity::EPrivilegeResults::ChatRestriction;
|
|
|
|
if (Results & AccountUseFailures)
|
|
{
|
|
return ECommonUserPrivilegeResult::AccountUseRestricted;
|
|
}
|
|
|
|
// If you can't play at all, this is a license failure
|
|
if (Privilege == EUserPrivileges::CanPlay)
|
|
{
|
|
return ECommonUserPrivilegeResult::LicenseInvalid;
|
|
}
|
|
|
|
// Unknown reason
|
|
return ECommonUserPrivilegeResult::PlatformFailure;
|
|
}
|
|
#else
|
|
ECommonUserPrivilege UCommonUserSubsystem::ConvertOnlineServicesPrivilege(EUserPrivileges Privilege) const
|
|
{
|
|
switch (Privilege)
|
|
{
|
|
case EUserPrivileges::CanPlay:
|
|
return ECommonUserPrivilege::CanPlay;
|
|
case EUserPrivileges::CanPlayOnline:
|
|
return ECommonUserPrivilege::CanPlayOnline;
|
|
case EUserPrivileges::CanCommunicateViaTextOnline:
|
|
return ECommonUserPrivilege::CanCommunicateViaTextOnline;
|
|
case EUserPrivileges::CanCommunicateViaVoiceOnline:
|
|
return ECommonUserPrivilege::CanCommunicateViaVoiceOnline;
|
|
case EUserPrivileges::CanUseUserGeneratedContent:
|
|
return ECommonUserPrivilege::CanUseUserGeneratedContent;
|
|
case EUserPrivileges::CanCrossPlay:
|
|
return ECommonUserPrivilege::CanUseCrossPlay;
|
|
default:
|
|
return ECommonUserPrivilege::Invalid_Count;
|
|
}
|
|
}
|
|
|
|
EUserPrivileges UCommonUserSubsystem::ConvertOnlineServicesPrivilege(ECommonUserPrivilege Privilege) const
|
|
{
|
|
switch (Privilege)
|
|
{
|
|
case ECommonUserPrivilege::CanPlay:
|
|
return EUserPrivileges::CanPlay;
|
|
case ECommonUserPrivilege::CanPlayOnline:
|
|
return EUserPrivileges::CanPlayOnline;
|
|
case ECommonUserPrivilege::CanCommunicateViaTextOnline:
|
|
return EUserPrivileges::CanCommunicateViaTextOnline;
|
|
case ECommonUserPrivilege::CanCommunicateViaVoiceOnline:
|
|
return EUserPrivileges::CanCommunicateViaVoiceOnline;
|
|
case ECommonUserPrivilege::CanUseUserGeneratedContent:
|
|
return EUserPrivileges::CanUseUserGeneratedContent;
|
|
case ECommonUserPrivilege::CanUseCrossPlay:
|
|
return EUserPrivileges::CanCrossPlay;
|
|
default:
|
|
// No failure type, return CanPlay
|
|
return EUserPrivileges::CanPlay;
|
|
}
|
|
}
|
|
|
|
ECommonUserPrivilegeResult UCommonUserSubsystem::ConvertOnlineServicesPrivilegeResult(EUserPrivileges Privilege, EPrivilegeResults Results) const
|
|
{
|
|
// The V1 results enum is a bitfield where each platform behaves a bit differently
|
|
if (Results == EPrivilegeResults::NoFailures)
|
|
{
|
|
return ECommonUserPrivilegeResult::Available;
|
|
}
|
|
if (EnumHasAnyFlags(Results, EPrivilegeResults::UserNotFound | EPrivilegeResults::UserNotLoggedIn))
|
|
{
|
|
return ECommonUserPrivilegeResult::UserNotLoggedIn;
|
|
}
|
|
if (EnumHasAnyFlags(Results, EPrivilegeResults::RequiredPatchAvailable | EPrivilegeResults::RequiredSystemUpdate))
|
|
{
|
|
return ECommonUserPrivilegeResult::VersionOutdated;
|
|
}
|
|
if (EnumHasAnyFlags(Results, EPrivilegeResults::AgeRestrictionFailure))
|
|
{
|
|
return ECommonUserPrivilegeResult::AgeRestricted;
|
|
}
|
|
if (EnumHasAnyFlags(Results, EPrivilegeResults::AccountTypeFailure))
|
|
{
|
|
return ECommonUserPrivilegeResult::AccountTypeRestricted;
|
|
}
|
|
if (EnumHasAnyFlags(Results, EPrivilegeResults::NetworkConnectionUnavailable))
|
|
{
|
|
return ECommonUserPrivilegeResult::NetworkConnectionUnavailable;
|
|
}
|
|
|
|
// Bucket other account failures together
|
|
const EPrivilegeResults AccountUseFailures = EPrivilegeResults::OnlinePlayRestricted
|
|
| EPrivilegeResults::UGCRestriction
|
|
| EPrivilegeResults::ChatRestriction;
|
|
|
|
if (EnumHasAnyFlags(Results, AccountUseFailures))
|
|
{
|
|
return ECommonUserPrivilegeResult::AccountUseRestricted;
|
|
}
|
|
|
|
// If you can't play at all, this is a license failure
|
|
if (Privilege == EUserPrivileges::CanPlay)
|
|
{
|
|
return ECommonUserPrivilegeResult::LicenseInvalid;
|
|
}
|
|
|
|
// Unknown reason
|
|
return ECommonUserPrivilegeResult::PlatformFailure;
|
|
}
|
|
#endif // COMMONUSER_OSSV1
|
|
|
|
FString UCommonUserSubsystem::PlatformUserIdToString(FPlatformUserId UserId)
|
|
{
|
|
if (UserId == PLATFORMUSERID_NONE)
|
|
{
|
|
return TEXT("None");
|
|
}
|
|
else
|
|
{
|
|
return FString::Printf(TEXT("%d"), UserId.GetInternalId());
|
|
}
|
|
}
|
|
|
|
FString UCommonUserSubsystem::ECommonUserOnlineContextToString(ECommonUserOnlineContext Context)
|
|
{
|
|
switch (Context)
|
|
{
|
|
case ECommonUserOnlineContext::Game:
|
|
return TEXT("Game");
|
|
case ECommonUserOnlineContext::Default:
|
|
return TEXT("Default");
|
|
case ECommonUserOnlineContext::Service:
|
|
return TEXT("Service");
|
|
case ECommonUserOnlineContext::ServiceOrDefault:
|
|
return TEXT("Service/Default");
|
|
case ECommonUserOnlineContext::Platform:
|
|
return TEXT("Platform");
|
|
case ECommonUserOnlineContext::PlatformOrDefault:
|
|
return TEXT("Platform/Default");
|
|
default:
|
|
return TEXT("Invalid");
|
|
}
|
|
}
|
|
|
|
FText UCommonUserSubsystem::GetPrivilegeDescription(ECommonUserPrivilege Privilege) const
|
|
{
|
|
switch (Privilege)
|
|
{
|
|
case ECommonUserPrivilege::CanPlay:
|
|
return NSLOCTEXT("CommonUser", "PrivilegeCanPlay", "play the game");
|
|
case ECommonUserPrivilege::CanPlayOnline:
|
|
return NSLOCTEXT("CommonUser", "PrivilegeCanPlayOnline", "play online");
|
|
case ECommonUserPrivilege::CanCommunicateViaTextOnline:
|
|
return NSLOCTEXT("CommonUser", "PrivilegeCanCommunicateViaTextOnline", "communicate with text");
|
|
case ECommonUserPrivilege::CanCommunicateViaVoiceOnline:
|
|
return NSLOCTEXT("CommonUser", "PrivilegeCanCommunicateViaVoiceOnline", "communicate with voice");
|
|
case ECommonUserPrivilege::CanUseUserGeneratedContent:
|
|
return NSLOCTEXT("CommonUser", "PrivilegeCanUseUserGeneratedContent", "access user content");
|
|
case ECommonUserPrivilege::CanUseCrossPlay:
|
|
return NSLOCTEXT("CommonUser", "PrivilegeCanUseCrossPlay", "play with other platforms");
|
|
default:
|
|
return NSLOCTEXT("CommonUser", "PrivilegeInvalid", "");
|
|
}
|
|
}
|
|
|
|
FText UCommonUserSubsystem::GetPrivilegeResultDescription(ECommonUserPrivilegeResult Result) const
|
|
{
|
|
// TODO these strings might have cert requirements we need to override per console
|
|
switch (Result)
|
|
{
|
|
case ECommonUserPrivilegeResult::Unknown:
|
|
return NSLOCTEXT("CommonUser", "ResultUnknown", "Unknown if the user is allowed");
|
|
case ECommonUserPrivilegeResult::Available:
|
|
return NSLOCTEXT("CommonUser", "ResultAvailable", "The user is allowed");
|
|
case ECommonUserPrivilegeResult::UserNotLoggedIn:
|
|
return NSLOCTEXT("CommonUser", "ResultUserNotLoggedIn", "The user must login");
|
|
case ECommonUserPrivilegeResult::LicenseInvalid:
|
|
return NSLOCTEXT("CommonUser", "ResultLicenseInvalid", "A valid game license is required");
|
|
case ECommonUserPrivilegeResult::VersionOutdated:
|
|
return NSLOCTEXT("CommonUser", "VersionOutdated", "The game or hardware needs to be updated");
|
|
case ECommonUserPrivilegeResult::NetworkConnectionUnavailable:
|
|
return NSLOCTEXT("CommonUser", "ResultNetworkConnectionUnavailable", "A network connection is required");
|
|
case ECommonUserPrivilegeResult::AgeRestricted:
|
|
return NSLOCTEXT("CommonUser", "ResultAgeRestricted", "This age restricted account is not allowed");
|
|
case ECommonUserPrivilegeResult::AccountTypeRestricted:
|
|
return NSLOCTEXT("CommonUser", "ResultAccountTypeRestricted", "This account type does not have access");
|
|
case ECommonUserPrivilegeResult::AccountUseRestricted:
|
|
return NSLOCTEXT("CommonUser", "ResultAccountUseRestricted", "This account is not allowed");
|
|
case ECommonUserPrivilegeResult::PlatformFailure:
|
|
return NSLOCTEXT("CommonUser", "ResultPlatformFailure", "Not allowed");
|
|
default:
|
|
return NSLOCTEXT("CommonUser", "ResultInvalid", "");
|
|
|
|
}
|
|
}
|
|
|
|
const UCommonUserSubsystem::FOnlineContextCache* UCommonUserSubsystem::GetContextCache(ECommonUserOnlineContext Context) const
|
|
{
|
|
return const_cast<UCommonUserSubsystem*>(this)->GetContextCache(Context);
|
|
}
|
|
|
|
UCommonUserSubsystem::FOnlineContextCache* UCommonUserSubsystem::GetContextCache(ECommonUserOnlineContext Context)
|
|
{
|
|
switch (Context)
|
|
{
|
|
case ECommonUserOnlineContext::Game:
|
|
case ECommonUserOnlineContext::Default:
|
|
return DefaultContextInternal;
|
|
|
|
case ECommonUserOnlineContext::Service:
|
|
return ServiceContextInternal;
|
|
case ECommonUserOnlineContext::ServiceOrDefault:
|
|
return ServiceContextInternal ? ServiceContextInternal : DefaultContextInternal;
|
|
|
|
case ECommonUserOnlineContext::Platform:
|
|
return PlatformContextInternal;
|
|
case ECommonUserOnlineContext::PlatformOrDefault:
|
|
return PlatformContextInternal ? PlatformContextInternal : DefaultContextInternal;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
ECommonUserOnlineContext UCommonUserSubsystem::ResolveOnlineContext(ECommonUserOnlineContext Context) const
|
|
{
|
|
switch (Context)
|
|
{
|
|
case ECommonUserOnlineContext::Game:
|
|
case ECommonUserOnlineContext::Default:
|
|
return ECommonUserOnlineContext::Default;
|
|
|
|
case ECommonUserOnlineContext::Service:
|
|
return ServiceContextInternal ? ECommonUserOnlineContext::Service : ECommonUserOnlineContext::Invalid;
|
|
case ECommonUserOnlineContext::ServiceOrDefault:
|
|
return ServiceContextInternal ? ECommonUserOnlineContext::Service : ECommonUserOnlineContext::Default;
|
|
|
|
case ECommonUserOnlineContext::Platform:
|
|
return PlatformContextInternal ? ECommonUserOnlineContext::Platform : ECommonUserOnlineContext::Invalid;
|
|
case ECommonUserOnlineContext::PlatformOrDefault:
|
|
return PlatformContextInternal ? ECommonUserOnlineContext::Platform : ECommonUserOnlineContext::Default;
|
|
}
|
|
|
|
return ECommonUserOnlineContext::Invalid;
|
|
}
|
|
|
|
bool UCommonUserSubsystem::HasSeparatePlatformContext() const
|
|
{
|
|
ECommonUserOnlineContext ServiceType = ResolveOnlineContext(ECommonUserOnlineContext::ServiceOrDefault);
|
|
ECommonUserOnlineContext PlatformType = ResolveOnlineContext(ECommonUserOnlineContext::PlatformOrDefault);
|
|
|
|
if (ServiceType != PlatformType)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void UCommonUserSubsystem::SetLocalPlayerUserInfo(ULocalPlayer* LocalPlayer, const UCommonUserInfo* UserInfo)
|
|
{
|
|
if (!bIsDedicatedServer && ensure(LocalPlayer && UserInfo))
|
|
{
|
|
LocalPlayer->SetPlatformUserId(UserInfo->GetPlatformUserId());
|
|
|
|
FUniqueNetIdRepl NetId = UserInfo->GetNetId(ECommonUserOnlineContext::Game);
|
|
LocalPlayer->SetCachedUniqueNetId(NetId);
|
|
|
|
// Also update player state if possible
|
|
APlayerController* PlayerController = LocalPlayer->GetPlayerController(nullptr);
|
|
if (PlayerController && PlayerController->PlayerState)
|
|
{
|
|
PlayerController->PlayerState->SetUniqueId(NetId);
|
|
}
|
|
}
|
|
}
|
|
|
|
const UCommonUserInfo* UCommonUserSubsystem::GetUserInfoForLocalPlayerIndex(int32 LocalPlayerIndex) const
|
|
{
|
|
TObjectPtr<UCommonUserInfo> const* Found = LocalUserInfos.Find(LocalPlayerIndex);
|
|
if (Found)
|
|
{
|
|
return *Found;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const UCommonUserInfo* UCommonUserSubsystem::GetUserInfoForPlatformUserIndex(int32 PlatformUserIndex) const
|
|
{
|
|
FPlatformUserId PlatformUser = GetPlatformUserIdForIndex(PlatformUserIndex);
|
|
return GetUserInfoForPlatformUser(PlatformUser);
|
|
}
|
|
|
|
const UCommonUserInfo* UCommonUserSubsystem::GetUserInfoForPlatformUser(FPlatformUserId PlatformUser) const
|
|
{
|
|
if (!IsRealPlatformUser(PlatformUser))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
for (TPair<int32, UCommonUserInfo*> Pair : LocalUserInfos)
|
|
{
|
|
// Don't include guest users in this check
|
|
if (ensure(Pair.Value) && Pair.Value->PlatformUser == PlatformUser && !Pair.Value->bIsGuest)
|
|
{
|
|
return Pair.Value;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const UCommonUserInfo* UCommonUserSubsystem::GetUserInfoForUniqueNetId(const FUniqueNetIdRepl& NetId) const
|
|
{
|
|
if (!NetId.IsValid())
|
|
{
|
|
// TODO do we need to handle pre-login case on mobile platforms where netID is invalid?
|
|
return nullptr;
|
|
}
|
|
|
|
for (TPair<int32, UCommonUserInfo*> UserPair : LocalUserInfos)
|
|
{
|
|
if (ensure(UserPair.Value))
|
|
{
|
|
for (const TPair<ECommonUserOnlineContext, UCommonUserInfo::FCachedData>& CachedPair : UserPair.Value->CachedDataMap)
|
|
{
|
|
if (NetId == CachedPair.Value.CachedNetId)
|
|
{
|
|
return UserPair.Value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const UCommonUserInfo* UCommonUserSubsystem::GetUserInfoForControllerId(int32 ControllerId) const
|
|
{
|
|
FPlatformUserId PlatformUser;
|
|
FInputDeviceId IgnoreDevice;
|
|
|
|
IPlatformInputDeviceMapper::Get().RemapControllerIdToPlatformUserAndDevice(ControllerId, PlatformUser, IgnoreDevice);
|
|
|
|
return GetUserInfoForPlatformUser(PlatformUser);
|
|
}
|
|
|
|
const UCommonUserInfo* UCommonUserSubsystem::GetUserInfoForInputDevice(FInputDeviceId InputDevice) const
|
|
{
|
|
FPlatformUserId PlatformUser = GetPlatformUserIdForInputDevice(InputDevice);
|
|
return GetUserInfoForPlatformUser(PlatformUser);
|
|
}
|
|
|
|
bool UCommonUserSubsystem::IsRealPlatformUserIndex(int32 PlatformUserIndex) const
|
|
{
|
|
if (PlatformUserIndex < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#if COMMONUSER_OSSV1
|
|
if (PlatformUserIndex >= MAX_LOCAL_PLAYERS)
|
|
{
|
|
// Check against OSS count
|
|
return false;
|
|
}
|
|
#else
|
|
// TODO: OSSv2 define MAX_LOCAL_PLAYERS?
|
|
#endif
|
|
|
|
if (PlatformUserIndex > 0 && GetTraitTags().HasTag(FCommonUserTags::Platform_Trait_SingleOnlineUser))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UCommonUserSubsystem::IsRealPlatformUser(FPlatformUserId PlatformUser) const
|
|
{
|
|
// Validation is done at conversion/allocation time so trust the type
|
|
if (!PlatformUser.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// TODO: Validate against OSS or input mapper somehow
|
|
|
|
if (GetTraitTags().HasTag(FCommonUserTags::Platform_Trait_SingleOnlineUser))
|
|
{
|
|
// Only the default user is supports online functionality
|
|
if (PlatformUser != IPlatformInputDeviceMapper::Get().GetPrimaryPlatformUser())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
FPlatformUserId UCommonUserSubsystem::GetPlatformUserIdForIndex(int32 PlatformUserIndex) const
|
|
{
|
|
return IPlatformInputDeviceMapper::Get().GetPlatformUserForUserIndex(PlatformUserIndex);
|
|
}
|
|
|
|
int32 UCommonUserSubsystem::GetPlatformUserIndexForId(FPlatformUserId PlatformUser) const
|
|
{
|
|
return IPlatformInputDeviceMapper::Get().GetUserIndexForPlatformUser(PlatformUser);
|
|
}
|
|
|
|
FPlatformUserId UCommonUserSubsystem::GetPlatformUserIdForInputDevice(FInputDeviceId InputDevice) const
|
|
{
|
|
return IPlatformInputDeviceMapper::Get().GetUserForInputDevice(InputDevice);
|
|
}
|
|
|
|
FInputDeviceId UCommonUserSubsystem::GetPrimaryInputDeviceForPlatformUser(FPlatformUserId PlatformUser) const
|
|
{
|
|
return IPlatformInputDeviceMapper::Get().GetPrimaryInputDeviceForUser(PlatformUser);
|
|
}
|
|
|
|
void UCommonUserSubsystem::SetTraitTags(const FGameplayTagContainer& InTags)
|
|
{
|
|
CachedTraitTags = InTags;
|
|
}
|
|
|
|
bool UCommonUserSubsystem::ShouldWaitForStartInput() const
|
|
{
|
|
// By default, don't wait for input if this is a single user platform
|
|
return !HasTraitTag(FCommonUserTags::Platform_Trait_SingleOnlineUser.GetTag());
|
|
}
|
|
|
|
#if COMMONUSER_OSSV1
|
|
void UCommonUserSubsystem::HandleIdentityLoginStatusChanged(int32 PlatformUserIndex, ELoginStatus::Type OldStatus, ELoginStatus::Type NewStatus, const FUniqueNetId& NewId, ECommonUserOnlineContext Context)
|
|
{
|
|
UE_LOG(LogCommonUser, Log, TEXT("Player login status changed - System:%s, UserIdx:%d, OldStatus:%s, NewStatus:%s, NewId:%s"),
|
|
*GetOnlineSubsystemName(Context).ToString(),
|
|
PlatformUserIndex,
|
|
ELoginStatus::ToString(OldStatus),
|
|
ELoginStatus::ToString(NewStatus),
|
|
*NewId.ToString());
|
|
|
|
if (NewStatus == ELoginStatus::NotLoggedIn && OldStatus != ELoginStatus::NotLoggedIn)
|
|
{
|
|
FPlatformUserId PlatformUser = GetPlatformUserIdForIndex(PlatformUserIndex);
|
|
LogOutLocalUser(PlatformUser);
|
|
}
|
|
}
|
|
|
|
void UCommonUserSubsystem::HandleControllerPairingChanged(int32 PlatformUserIndex, FControllerPairingChangedUserInfo PreviousUser, FControllerPairingChangedUserInfo NewUser)
|
|
{
|
|
UE_LOG(LogCommonUser, Log, TEXT("Player controller pairing changed - UserIdx:%d, PreviousUser:%s, NewUser:%s"),
|
|
PlatformUserIndex,
|
|
*ToDebugString(PreviousUser),
|
|
*ToDebugString(NewUser));
|
|
|
|
UGameInstance* GameInstance = GetGameInstance();
|
|
FPlatformUserId PlatformUser = GetPlatformUserIdForIndex(PlatformUserIndex);
|
|
ULocalPlayer* ControlledLocalPlayer = GameInstance->FindLocalPlayerFromPlatformUserId(PlatformUser);
|
|
ULocalPlayer* NewLocalPlayer = GameInstance->FindLocalPlayerFromUniqueNetId(NewUser.User);
|
|
const UCommonUserInfo* NewUserInfo = GetUserInfoForUniqueNetId(FUniqueNetIdRepl(NewUser.User));
|
|
const UCommonUserInfo* PreviousUserInfo = GetUserInfoForUniqueNetId(FUniqueNetIdRepl(PreviousUser.User));
|
|
|
|
// See if we think this is already bound to an existing player
|
|
if (PreviousUser.ControllersRemaining == 0 && PreviousUserInfo && PreviousUserInfo != NewUserInfo)
|
|
{
|
|
// This means that the user deliberately logged out using a platform interface
|
|
if (IsRealPlatformUser(PlatformUser))
|
|
{
|
|
LogOutLocalUser(PlatformUser);
|
|
}
|
|
}
|
|
|
|
if (ControlledLocalPlayer && ControlledLocalPlayer != NewLocalPlayer)
|
|
{
|
|
// TODO Currently the platforms that call this delegate do not really handle swapping controller IDs
|
|
// SetLocalPlayerUserIndex(ControlledLocalPlayer, -1);
|
|
}
|
|
}
|
|
|
|
void UCommonUserSubsystem::HandleNetworkConnectionStatusChanged(const FString& ServiceName, EOnlineServerConnectionStatus::Type LastConnectionStatus, EOnlineServerConnectionStatus::Type ConnectionStatus, ECommonUserOnlineContext Context)
|
|
{
|
|
UE_LOG(LogCommonUser, Log, TEXT("HandleNetworkConnectionStatusChanged(ServiceName: %s, LastStatus: %s, ConnectionStatus: %s)"),
|
|
*ServiceName,
|
|
EOnlineServerConnectionStatus::ToString(LastConnectionStatus),
|
|
EOnlineServerConnectionStatus::ToString(ConnectionStatus));
|
|
|
|
// Cache old availablity for current users
|
|
TMap<UCommonUserInfo*, ECommonUserAvailability> AvailabilityMap;
|
|
|
|
for (TPair<int32, UCommonUserInfo*> Pair : LocalUserInfos)
|
|
{
|
|
AvailabilityMap.Add(Pair.Value, Pair.Value->GetPrivilegeAvailability(ECommonUserPrivilege::CanPlayOnline));
|
|
}
|
|
|
|
FOnlineContextCache* System = GetContextCache(Context);
|
|
if (ensure(System))
|
|
{
|
|
// Service name is normally the same as the OSS name, but not necessarily on all platforms
|
|
System->CurrentConnectionStatus = ConnectionStatus;
|
|
}
|
|
|
|
for (TPair<UCommonUserInfo*, ECommonUserAvailability> Pair : AvailabilityMap)
|
|
{
|
|
// Notify other systems when someone goes online/offline
|
|
HandleChangedAvailability(Pair.Key, ECommonUserPrivilege::CanPlayOnline, Pair.Value);
|
|
}
|
|
|
|
}
|
|
#else
|
|
void UCommonUserSubsystem::HandleAuthLoginStatusChanged(const UE::Online::FAuthLoginStatusChanged& EventParameters, ECommonUserOnlineContext Context)
|
|
{
|
|
UE_LOG(LogCommonUser, Log, TEXT("Player login status changed - System:%d, UserId:%s, NewStatus:%s"),
|
|
(int)Context,
|
|
*ToLogString(EventParameters.AccountInfo->AccountId),
|
|
LexToString(EventParameters.LoginStatus));
|
|
}
|
|
|
|
void UCommonUserSubsystem::HandleNetworkConnectionStatusChanged(const UE::Online::FConnectionStatusChanged& EventParameters, ECommonUserOnlineContext Context)
|
|
{
|
|
UE_LOG(LogCommonUser, Log, TEXT("HandleNetworkConnectionStatusChanged(Context:%d, ServiceName:%s, OldStatus:%s, NewStatus:%s)"),
|
|
(int)Context,
|
|
*EventParameters.ServiceName,
|
|
LexToString(EventParameters.PreviousStatus),
|
|
LexToString(EventParameters.CurrentStatus));
|
|
|
|
// Cache old availablity for current users
|
|
TMap<UCommonUserInfo*, ECommonUserAvailability> AvailabilityMap;
|
|
|
|
for (TPair<int32, UCommonUserInfo*> Pair : LocalUserInfos)
|
|
{
|
|
AvailabilityMap.Add(Pair.Value, Pair.Value->GetPrivilegeAvailability(ECommonUserPrivilege::CanPlayOnline));
|
|
}
|
|
|
|
FOnlineContextCache* System = GetContextCache(Context);
|
|
if (ensure(System))
|
|
{
|
|
// Service name is normally the same as the OSS name, but not necessarily on all platforms
|
|
System->CurrentConnectionStatus = EventParameters.CurrentStatus;
|
|
}
|
|
|
|
for (TPair<UCommonUserInfo*, ECommonUserAvailability> Pair : AvailabilityMap)
|
|
{
|
|
// Notify other systems when someone goes online/offline
|
|
HandleChangedAvailability(Pair.Key, ECommonUserPrivilege::CanPlayOnline, Pair.Value);
|
|
}
|
|
}
|
|
#endif // COMMONUSER_OSSV1
|
|
|
|
void UCommonUserSubsystem::HandleInputDeviceConnectionChanged(EInputDeviceConnectionState NewConnectionState, FPlatformUserId PlatformUserId, FInputDeviceId InputDeviceId)
|
|
{
|
|
FString InputDeviceIDString = FString::Printf(TEXT("%d"), InputDeviceId.GetId());
|
|
const bool bIsConnected = NewConnectionState == EInputDeviceConnectionState::Connected;
|
|
UE_LOG(LogCommonUser, Log, TEXT("Controller connection changed - UserIdx:%d, UserID:%s, Connected:%d"), *InputDeviceIDString, *PlatformUserIdToString(PlatformUserId), bIsConnected ? 1 : 0);
|
|
|
|
// TODO Implement for platforms that support this
|
|
}
|
|
|