// Copyright Epic Games, Inc. All Rights Reserved. #include "SocialDebugTools.h" #include "Interfaces/OnlineUserInterface.h" #include "Interfaces/OnlinePresenceInterface.h" #include "Misc/ScopeExit.h" #include "Online/Presence.h" #include "OnlineFramework/CommonConfig.h" #include "OnlineFramework/CommonAccount.h" #include "OnlineFramework/CommonAccountUtils.h" #include "OnlineSubsystemUtils.h" #include "Party/PartyMember.h" #include "SocialManager.h" #include "TimerManager.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(SocialDebugTools) USocialDebugTools::USocialDebugTools() : bAutoAcceptFriendInvites(true) , bAutoAcceptPartyInvites(true) { } void USocialDebugTools::Shutdown() { // shutdown OSS instances that were created for (auto& Entry : Contexts) { FInstanceContext& Context = Entry.Value; GetContext(Context.Name).Shutdown(); } Contexts.Empty(); } void USocialDebugTools::PrintExecUsage() const { UE_LOG(LogParty, Log, TEXT("Usage SOCIAL DEBUG CONTEXT= ")); } void USocialDebugTools::PrintExecCommands() const { UE_LOG(LogParty, Log, TEXT("LOGIN CONTEXT= ...>")); UE_LOG(LogParty, Log, TEXT("LOGOUT")); UE_LOG(LogParty, Log, TEXT("JOINPARTY ")); UE_LOG(LogParty, Log, TEXT("LEAVEPARTY")); UE_LOG(LogParty, Log, TEXT("AUTOACCEPTFRIENDINVITES")); UE_LOG(LogParty, Log, TEXT("AUTOACCEPTPARTYINVITES")); UE_LOG(LogParty, Log, TEXT("JIP")); UE_LOG(LogParty, Log, TEXT("HELP")); } #if UE_ALLOW_EXEC_COMMANDS bool USocialDebugTools::Exec(class UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Out) { if (FParse::Command(&Cmd, TEXT("DEBUG"))) { FString Instance; FParse::Value(Cmd, TEXT("CONTEXT="), Instance); if (FParse::Command(&Cmd, TEXT("HELP"))) { PrintExecUsage(); PrintExecCommands(); } else if (Instance.IsEmpty()) { PrintExecUsage(); } else { bool bAllInstancesRequested = false; TArray TargetInstances; // strip out context to parse next entry FParse::Command(&Cmd, *FString(TEXT("CONTEXT=") + Instance)); if(Instance.Equals(TEXT("all"))) { bAllInstancesRequested = true; GetContextNames(TargetInstances); if(TargetInstances.Num() == 0) { UE_LOG(LogParty, Log, TEXT("CONTEXT=ALL used, but no OSS contexts found!")); } } else { TargetInstances.Add(Instance); } return RunCommand(Cmd, TargetInstances); } return true; } return false; } #endif // UE_ALLOW_EXEC_COMMANDS void USocialDebugTools::Login(const FString& Instance, const FOnlineAccountCredentials& Credentials, const FLoginComplete& OnComplete) { bool bResult = false; FInstanceContext& Context = GetContext(Instance); IOnlineSubsystem* OnlineSub = Context.GetOSS(); if (OnlineSub) { IOnlineIdentityPtr OnlineIdentity = OnlineSub->GetIdentityInterface(); if (OnlineIdentity.IsValid()) { if (OnlineIdentity->GetLoginStatus(LocalUserNum) == ELoginStatus::LoggedIn) { bResult = true; } else { if (!Context.LoginCompleteDelegateHandle.IsValid()) { auto PresenceDelegateV2 = [this, Instance, OnComplete](const UE::Online::FPresenceUpdated& Event) { FInstanceContext& ContextTmp = GetContext(Instance); OnComplete.ExecuteIfBound(true); ContextTmp.OnPresenceUpdatedHandle.Unbind(); }; auto PresenceDelegate = FOnPresenceReceivedDelegate::CreateLambda([this, Instance, OnComplete](const class FUniqueNetId& LocalUserId, const TSharedRef& Presence) { FInstanceContext& ContextTmp = GetContext(Instance); OnComplete.ExecuteIfBound(true); if (ContextTmp.PresenceReceivedDelegateHandle.IsValid()) { ContextTmp.GetOSS()->GetPresenceInterface()->ClearOnPresenceReceivedDelegate_Handle(ContextTmp.PresenceReceivedDelegateHandle); ContextTmp.PresenceReceivedDelegateHandle.Reset(); } }); auto LoginDelegate = FOnLoginCompleteDelegate::CreateLambda([this, Instance, PresenceDelegate, PresenceDelegateV2, OnComplete](int32 InLocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error) { FInstanceContext& ContextTmp = GetContext(Instance); if (bWasSuccessful) { if (USocialManager::ShouldReadPresenceFromOnlineServices()) { using namespace UE::Online; using namespace UE::OnlineFramework; const FName& PresenceFrameworkInstance = USocialManager::GetPresenceFrameworkInstance(); FCommonConfig CommonConfig(this); FAccountId LocalUserAccountId, PresenceUserAccountId; if (IOnlineIdentityPtr OnlineIdentity = ContextTmp.GetOSS()->GetIdentityInterface()) { if (FUniqueNetIdPtr LocalUserNetIdPtr = OnlineIdentity->GetUniquePlayerId(InLocalUserNum)) { if (const FCommonAccountPtr LocalUserCommonAccountPtr = GetCommonAccountFromV1(CommonConfig, LocalUserNetIdPtr, GetFirstFrameworkInstanceName(LocalUserNetIdPtr))) { LocalUserAccountId = LocalUserCommonAccountPtr->GetId(PresenceFrameworkInstance); } } } if (const FCommonAccountPtr PresenceUserCommonAccountPtr = GetCommonAccountFromV1(CommonConfig, UserId.AsShared(), GetFirstFrameworkInstanceName(UserId))) { PresenceUserAccountId = PresenceUserCommonAccountPtr->GetId(PresenceFrameworkInstance); } if (LocalUserAccountId.IsValid() && PresenceUserAccountId.IsValid()) { if (IOnlineServicesPtr OnlineServices = CommonConfig.GetServices(PresenceFrameworkInstance)) { if (IPresencePtr Presence = OnlineServices->GetPresenceInterface()) { ContextTmp.OnPresenceUpdatedHandle = Presence->OnPresenceUpdated().Add(this, PresenceDelegateV2); TSharedRef UpdatedPresence = MakeShared(); UpdatedPresence->AccountId = PresenceUserAccountId; UpdatedPresence->Status = EUserPresenceStatus::Online; UpdatedPresence->StatusString = FString(TEXT("Golem:")) + Instance; FUpdatePresence::Params Params = { LocalUserAccountId, UpdatedPresence }; Presence->UpdatePresence(MoveTemp(Params)); } else { OnComplete.ExecuteIfBound(false); } } else { OnComplete.ExecuteIfBound(false); } } else { OnComplete.ExecuteIfBound(false); } } else { IOnlinePresencePtr OnlinePresence = ContextTmp.GetOSS()->GetPresenceInterface(); if (OnlinePresence.IsValid()) { ContextTmp.PresenceReceivedDelegateHandle = OnlinePresence->AddOnPresenceReceivedDelegate_Handle(PresenceDelegate); FOnlinePresenceSetPresenceParameters Status; Status.State = EOnlinePresenceState::Online; Status.StatusStr = FString(TEXT("Golem:")) + Instance; Status.Properties.Emplace(); // Intentionally empty to clear out existing fields OnlinePresence->SetPresence(UserId, MoveTemp(Status)); } else { OnComplete.ExecuteIfBound(false); } } } else { OnComplete.ExecuteIfBound(false); } if (ContextTmp.LoginCompleteDelegateHandle.IsValid()) { ContextTmp.GetOSS()->GetIdentityInterface()->ClearOnLoginCompleteDelegate_Handle(InLocalUserNum, ContextTmp.LoginCompleteDelegateHandle); ContextTmp.LoginCompleteDelegateHandle.Reset(); } }); Context.LoginCompleteDelegateHandle = OnlineIdentity->AddOnLoginCompleteDelegate_Handle(LocalUserNum, LoginDelegate); OnlineIdentity->Login(LocalUserNum, Credentials); return; } } } } OnComplete.ExecuteIfBound(bResult); } void USocialDebugTools::Logout(const FString& Instance, const FLogoutComplete& OnComplete) { bool bResult = false; FInstanceContext& Context = GetContext(Instance); IOnlineSubsystem* OnlineSub = Context.GetOSS(); if (OnlineSub) { IOnlineIdentityPtr OnlineIdentity = OnlineSub->GetIdentityInterface(); if (OnlineIdentity.IsValid()) { if (OnlineIdentity->GetLoginStatus(LocalUserNum) == ELoginStatus::NotLoggedIn) { bResult = true; } else { if (!Context.LogoutCompleteDelegateHandle.IsValid()) { auto Delegate = FOnLogoutCompleteDelegate::CreateLambda([this, Instance, OnComplete](int32 InLocalUserNum, bool bWasSuccessful) { OnComplete.ExecuteIfBound(bWasSuccessful); FInstanceContext& ContextTmp = GetContext(Instance); if (ContextTmp.LogoutCompleteDelegateHandle.IsValid()) { GetContext(Instance).GetOSS()->GetIdentityInterface()->ClearOnLogoutCompleteDelegate_Handle(InLocalUserNum, ContextTmp.LogoutCompleteDelegateHandle); ContextTmp.LogoutCompleteDelegateHandle.Reset(); } }); Context.LogoutCompleteDelegateHandle = OnlineIdentity->AddOnLogoutCompleteDelegate_Handle(LocalUserNum, Delegate); OnlineIdentity->Logout(LocalUserNum); return; } } } } OnComplete.ExecuteIfBound(bResult); } void USocialDebugTools::JoinInProgress(const FString& Instance, const FJoinInProgressComplete& OnComplete) { // Assume an error, and trigger delegate on scope exit TOptional OnError = OnComplete; ON_SCOPE_EXIT { // Execute error if (OnError) { // Error reason doesn't matter, but we're very unlikely to receive this from the JIP target OnError->ExecuteIfBound(EPartyJoinDenialReason::OssUnavailable); } }; FInstanceContext& Context = GetContext(Instance); IOnlineSubsystem* OnlineSub = Context.GetOSS(); if (!OnlineSub) { return; } FUniqueNetIdPtr LocalUserId = Context.GetLocalUserId(); if (!LocalUserId) { return; } // Find the party we're in IOnlinePartyPtr PartySystem = OnlineSub->GetPartyInterface(); if (!PartySystem) { return; } FOnlinePartyConstPtr Party = PartySystem->GetParty(*LocalUserId, IOnlinePartySystem::GetPrimaryPartyTypeId()); if (!Party) { return; } // JIP on the party leader FUniqueNetIdPtr JipTargetId = Party->LeaderId; if (!JipTargetId.IsValid()) { return; } FPartyMemberJoinInProgressRequest Request; Request.Target = JipTargetId; Request.Time = FDateTime::UtcNow().ToUnixTimestamp(); if (Context.SetJIPRequest(Request)) { OnError.Reset(); // No longer assuming an error // Wait for player to respond TSharedRef DelegateHandleRef = MakeShared(); TSharedRef TimerHandleRef = MakeShared(); auto CleanupJipRequest = [this, Instance, PartySystem = PartySystem.ToWeakPtr(), OnComplete, DelegateHandleRef, TimerHandleRef](EPartyJoinDenialReason Result) { // Clear our JIP request FInstanceContext& LocalContext = GetContext(Instance); LocalContext.SetJIPRequest(FPartyMemberJoinInProgressRequest()); OnComplete.ExecuteIfBound(Result); // Copy lambda captures as they will be deleted through the unbinds here TSharedRef DelegateHandleCopy = DelegateHandleRef; TSharedRef TimerHandleCopy = TimerHandleRef; IOnlinePartyPtr StrongPartySystem = PartySystem.Pin(); UWorld* World = GetWorld(); if (StrongPartySystem) { StrongPartySystem->ClearOnPartyMemberDataReceivedDelegate_Handle(*DelegateHandleCopy); } if (World) { World->GetTimerManager().ClearTimer(*TimerHandleCopy); } }; auto OnPartyMemberDataReceivedLambda = [this, LocalUserId, JipTargetId, Request, CleanupJipRequest](const FUniqueNetId& InLocalUserId, const FOnlinePartyId& InPartyId, const FUniqueNetId& InMemberId, const FName& InNamespace, const FOnlinePartyData& InPartyMemberData) { if (InMemberId == *JipTargetId) { FPartyMemberRepData ReceivedPartyMemberRepData; if (FVariantDataConverter::VariantMapToUStruct(InPartyMemberData.GetKeyValAttrs(), FPartyMemberRepData::StaticStruct(), &ReceivedPartyMemberRepData, 0, CPF_Transient | CPF_RepSkip)) { for (const FPartyMemberJoinInProgressResponse& Response : ReceivedPartyMemberRepData.GetJoinInProgressDataResponses()) { if (Response.RequestTime != Request.Time || Response.Requester != LocalUserId) { // Response was not for us. continue; } CleanupJipRequest(static_cast(Response.DenialReason)); return; } } } }; *DelegateHandleRef = PartySystem->AddOnPartyMemberDataReceivedDelegate_Handle(FOnPartyMemberDataReceivedDelegate::CreateWeakLambda(this, OnPartyMemberDataReceivedLambda)); // wait 60 seconds for a response if (UWorld* World = GetWorld()) { World->GetTimerManager().SetTimer(*TimerHandleRef, FTimerDelegate::CreateWeakLambda(this, CleanupJipRequest, static_cast(EPartyJoinDenialReason::OssUnavailable)), 60, false); } } } void USocialDebugTools::JoinParty(const FString& Instance, const FString& FriendName, const FJoinPartyComplete& OnComplete) { FInstanceContext& Context = GetContext(Instance); IOnlineSubsystem* OnlineSub = Context.GetOSS(); if (OnlineSub) { if (FUniqueNetIdPtr LocalUserId = Context.GetLocalUserId()) { IOnlinePartyPtr OnlineParty = OnlineSub->GetPartyInterface(); if (OnlineParty.IsValid()) { // query user id by name if (!FriendName.IsEmpty()) { IOnlineUserPtr OnlineUser = OnlineSub->GetUserInterface(); if (OnlineUser.IsValid()) { OnlineUser->QueryUserIdMapping(*LocalUserId, FriendName, IOnlineUser::FOnQueryUserMappingComplete::CreateLambda([this, Instance, OnlineParty, OnComplete](bool bWasSuccessful, const FUniqueNetId& UserId, const FString& DisplayNameOrEmail, const FUniqueNetId& FoundUserId, const FString& Error) { if (bWasSuccessful) { IOnlinePartyJoinInfoConstPtr JoinInfo = OnlineParty->GetAdvertisedParty(UserId, FoundUserId, IOnlinePartySystem::GetPrimaryPartyTypeId()); if (JoinInfo.IsValid()) { OnlineParty->JoinParty(UserId, *JoinInfo, FOnJoinPartyComplete::CreateLambda([this, Instance, OnlineParty, OnComplete](const FUniqueNetId& UserIdTmp, const FOnlinePartyId& PartyId, const EJoinPartyCompletionResult Result, const int32 NotApprovedReason) { bool bSuccess = Result == EJoinPartyCompletionResult::Succeeded; if (bSuccess) { FOnlinePartyDataConstPtr PartyMemberData = GetContext(Instance).GetPartyMemberData(); if (PartyMemberData.IsValid()) { OnlineParty->UpdatePartyMemberData(UserIdTmp, PartyId, DefaultPartyDataNamespace, *PartyMemberData); } } else { UE_LOG(LogParty, Warning, TEXT("Party context[%s] join attempt denied for reason [%d]"), *Instance, (int32)Result); } OnComplete.ExecuteIfBound(bSuccess); })); return; } } OnComplete.ExecuteIfBound(false); })); return; } } else { IOnlinePartyJoinInfoConstPtr JoinInfo = GetDefaultPartyJoinInfo(); if (JoinInfo.IsValid()) { OnlineParty->JoinParty(*LocalUserId, *JoinInfo, FOnJoinPartyComplete::CreateLambda([this, Instance, OnlineParty, OnComplete](const FUniqueNetId& UserId, const FOnlinePartyId& PartyId, const EJoinPartyCompletionResult Result, const int32 NotApprovedReason) { bool bSuccess = Result == EJoinPartyCompletionResult::Succeeded; if (bSuccess) { FOnlinePartyDataConstPtr PartyMemberData = GetContext(Instance).GetPartyMemberData(); if (PartyMemberData.IsValid()) { OnlineParty->UpdatePartyMemberData(UserId, PartyId, DefaultPartyDataNamespace, *PartyMemberData); } } else { UE_LOG(LogParty, Warning, TEXT("Party context[%s] join attempt denied for reason [%d]"), *Instance, (int32)Result); } OnComplete.ExecuteIfBound(bSuccess); })); return; } } } } } OnComplete.ExecuteIfBound(false); } void USocialDebugTools::LeaveParty(const FString& Instance, const FLeavePartyComplete& OnComplete) { FInstanceContext& Context = GetContext(Instance); IOnlineSubsystem* OnlineSub = GetContext(Instance).GetOSS(); if (OnlineSub) { if (FUniqueNetIdPtr UserId = Context.GetLocalUserId()) { IOnlinePartyPtr OnlineParty = OnlineSub->GetPartyInterface(); if (OnlineParty.IsValid()) { TSharedPtr ExistingParty = OnlineParty->GetParty(*UserId, IOnlinePartySystem::GetPrimaryPartyTypeId()); if (ExistingParty.IsValid()) { OnlineParty->LeaveParty(*UserId, *ExistingParty->PartyId, true, FOnLeavePartyComplete::CreateLambda([this, Instance, OnComplete](const FUniqueNetId& LocalUserId, const FOnlinePartyId& PartyId, const ELeavePartyCompletionResult Result) { OnComplete.ExecuteIfBound(Result == ELeavePartyCompletionResult::Succeeded); })); return; } } } } OnComplete.ExecuteIfBound(false); } void USocialDebugTools::CleanupParties(const FString& Instance, const FCleanupPartiesComplete& OnComplete) { FInstanceContext& Context = GetContext(Instance); IOnlineSubsystem* OnlineSub = Context.GetOSS(); if (OnlineSub) { if (FUniqueNetIdPtr UserId = Context.GetLocalUserId()) { IOnlinePartyPtr OnlineParty = OnlineSub->GetPartyInterface(); if (OnlineParty.IsValid()) { OnlineParty->CleanupParties(*UserId, FOnCleanupPartiesComplete::CreateLambda([this, Instance, OnComplete](const FUniqueNetId& LocalUserId, const FOnlineError& Result) { OnComplete.ExecuteIfBound(Result.WasSuccessful()); })); return; } } } OnComplete.ExecuteIfBound(false); } void USocialDebugTools::SetPartyMemberData(const FString& Instance, const UStruct* StructType, const void* StructData, const FSetPartyMemberDataComplete& OnComplete) { check(StructType); check(StructData); bool bResult = false; FInstanceContext& Context = GetContext(Instance); // parse from struct to party data FOnlinePartyData OnlinePartyData; if (FVariantDataConverter::UStructToVariantMap(StructType, StructData, OnlinePartyData.GetKeyValAttrs(), 0, CPF_Transient | CPF_RepSkip)) { if (!Context.PartyMemberData.IsValid()) { Context.PartyMemberData = MakeShared(); } // cache party data on the context so it can be sent whenever a party is joined *Context.PartyMemberData = OnlinePartyData; // send the party data if connected to a party IOnlineSubsystem* OnlineSub = Context.GetOSS(); if (OnlineSub) { if (FUniqueNetIdPtr UserId = Context.GetLocalUserId()) { IOnlinePartyPtr OnlineParty = OnlineSub->GetPartyInterface(); if (OnlineParty.IsValid()) { TSharedPtr ExistingParty = OnlineParty->GetParty(*UserId, IOnlinePartySystem::GetPrimaryPartyTypeId()); if (ExistingParty.IsValid()) { const FOnlinePartyId& PartyId = *ExistingParty->PartyId; if (StructType->IsChildOf(FPartyMemberRepData::StaticStruct())) { UE_LOG(LogParty, Display, TEXT("Sending rep data update for member within party [%s]."), *PartyId.ToDebugString()); bResult = OnlineParty->UpdatePartyMemberData(*UserId, PartyId, DefaultPartyDataNamespace, *Context.PartyMemberData); } } } } } } OnComplete.ExecuteIfBound(bResult); } void USocialDebugTools::SetPartyMemberDataJson(const FString& Instance, const FString& JsonStr, const FSetPartyMemberDataComplete& OnComplete) { bool bResult = false; FInstanceContext& Context = GetContext(Instance); // parse from struct to party data FOnlinePartyData OnlinePartyData; OnlinePartyData.FromJson(JsonStr); if (OnlinePartyData.GetKeyValAttrs().Num() > 0) { if (!Context.PartyMemberData.IsValid()) { Context.PartyMemberData = MakeShared(); } // cache party data on the context so it can be sent whenever a party is joined *Context.PartyMemberData = OnlinePartyData; // send the party data if connected to a party IOnlineSubsystem* OnlineSub = Context.GetOSS(); if (OnlineSub) { if (FUniqueNetIdPtr UserId = Context.GetLocalUserId()) { IOnlinePartyPtr OnlineParty = OnlineSub->GetPartyInterface(); if (OnlineParty.IsValid()) { TSharedPtr ExistingParty = OnlineParty->GetParty(*UserId, IOnlinePartySystem::GetPrimaryPartyTypeId()); if (ExistingParty.IsValid()) { const FOnlinePartyId& PartyId = *ExistingParty->PartyId; UE_LOG(LogParty, Display, TEXT("Sending rep data update for member within party [%s]."), *PartyId.ToDebugString()); bResult = OnlineParty->UpdatePartyMemberData(*UserId, PartyId, DefaultPartyDataNamespace, *Context.PartyMemberData); } } } } } else { UE_LOG(LogParty, Warning, TEXT("SetPartyMemberDataJson didnt parse data from JsonStr=[%s]."), *JsonStr); } OnComplete.ExecuteIfBound(bResult); } IOnlineSubsystem* USocialDebugTools::GetDefaultOSS() const { if (UWorld* World = GetWorld()) { return Online::GetSubsystem(World, MCP_SUBSYSTEM); } else { return IOnlineSubsystem::Get(MCP_SUBSYSTEM); } } USocialManager& USocialDebugTools::GetSocialManager() const { USocialManager* OuterSocialManager = GetTypedOuter(); check(OuterSocialManager); return *OuterSocialManager; } IOnlinePartyJoinInfoConstPtr USocialDebugTools::GetDefaultPartyJoinInfo() const { IOnlinePartyJoinInfoConstPtr Result; IOnlineSubsystem* OnlineSub = GetDefaultOSS(); if (OnlineSub) { IOnlineIdentityPtr OnlineIdentity = OnlineSub->GetIdentityInterface(); if (OnlineIdentity.IsValid()) { FUniqueNetIdPtr UserId = OnlineIdentity->GetUniquePlayerId(LocalUserNum); if (UserId.IsValid()) { IOnlinePartyPtr OnlineParty = OnlineSub->GetPartyInterface(); if (OnlineParty.IsValid()) { Result = OnlineParty->GetAdvertisedParty(*UserId, *UserId, IOnlinePartySystem::GetPrimaryPartyTypeId()); if (!Result.IsValid()) { TSharedPtr ExistingParty = OnlineParty->GetParty(*UserId, IOnlinePartySystem::GetPrimaryPartyTypeId()); if (ExistingParty.IsValid()) { FString JsonJoinInfo = OnlineParty->MakeJoinInfoJson(*UserId, *ExistingParty->PartyId); Result = OnlineParty->MakeJoinInfoFromJson(JsonJoinInfo); } } } } } } return Result; } USocialDebugTools::FInstanceContext& USocialDebugTools::GetContext(const FString& Instance) { FInstanceContext* Context = Contexts.Find(Instance); if (!Context) { Context = &Contexts.Add(Instance, FInstanceContext(Instance, *this)); Context->Init(); NotifyContextInitialized(*Context); } return *Context; } USocialDebugTools::FInstanceContext* USocialDebugTools::GetContextForUser(const FUniqueNetId& UserId) { FInstanceContext* Result = nullptr; for (TPair& Entry : Contexts) { FInstanceContext& Context = Entry.Value; if (Context.GetOSS()) { if (FUniqueNetIdPtr LocalUserId = Context.GetLocalUserId()) { if (*LocalUserId == UserId) { Result = &Context; break; } } } } return Result; } bool USocialDebugTools::RunCommand(const TCHAR* Cmd, const TArray& TargetInstances) { if (FParse::Command(&Cmd, TEXT("LOGIN"))) { if (TargetInstances.Num() > 1) { UE_LOG(LogParty, Log, TEXT("CONTEXT=ALL cannot be used for the LOGIN command!")); } else { FString InstanceName = TargetInstances[0]; FString Id = FParse::Token(Cmd, false); FString Auth = FParse::Token(Cmd, false); int MaxConcurrentLogins = 512; // Reasonable limit to prevent infinite loop while (!Id.IsEmpty() && !Auth.IsEmpty() && MaxConcurrentLogins >= 0) { Login(InstanceName, FOnlineAccountCredentials(TEXT("epic"), Id, Auth), FLoginComplete::CreateLambda([this, InstanceName](bool bSuccess) { UE_LOG(LogParty, Display, TEXT("Login OSS context[%s] %s"), *InstanceName, *LexToString(bSuccess)); })); // Allow for more logins by concatenating more context=name auth pass if (FParse::Value(Cmd, TEXT("CONTEXT="), InstanceName)) { // strip out context to parse next entry FParse::Command(&Cmd, *FString(TEXT("CONTEXT=") + InstanceName)); Id = FParse::Token(Cmd, false); Auth = FParse::Token(Cmd, false); --MaxConcurrentLogins; } else { break; } } } return true; } else if (FParse::Command(&Cmd, TEXT("LOGOUT"))) { for (const FString& TargetInstance : TargetInstances) { Logout(TargetInstance, FLogoutComplete::CreateLambda([this, TargetInstance](bool bSuccess) { UE_LOG(LogParty, Display, TEXT("Logout OSS context[%s] %s"), *TargetInstance, *LexToString(bSuccess)); })); } return true; } else if (FParse::Command(&Cmd, TEXT("JOINPARTY"))) { const FString Id = FParse::Token(Cmd, false); const FString Auth = FParse::Token(Cmd, false); const FString FriendName = FParse::Token(Cmd, false); for (const FString& TargetInstance : TargetInstances) { Login(TargetInstance, FOnlineAccountCredentials(TEXT("epic"), Id, Auth), FLoginComplete::CreateLambda([this, TargetInstance, FriendName](bool bSuccess) { UE_LOG(LogParty, Display, TEXT("Login OSS context[%s] %s"), *TargetInstance, *LexToString(bSuccess)); if (bSuccess) { CleanupParties(TargetInstance, FCleanupPartiesComplete::CreateLambda([this, TargetInstance, FriendName](bool bCleanupPartiesSuccess) { LeaveParty(TargetInstance, FLeavePartyComplete::CreateLambda([this, TargetInstance, FriendName](bool bLeavePartySuccess) { UE_LOG(LogParty, Display, TEXT("Leave party OSS context[%s] %s"), *TargetInstance, *LexToString(bLeavePartySuccess)); JoinParty(TargetInstance, FriendName, FJoinPartyComplete::CreateLambda([this, TargetInstance](bool bJoinPartySuccess) { UE_LOG(LogParty, Display, TEXT("Join party OSS context[%s] %s"), *TargetInstance, *LexToString(bJoinPartySuccess)); })); })); })); } })); } return true; } else if (FParse::Command(&Cmd, TEXT("JIP"))) { for (const FString& TargetInstance : TargetInstances) { JoinInProgress(TargetInstance, FJoinInProgressComplete::CreateLambda([this, TargetInstance](EPartyJoinDenialReason Result) { UE_LOG(LogParty, Display, TEXT("JIP OSS context[%s] %s"), *TargetInstance, ToString(Result)); })); } return true; } else if (FParse::Command(&Cmd, TEXT("LEAVEPARTY"))) { for (const FString& TargetInstance : TargetInstances) { LeaveParty(TargetInstance, FLeavePartyComplete::CreateLambda([this, TargetInstance](bool bSuccess) { UE_LOG(LogParty, Display, TEXT("Leave party OSS context[%s] %s"), *TargetInstance, *LexToString(bSuccess)); })); } return true; } else if (FParse::Command(&Cmd, TEXT("AUTOACCEPTFRIENDINVITES"))) { const FString Enabled = FParse::Token(Cmd, false); if (Enabled == TEXT("true") || Enabled == TEXT("1")) { bAutoAcceptFriendInvites = true; } else if (Enabled == TEXT("false") || Enabled == TEXT("0")) { bAutoAcceptFriendInvites = false; } return true; } else if (FParse::Command(&Cmd, TEXT("AUTOACCEPTPARTYINVITES"))) { const FString Enabled = FParse::Token(Cmd, false); if (Enabled == TEXT("true") || Enabled == TEXT("1")) { bAutoAcceptPartyInvites = true; } else if (Enabled == TEXT("false") || Enabled == TEXT("0")) { bAutoAcceptPartyInvites = false; } return true; } else if (FParse::Command(&Cmd, TEXT("DUMPPARTY"))) { for (const FString& TargetInstance : TargetInstances) { UE_LOG(LogParty, Display, TEXT("---DUMPPARTY - party OSS context[%s]"), *TargetInstance); IOnlineSubsystem* OnlineSub = GetContext(TargetInstance).GetOSS(); if (OnlineSub && OnlineSub->GetPartyInterface().IsValid()) { OnlineSub->GetPartyInterface()->DumpPartyState(); } } return true; } return false; } void USocialDebugTools::FInstanceContext::Init() { const FString OSSName = FName(MCP_SUBSYSTEM).ToString() + TEXT(":") + Name; OnlineSub = IOnlineSubsystem::Get(*OSSName); if (OnlineSub) { // register delegates IOnlineFriendsPtr OnlineFriends = OnlineSub->GetFriendsInterface(); if (OnlineFriends.IsValid()) { FriendInviteReceivedDelegateHandle = OnlineFriends->AddOnInviteReceivedDelegate_Handle(FOnInviteReceivedDelegate::CreateUObject(&Owner, &USocialDebugTools::HandleFriendInviteReceived)); } IOnlinePartyPtr OnlineParty = OnlineSub->GetPartyInterface(); if (OnlineParty.IsValid()) { PartyInviteReceivedDelegateHandle = OnlineParty->AddOnPartyInviteReceivedExDelegate_Handle(FOnPartyInviteReceivedExDelegate::CreateUObject(&Owner, &USocialDebugTools::HandlePartyInviteReceived)); PartyJoinRequestReceivedDelegateHandle = OnlineParty->AddOnPartyJoinRequestReceivedDelegate_Handle(FOnPartyJoinRequestReceivedDelegate::CreateUObject(&Owner, &USocialDebugTools::HandlePartyJoinRequestReceived)); } } } void USocialDebugTools::FInstanceContext::Shutdown() { if (OnlineSub) { // unregister delegates IOnlineFriendsPtr OnlineFriends = OnlineSub->GetFriendsInterface(); if (OnlineFriends.IsValid()) { OnlineFriends->ClearOnInviteReceivedDelegate_Handle(FriendInviteReceivedDelegateHandle); OnlineFriends.Reset(); } IOnlinePartyPtr OnlineParty = OnlineSub->GetPartyInterface(); if (OnlineParty.IsValid()) { OnlineParty->ClearOnPartyInviteReceivedExDelegate_Handle(PartyInviteReceivedDelegateHandle); OnlineParty->ClearOnPartyJoinRequestReceivedDelegate_Handle(PartyJoinRequestReceivedDelegateHandle); OnlineParty.Reset(); } OnlineSub->Shutdown(); const FString OSSName = FName(MCP_SUBSYSTEM).ToString() + TEXT(":") + Name; IOnlineSubsystem::Destroy(*OSSName); } } FUniqueNetIdPtr USocialDebugTools::FInstanceContext::GetLocalUserId() const { FUniqueNetIdPtr LocalUserId; if (OnlineSub) { IOnlineIdentityPtr OnlineIdentity = OnlineSub->GetIdentityInterface(); if (OnlineIdentity.IsValid()) { LocalUserId = OnlineIdentity->GetUniquePlayerId(LocalUserNum); } } return LocalUserId; } void USocialDebugTools::FInstanceContext::ModifyPartyField(const FString& FieldName, const FVariantData& FieldValue) { if (!OnlineSub) { return; } IOnlinePartyPtr PartySystem = OnlineSub->GetPartyInterface(); if (!PartySystem) { return; } FUniqueNetIdPtr LocalUserId = GetLocalUserId(); if (!LocalUserId) { return; } TSharedPtr Party = PartySystem->GetParty(*LocalUserId, IOnlinePartySystem::GetPrimaryPartyTypeId()); if (!Party) { return; } // Get current party data and update the JIP field if (!PartyMemberData) { if (FOnlinePartyDataConstPtr ExistingPartyMemberData = PartySystem->GetPartyMemberData(*LocalUserId, *Party->PartyId, *LocalUserId, DefaultPartyDataNamespace)) { PartyMemberData = MakeShared(*ExistingPartyMemberData); } } if (!PartyMemberData) { PartyMemberData = MakeShared(); } PartyMemberData->SetAttribute(FieldName, FieldValue); PartySystem->UpdatePartyMemberData(*LocalUserId, *Party->PartyId, DefaultPartyDataNamespace , *PartyMemberData); } bool USocialDebugTools::FInstanceContext::SetJIPRequest(const FPartyMemberJoinInProgressRequest& InRequest) { FPartyMemberRepData PartyMemberRepData; PartyMemberRepData.MarkOwnerless(); PartyMemberRepData.SetJoinInProgressDataRequest(InRequest); FOnlinePartyData OnlinePartyData; const FString JipFieldName(TEXT("JoinInProgressData")); if (FVariantDataConverter::UStructToVariantMap(FPartyMemberRepData::StaticStruct(), &PartyMemberRepData, OnlinePartyData.GetKeyValAttrs(), 0, CPF_Transient | CPF_RepSkip) && ensure(OnlinePartyData.GetKeyValAttrs().Contains(JipFieldName))) { ModifyPartyField(JipFieldName, OnlinePartyData.GetKeyValAttrs().FindChecked(JipFieldName)); return true; } return false; } void USocialDebugTools::HandleFriendInviteReceived(const FUniqueNetId& LocalUserId, const FUniqueNetId& FriendId) { if (bAutoAcceptFriendInvites) { FInstanceContext* Context = GetContextForUser(LocalUserId); if (Context) { IOnlineFriendsPtr OnlineFriends = Context->GetOSS()->GetFriendsInterface(); if (OnlineFriends.IsValid()) { OnlineFriends->AcceptInvite(LocalUserNum, FriendId, EFriendsLists::ToString(EFriendsLists::Default)); } } } } void USocialDebugTools::HandlePartyInviteReceived(const FUniqueNetId& LocalUserId, const IOnlinePartyJoinInfo& Invitation) { if (bAutoAcceptPartyInvites) { FInstanceContext* Context = GetContextForUser(LocalUserId); if (Context) { IOnlinePartyPtr OnlineParty = Context->GetOSS()->GetPartyInterface(); if (OnlineParty.IsValid()) { const FString Instance = Context->Name; OnlineParty->JoinParty(LocalUserId, Invitation, FOnJoinPartyComplete::CreateLambda([this, Instance, OnlineParty](const FUniqueNetId& UserId, const FOnlinePartyId& PartyIdTmp, const EJoinPartyCompletionResult Result, const int32 NotApprovedReason) { bool bSuccess = Result == EJoinPartyCompletionResult::Succeeded; if (bSuccess) { FOnlinePartyDataConstPtr PartyMemberData = GetContext(Instance).GetPartyMemberData(); if (PartyMemberData.IsValid()) { OnlineParty->UpdatePartyMemberData(UserId, PartyIdTmp, DefaultPartyDataNamespace, *PartyMemberData); } } else { UE_LOG(LogParty, Warning, TEXT("Party context[%s] invite join attempt denied for reason [%d]"), *Instance, (int32)Result); } })); } } } } void USocialDebugTools::HandlePartyJoinRequestReceived(const FUniqueNetId& LocalUserId, const FOnlinePartyId& PartyId, const IOnlinePartyPendingJoinRequestInfo& JoinRequestInfo) { FInstanceContext* Context = GetContextForUser(LocalUserId); if (Context) { IOnlinePartyPtr OnlineParty = Context->GetOSS()->GetPartyInterface(); if (OnlineParty.IsValid()) { TArray JoiningUsers; JoinRequestInfo.GetUsers(JoiningUsers); check(JoiningUsers.IsValidIndex(0)); OnlineParty->ApproveJoinRequest(LocalUserId, PartyId, *JoiningUsers[0]->GetUserId(), true); return; } } }