// Copyright Epic Games, Inc. All Rights Reserved. #include "SocialManager.h" #include "Engine/GameInstance.h" #include "Engine/GameViewportClient.h" #include "Interactions/SocialInteractionMacros.h" #include "SocialToolkit.h" #include "Online/OnlineSessionNames.h" #include "SocialDebugTools.h" #include "Interactions/CoreInteractions.h" #include "Interactions/PartyInteractions.h" #include "OnlineSessionSettings.h" #include "Party/SocialParty.h" #include "Party/PartyMember.h" #include "Party/PartyPlatformSessionMonitor.h" #include "OnlineSubsystemUtils.h" #include "Engine/LocalPlayer.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(SocialManager) #if PARTY_PLATFORM_SESSIONS_XBL #include "Misc/Base64.h" #endif #if !UE_BUILD_SHIPPING static TAutoConsoleVariable CVarForceDisconnectedToPartyService( TEXT("SocialUI.ForceDisconnectedToPartyService"), false, TEXT("Overrides the bIsConnectedToPartyService state")); #endif namespace UE::OnlineFramework::CVars { bool bAssignPartyJoinInfoOnPartyJoinFailure = true; static FAutoConsoleVariableRef CVarAssignPartyJoinInfoOnPartyJoinFailure( TEXT("Party.AssignPartyJoinInfoOnPartyJoinFailure"), bAssignPartyJoinInfoOnPartyJoinFailure, TEXT("Temporary cvar (remove after proven stable), whether to set the party join info on the join attempt when ValidateJoinTarget fails"), ECVF_Default); } ////////////////////////////////////////////////////////////////////////// // FJoinPartyAttempt ////////////////////////////////////////////////////////////////////////// USocialManager::FJoinPartyAttempt::FJoinPartyAttempt(const USocialUser* InTargetUser, const FOnlinePartyTypeId& InPartyTypeId, const FName& InJoinMethod, const FOnJoinPartyAttemptComplete& InOnJoinComplete) : TargetUser(InTargetUser) , PartyTypeId(InPartyTypeId) , JoinMethod(InJoinMethod) , OnJoinComplete(InOnJoinComplete) {} FString USocialManager::FJoinPartyAttempt::ToDebugString() const { return FString::Printf(TEXT("TargetUser(%s), PartyId(%s), TypeId(%d), TargetUserPlatformId(%s), JoinMethod(%s)"), TargetUser.IsValid() ? *TargetUser->ToDebugString() : TEXT("invalid"), JoinInfo.IsValid() ? *JoinInfo->GetPartyId()->ToDebugString() : TEXT("unknown"), PartyTypeId.GetValue(), *TargetUserPlatformId.ToDebugString(), *JoinMethod.ToString()); } const FName USocialManager::FJoinPartyAttempt::Step_FindPlatformSession = TEXT("FindPlatformSession"); const FName USocialManager::FJoinPartyAttempt::Step_QueryJoinability = TEXT("QueryJoinability"); const FName USocialManager::FJoinPartyAttempt::Step_LeaveCurrentParty = TEXT("LeaveCurrentParty"); const FName USocialManager::FJoinPartyAttempt::Step_JoinParty = TEXT("JoinParty"); const FName USocialManager::FJoinPartyAttempt::Step_DeferredPartyCreation = TEXT("DeferredPartyCreation"); const FName USocialManager::FJoinPartyAttempt::Step_WaitForPersistentPartyCreation = TEXT("WaitForPersistentPartyCreation"); ////////////////////////////////////////////////////////////////////////// // USocialManager ////////////////////////////////////////////////////////////////////////// TArray USocialManager::DefaultSubsystems; TArray USocialManager::RegisteredInteractions; /*static*/bool USocialManager::IsSocialSubsystemEnabled(ESocialSubsystem SubsystemType) { return GetSocialOss(nullptr, SubsystemType) != nullptr; } /*static*/FName USocialManager::GetSocialOssName(ESocialSubsystem SubsystemType) { if (IOnlineSubsystem* SocialOSS = GetSocialOss(nullptr, SubsystemType)) { return SocialOSS->GetSubsystemName(); } return NAME_None; } /*static*/ FText USocialManager::GetSocialOssPlatformName(ESocialSubsystem SubsystemType) { if (IOnlineSubsystem* SocialOSS = GetSocialOss(nullptr, SubsystemType)) { return SocialOSS->GetSocialPlatformName(); } return FText::GetEmpty(); } /*static*/IOnlineSubsystem* USocialManager::GetSocialOss(UWorld* World, ESocialSubsystem SubsystemType) { if (SubsystemType == ESocialSubsystem::Primary) { IOnlineSubsystem* PrimaryOss = Online::GetSubsystem(World); if (PrimaryOss && !PrimaryOss->GetSubsystemName().IsEqual(NULL_SUBSYSTEM)) { return PrimaryOss; } } else if (SubsystemType == ESocialSubsystem::Platform) { if (IOnlineSubsystem::IsEnabled(TENCENT_SUBSYSTEM)) { return Online::GetSubsystem(World, TENCENT_SUBSYSTEM); } /*else if (IOnlineSubsystem::IsEnabled(STEAM_SUBSYSTEM)) { return Online::GetSubsystem(World, STEAM_SUBSYSTEM); }*/ else { return IOnlineSubsystem::GetByPlatform(); } } return nullptr; } FUserPlatform USocialManager::GetLocalUserPlatform() { return IOnlineSubsystem::GetLocalPlatformName(); } USocialManager::USocialManager() : ToolkitClass(USocialToolkit::StaticClass()) { if (!IsTemplate()) { if (DefaultSubsystems.Num() == 0) { //@todo DanH social: This module assumes there is a primary (aka mcp) OSS available that other accounts are linked to. Consider whether we want to support platform-only situations with this module #future if (IsSocialSubsystemEnabled(ESocialSubsystem::Primary)) { DefaultSubsystems.Add(ESocialSubsystem::Primary); if (IsSocialSubsystemEnabled(ESocialSubsystem::Platform)) { DefaultSubsystems.Add(ESocialSubsystem::Platform); } } } } } void USocialManager::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector) { Super::AddReferencedObjects(InThis, Collector); USocialManager& This = *CastChecked(InThis); Collector.AddReferencedObjects(This.JoinedPartiesByTypeId); Collector.AddReferencedObjects(This.LeavingPartiesByTypeId); } void USocialManager::InitSocialManager() { if (RegisteredInteractions.Num() == 0) { RegisterSocialInteractions(); } if (FPartyPlatformSessionManager::DoesOssNeedPartySession(GetSocialOssName(ESocialSubsystem::Platform))) { // We're on a platform that requires a platform session backing each party, so spin up the manager to take care of that PartySessionManager = FPartyPlatformSessionManager::Create(*this); } UGameInstance& GameInstance = GetGameInstance(); GameInstance.OnNotifyPreClientTravel().AddUObject(this, &USocialManager::HandlePreClientTravel); if (GameInstance.GetGameViewportClient()) { HandleGameViewportInitialized(); } else { UGameViewportClient::OnViewportCreated().AddUObject(this, &USocialManager::HandleGameViewportInitialized); } //@todo DanH Sessions: So it's only at the FortOnlineSessionClient level that the console session interface is used #required // Technically I could have the platform session manager just listen to the platform session interface itself for invites, but then I'm sure we miss out on functionality // Having this happen only at the Fort level though is a travesty, we NEED to know when a platform session invite has been accepted /*if (UOnlineSession* OnlineSession = GameInstance.GetOnlineSession()) { OnlineSession->OnSessionInviteAccepted().BindUObject(this, &UFortSocialManager::ProcessConsoleInvite); }*/ // Because multiclient PIE, we need to have a world to be able to access the appropriate OSS suite FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(this, &USocialManager::HandleWorldEstablished); if (UWorld* World = GetWorld()) { HandleWorldEstablished(World); } #if !UE_SERVER && !UE_BUILD_SHIPPING SocialDebugTools = NewObject(this, GetSocialDebugToolsClass()); check(SocialDebugTools); #endif } void USocialManager::ShutdownSocialManager() { bCanCreatePartyObjects = false; JoinAttemptsByTypeId.Reset(); // Mark all parties and members pending kill to prevent any callbacks from being triggered on them during shutdown const auto ShutdownPartiesFunc = [this] (TMap>& PartiesByTypeId) { for (auto& TypeIdPartyPair : PartiesByTypeId) { for (UPartyMember* PartyMember : TypeIdPartyPair.Value->GetPartyMembers()) { PartyMember->MarkAsGarbage(); } TypeIdPartyPair.Value->MarkAsGarbage(); } PartiesByTypeId.Reset(); }; ShutdownPartiesFunc(JoinedPartiesByTypeId); ShutdownPartiesFunc(LeavingPartiesByTypeId); if (SocialDebugTools) { SocialDebugTools->Shutdown(); SocialDebugTools = nullptr; } // We could have outstanding OSS queries and requests, and we are no longer interested in getting any callbacks triggered bShutdownPending = true; } USocialToolkit& USocialManager::GetSocialToolkit(const ULocalPlayer& LocalPlayer) const { USocialToolkit* FoundToolkit = nullptr; for (USocialToolkit* Toolkit : SocialToolkits) { if (&LocalPlayer == Toolkit->GetOwningLocalPlayerPtr()) { FoundToolkit = Toolkit; break; } } checkf(FoundToolkit, TEXT("No SocialToolkit exists for LocalPlayer [%s]. Should be impossible. Was the LocalPlayer created correctly via UGameInstance::CreateLocalPlayer?"), *LocalPlayer.GetName()); return *FoundToolkit; } USocialToolkit* USocialManager::GetSocialToolkit(int32 LocalPlayerNum) const { for (USocialToolkit* Toolkit : SocialToolkits) { if (Toolkit->GetLocalUserNum() == LocalPlayerNum) { return Toolkit; } } return nullptr; } USocialToolkit* USocialManager::GetSocialToolkit(FUniqueNetIdRepl LocalUserId) const { for (USocialToolkit* Toolkit : SocialToolkits) { const ULocalPlayer* LocalPlayer = Toolkit->GetOwningLocalPlayerPtr(); if (LocalPlayer && LocalPlayer->GetPreferredUniqueNetId() == LocalUserId) { return Toolkit; } } return nullptr; } TSubclassOf USocialManager::GetSocialDebugToolsClass() const { return USocialDebugTools::StaticClass(); } USocialToolkit* USocialManager::GetFirstLocalUserToolkit() const { if (SocialToolkits.Num() > 0) { return SocialToolkits[0]; } return nullptr; } FUniqueNetIdRepl USocialManager::GetFirstLocalUserId(ESocialSubsystem SubsystemType) const { if (SocialToolkits.Num() > 0) { return SocialToolkits[0]->GetLocalUserNetId(SubsystemType); } return FUniqueNetIdRepl(); } bool USocialManager::IsLocalUser(const FUniqueNetIdRepl& LocalUserId, ESocialSubsystem SubsystemType) const { for(USocialToolkit* SocialToolkit : SocialToolkits) { if (SocialToolkit->GetLocalUserNetId(SubsystemType) == LocalUserId) { return true; } } return false; } int32 USocialManager::GetFirstLocalUserNum() const { if (SocialToolkits.Num() > 0) { return SocialToolkits[0]->GetLocalUserNum(); } return 0; } void USocialManager::CreateParty(const FOnlinePartyTypeId& PartyTypeId, const FPartyConfiguration& PartyConfig, const FOnCreatePartyAttemptComplete& OnCreatePartyComplete) { if (const USocialParty* ExistingParty = GetPartyInternal(PartyTypeId, true)) { UE_LOG(LogParty, Warning, TEXT("Existing party [%s] of type [%d] found when trying to create a new one. Cannot create new one until the existing one has been left."), *ExistingParty->GetPartyId().ToDebugString(), PartyTypeId.GetValue()); OnCreatePartyComplete.ExecuteIfBound(ECreatePartyCompletionResult::AlreadyInPartyOfSpecifiedType); } else { // Only the primary local player can create parties (which secondary players will auto-join) FUniqueNetIdRepl PrimaryLocalUserId = GetFirstLocalUserId(ESocialSubsystem::Primary); IOnlinePartyPtr PartyInterface = Online::GetPartyInterface(GetWorld()); if (PartyInterface.IsValid() && PrimaryLocalUserId.IsValid()) { PartyInterface->CreateParty(*PrimaryLocalUserId, PartyTypeId, PartyConfig, FOnCreatePartyComplete::CreateUObject(this, &USocialManager::HandleCreatePartyComplete, PartyTypeId, OnCreatePartyComplete)); } else { UE_LOG(LogParty, Warning, TEXT("Cannot create party of type [%d] - PartyInterface.IsValid=%s PrimaryLocalUserId.IsValid=%s primary OSS [%s]"), PartyTypeId.GetValue(), *LexToString(PartyInterface.IsValid()), *LexToString(PrimaryLocalUserId.IsValid()), *GetSocialOssName(ESocialSubsystem::Primary).ToString()); OnCreatePartyComplete.ExecuteIfBound(ECreatePartyCompletionResult::UnknownClientFailure); } } } void USocialManager::CreatePersistentParty(const FOnCreatePartyAttemptComplete& OnCreatePartyComplete) { UE_LOG(LogParty, Log, TEXT("Attempting to create new persistent party")); // If the player tries to join a party while persistent party creation is in progress, the persistent party // should be left when creation completes before attempting to handle the join attempt. bCreatingPersistentParty = true; // The persistent party starts off closed by default, and will update its config as desired after initializing FPartyConfiguration InitialPersistentPartyConfig; InitialPersistentPartyConfig.JoinRequestAction = EJoinRequestAction::Manual; InitialPersistentPartyConfig.bIsAcceptingMembers = false; InitialPersistentPartyConfig.bShouldRemoveOnDisconnection = true; InitialPersistentPartyConfig.PresencePermissions = PartySystemPermissions::EPermissionType::Noone; InitialPersistentPartyConfig.InvitePermissions = PartySystemPermissions::EPermissionType::Noone; InitialPersistentPartyConfig.MaxMembers = USocialSettings::GetDefaultMaxPartySize(); CreateParty(IOnlinePartySystem::GetPrimaryPartyTypeId(), InitialPersistentPartyConfig, FOnCreatePartyAttemptComplete::CreateUObject(this, &USocialManager::OnCreatePersistentPartyCompleteInternal, OnCreatePartyComplete)); } void USocialManager::OnCreatePersistentPartyCompleteInternal(ECreatePartyCompletionResult Result, FOnCreatePartyAttemptComplete OnCreatePartyComplete) { ABORT_DURING_SHUTDOWN(); bCreatingPersistentParty = false; // Notify completion before possibly tearing down the party for a join attempt. OnCreatePartyComplete.ExecuteIfBound(Result); // Leave persistent party if a join attempt was made to a different persistent party while creation was underway. // OnPartyLeft will handle joining the other persistent party. if (FJoinPartyAttempt* JoinAttempt = JoinAttemptsByTypeId.Find(IOnlinePartySystem::GetPrimaryPartyTypeId())) { JoinAttempt->ActionTimeTracker.CompleteStep(FJoinPartyAttempt::Step_WaitForPersistentPartyCreation); if (Result == ECreatePartyCompletionResult::Succeeded) { if (USocialParty* ExistingParty = GetPartyInternal(IOnlinePartySystem::GetPrimaryPartyTypeId(), true)) { // Another party of the same type exists - leave that one first JoinAttempt->ActionTimeTracker.BeginStep(FJoinPartyAttempt::Step_LeaveCurrentParty); if (!ExistingParty->IsCurrentlyLeaving()) { ExistingParty->LeaveParty(USocialParty::FOnLeavePartyAttemptComplete::CreateUObject(this, &USocialManager::HandleLeavePartyForJoinComplete, ExistingParty)); } } else { // This case shouldn't really be possible if the create succeeded - attempt to join. JoinPartyInternal(*JoinAttempt); } } else { // Party creation failed, attempt to join. JoinPartyInternal(*JoinAttempt); } } } void USocialManager::RegisterSocialInteractions() { // Register Party Interactions RegisterInteraction(); RegisterInteraction(); RegisterInteraction(); RegisterInteraction(); RegisterInteraction(); RegisterInteraction(); RegisterInteraction(); RegisterInteraction(); RegisterInteraction(); RegisterInteraction(); // Register Core interactions RegisterInteraction(); RegisterInteraction(); RegisterInteraction(); RegisterInteraction(); RegisterInteraction(); RegisterInteraction(); RegisterInteraction(); RegisterInteraction(); RegisterInteraction(); } FJoinPartyResult USocialManager::ValidateJoinAttempt(const FOnlinePartyTypeId& PartyTypeId) const { UE_LOG(LogParty, VeryVerbose, TEXT("Validating join attempt of party of type [%d]"), PartyTypeId.GetValue()); FJoinPartyResult ValidationResult; IOnlinePartyPtr PartyInterface = Online::GetPartyInterface(GetWorld()); if (!PartyInterface.IsValid()) { return FPartyJoinDenialReason(EPartyJoinDenialReason::OssUnavailable); } else if (JoinAttemptsByTypeId.Contains(PartyTypeId)) { //@todo DanH Party: Is this ok? Or should we mark the existing attempt as something we should bail asap and restart the process with the new target? #suggested // We'll need to track join attempts by party ID if that's the case and just be diligent about making sure that only 1 of the same party type is actually live at a time. return EJoinPartyCompletionResult::AlreadyJoiningParty; } else if (!GetPartyClassForType(PartyTypeId)) { return FPartyJoinDenialReason(EPartyJoinDenialReason::MissingPartyClassForTypeId); } return ValidationResult; } FJoinPartyResult USocialManager::ValidateJoinTarget(const USocialUser& UserToJoin, const FOnlinePartyTypeId& PartyTypeId) const { return ValidateJoinTarget(UserToJoin, PartyTypeId, /*bCheckPlatformSession*/true); } bool USocialManager::ShouldReadPresenceFromOnlineServices() { if (IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("onlineframework.party.shouldreadpresencefromonlineservices"))) { return CVar->GetBool(); } return false; } FName USocialManager::GetPresenceFrameworkInstance() { return GetDefault()->PresenceFrameworkInstance; } FName USocialManager::GetPresenceFrameworkAdapterInstance() { return GetDefault()->PresenceFrameworkAdapterInstance; } FJoinPartyResult USocialManager::ValidateJoinTarget(const USocialUser& UserToJoin, const FOnlinePartyTypeId& PartyTypeId, bool bCheckPlatformSession) const { UE_LOG(LogParty, VeryVerbose, TEXT("Validating user [%s] as join target of party type [%d]"), *UserToJoin.ToDebugString(), PartyTypeId.GetValue()); FJoinPartyResult PartyTypeValidation = ValidateJoinAttempt(PartyTypeId); if (!PartyTypeValidation.WasSuccessful()) { // Don't bother checking the user for info if we can't even join anyway return PartyTypeValidation; } else if (!UserToJoin.GetOwningToolkit().IsOwnerLoggedIn()) { return FPartyJoinDenialReason(EPartyJoinDenialReason::NotLoggedIn); } else if ((ShouldReadPresenceFromOnlineServices() ? UserToJoin.GetOnlineStatusV2() == UE::Online::EUserPresenceStatus::Away : UserToJoin.GetOnlineStatus() == EOnlinePresenceState::Away) && // Away indicates they do not want to be joined (!CanAcceptInvitationsFromAwayUsers() || !UserToJoin.HasSentPartyInvite(PartyTypeId))) // They can still send you invitations while away, should we ignore the Away status if we've received an invite? { return FPartyJoinDenialReason(EPartyJoinDenialReason::TargetUserAway); } else if (UserToJoin.GetPartyMember(PartyTypeId)) { return EJoinPartyCompletionResult::AlreadyInParty; } else if (UserToJoin.IsBlocked()) { return FPartyJoinDenialReason(EPartyJoinDenialReason::TargetUserBlocked); } else { TSharedPtr JoinInfo = UserToJoin.GetPartyJoinInfo(PartyTypeId); if (JoinInfo.IsValid()) { if (!JoinInfo->IsValid()) { return EJoinPartyCompletionResult::JoinInfoInvalid; } else if (!JoinInfo->IsAcceptingMembers()) { FPartyJoinDenialReason DenialReason = JoinInfo->GetNotAcceptingReason(); if (DenialReason.GetReason() != EPartyJoinDenialReason::PartyPrivate || !UserToJoin.HasSentPartyInvite(PartyTypeId)) { return DenialReason; } } } else if (UserToJoin.IsFriend(ESocialSubsystem::Platform)) { ECrossplayPreference Preference = GetCrossplayPreference(); if (UserToJoin.GetCurrentPlatform().IsCrossplayWithLocalPlatform() && OptedOutOfCrossplay(Preference)) { return FPartyJoinDenialReason(EPartyJoinDenialReason::JoinerCrossplayRestricted); } if (const FOnlineUserPresence* PlatformPresence = UserToJoin.GetFriendPresenceInfo(ESocialSubsystem::Platform)) { if (!PlatformPresence->bIsPlayingThisGame) { return FPartyJoinDenialReason(EPartyJoinDenialReason::TargetUserPlayingDifferentGame); } else if (!UserToJoin.HasSentPartyInvite(PartyTypeId)) { if (!PlatformPresence->bIsJoinable) { return FPartyJoinDenialReason(EPartyJoinDenialReason::TargetUserUnjoinable); } else if (!PlatformPresence->SessionId.IsValid() || !PlatformPresence->SessionId->IsValid()) { if (bCheckPlatformSession) { return FPartyJoinDenialReason(EPartyJoinDenialReason::TargetUserMissingPlatformSession); } else { return FPartyJoinDenialReason(EPartyJoinDenialReason::PartyPrivate); } } } } else { return FPartyJoinDenialReason(EPartyJoinDenialReason::TargetUserMissingPresence); } } else if (!UserToJoin.IsPlayingThisGame()) { return FPartyJoinDenialReason(EPartyJoinDenialReason::TargetUserPlayingDifferentGame); } else { // We've got no info on this party for the given user, so it's gotta be private (or doesn't even exist) return FPartyJoinDenialReason(EPartyJoinDenialReason::PartyPrivate); } } return FJoinPartyResult(); } bool USocialManager::CanAcceptInvitationsFromAwayUsers() const { return bAcceptInvitationsFromAwayUsers; } void USocialManager::JoinParty(const USocialUser& UserToJoin, const FOnlinePartyTypeId& PartyTypeId, const FOnJoinPartyAttemptComplete& OnJoinPartyComplete, const FName& JoinMethod) { UE_LOG(LogParty, Verbose, TEXT("Attempting to join user [%s]'s party of type [%d] by [%s]"), *UserToJoin.ToDebugString(), PartyTypeId.GetValue(), *JoinMethod.ToString()); FJoinPartyAttempt NewAttempt(&UserToJoin, PartyTypeId, JoinMethod, OnJoinPartyComplete); NewAttempt.AnalyticsContext = UserToJoin.GetAnalyticsContext(); const FJoinPartyResult ValidationResult = ValidateJoinTarget(UserToJoin, PartyTypeId, /*bCheckPlatformSession*/true); if (ValidationResult.WasSuccessful()) { FJoinPartyAttempt& JoinAttempt = JoinAttemptsByTypeId.Add(PartyTypeId, NewAttempt); JoinAttempt.JoinInfo = UserToJoin.GetPartyJoinInfo(PartyTypeId); if (JoinAttempt.JoinInfo.IsValid()) { QueryPartyJoinabilityInternal(JoinAttempt); } else { JoinAttempt.ActionTimeTracker.BeginStep(FJoinPartyAttempt::Step_FindPlatformSession); PartySessionManager->FindSession(UserToJoin, FPartyPlatformSessionManager::FOnFindSessionAttemptComplete::CreateUObject(this, &USocialManager::HandleFindSessionForJoinComplete, PartyTypeId)); } } else { if (UE::OnlineFramework::CVars::bAssignPartyJoinInfoOnPartyJoinFailure) { NewAttempt.JoinInfo = UserToJoin.GetPartyJoinInfo(PartyTypeId); } // We don't do the standard FinishJoinAttempt here because this entry isn't actually in our map of join attempts yet // It's possible that this attempt failed immediately because a join is already in progress, in which case we don't want to replace the legitimate attempt with the same ID OnJoinPartyAttemptCompleteInternal(NewAttempt, ValidationResult); NewAttempt.OnJoinComplete.ExecuteIfBound(ValidationResult); } } void USocialManager::NotifyPartyInitialized(USocialParty& Party) { //only make the outside modules aware of party after initialization is complete OnPartyJoined().Broadcast(Party); } bool USocialManager::IsPartyJoinInProgress(const FOnlinePartyTypeId& TypeId) const { return JoinAttemptsByTypeId.Contains(TypeId); } bool USocialManager::IsPersistentPartyJoinInProgress() const { return IsPartyJoinInProgress(IOnlinePartySystem::GetPrimaryPartyTypeId()); } void USocialManager::FillOutJoinRequestData(const FOnlinePartyId& TargetParty, FOnlinePartyData& OutJoinRequestData) const { ECrossplayPreference Preference = GetCrossplayPreference(); if (Preference != ECrossplayPreference::NoSelection) { FVariantData CrossplayPreferenceVal; CrossplayPreferenceVal.SetValue((int32)Preference); OutJoinRequestData.SetAttribute(TEXT("CrossplayPreference"), CrossplayPreferenceVal); } } TSubclassOf USocialManager::GetPartyClassForType(const FOnlinePartyTypeId& PartyTypeId) const { return USocialParty::StaticClass(); } void USocialManager::OnJoinPartyAttemptCompleteInternal(const FJoinPartyAttempt& JoinAttemptInfo, const FJoinPartyResult& Result) { UE_LOG(LogParty, Verbose, TEXT("JoinPartyAttempt [%s] completed with result [%s] and reason [%s]"), *JoinAttemptInfo.ToDebugString(), ToString(Result.GetResult()), ToString(Result.GetDenialReason().GetReason())); } void USocialManager::OnToolkitCreatedInternal(USocialToolkit& NewToolkit) { OnSocialToolkitCreated().Broadcast(NewToolkit); } bool USocialManager::CanCreateNewPartyObjects() const { // At the root level, we just want to be sure that we have a world before spinning up party UObjects return GetWorld() != nullptr; } ECrossplayPreference USocialManager::GetCrossplayPreference() const { return ECrossplayPreference::NoSelection; } void USocialManager::RefreshCanCreatePartyObjects() { const bool bCanNowCreate = CanCreateNewPartyObjects(); if (bCanNowCreate != bCanCreatePartyObjects) { bCanCreatePartyObjects = bCanNowCreate; if (bCanNowCreate && JoinAttemptsByTypeId.Num() > 0) { // We'll potentially be removing map entries mid-loop, just work with a copy auto JoinAttemptsCopy = JoinAttemptsByTypeId; for (auto& TypeIdJoinAttemptPair : JoinAttemptsCopy) { FJoinPartyAttempt& JoinAttempt = TypeIdJoinAttemptPair.Value; if (JoinAttempt.ActionTimeTracker.GetCurrentStepName() == FJoinPartyAttempt::Step_DeferredPartyCreation && ensure(JoinAttempt.JoinInfo.IsValid())) { JoinAttempt.ActionTimeTracker.CompleteStep(FJoinPartyAttempt::Step_DeferredPartyCreation); FJoinPartyResult JoinResult = EJoinPartyCompletionResult::Succeeded; if (!EstablishNewParty(*GetFirstLocalUserId(ESocialSubsystem::Primary), *JoinAttempt.JoinInfo->GetPartyId(), JoinAttempt.JoinInfo->GetPartyTypeId())) { JoinResult = EJoinPartyCompletionResult::UnknownClientFailure; } FinishJoinPartyAttempt(JoinAttempt, JoinResult); } } } } } UGameInstance& USocialManager::GetGameInstance() const { return *GetTypedOuter(); } USocialToolkit& USocialManager::CreateSocialToolkit(ULocalPlayer& OwningLocalPlayer, int32 LocalPlayerIndex) { for (USocialToolkit* ExistingToolkit : SocialToolkits) { check(&OwningLocalPlayer != ExistingToolkit->GetOwningLocalPlayerPtr()); } check(ToolkitClass); USocialToolkit* NewToolkit = NewObject(this, ToolkitClass); SocialToolkits.Insert(NewToolkit, LocalPlayerIndex); NewToolkit->InitializeToolkit(OwningLocalPlayer); OnToolkitCreatedInternal(*NewToolkit); NewToolkit->OnToolkitReset().AddUObject(this, &USocialManager::HandleToolkitReset, NewToolkit->GetLocalUserNum()); return *NewToolkit; } void USocialManager::RegisterSecondaryPlayer(int32 LocalPlayerNum, const FOnJoinPartyComplete& JoinDelegate) { USocialToolkit* SocialToolkit = GetSocialToolkit(LocalPlayerNum); IOnlinePartyPtr PartyInterface = Online::GetPartyInterfaceChecked(GetWorld()); USocialParty* PersistentParty = GetPersistentParty(); if (PartyInterface != nullptr && PersistentParty != nullptr) { FUniqueNetIdRepl PrimaryUserId = GetFirstLocalUserId(ESocialSubsystem::Primary); FUniqueNetIdRepl SecondaryUserId = SocialToolkit->GetLocalUser().GetUserId(ESocialSubsystem::Primary); if (PrimaryUserId.IsValid() && SecondaryUserId.IsValid()) { // If P2 is already in the party, leave first so we can join cleanly if (PersistentParty->GetPartyMember(SecondaryUserId)) { PersistentParty->RemoveLocalMember(SecondaryUserId, USocialParty::FOnLeavePartyAttemptComplete::CreateWeakLambda(this, [this, LocalPlayerNum, JoinDelegate](ELeavePartyCompletionResult LeaveResult) { if (LeaveResult == ELeavePartyCompletionResult::Succeeded) { RegisterSecondaryPlayer(LocalPlayerNum, JoinDelegate); } else { UE_LOG(LogParty, Warning, TEXT("RegisterSecondaryPlayer RemoveLocalMember failed LeaveResult=%s"), ToString(LeaveResult)); USocialToolkit* LambdaSocialToolkit = GetSocialToolkit(LocalPlayerNum); USocialParty* LambdaPersistentParty = GetPersistentParty(); FUniqueNetIdRepl LambdaSecondaryUserId = LambdaSocialToolkit->GetLocalUser().GetUserId(ESocialSubsystem::Primary); JoinDelegate.Execute(*LambdaSecondaryUserId, LambdaPersistentParty->GetPartyId(), EJoinPartyCompletionResult::UnknownClientFailure, 0); } })); } else { FString JoinInfoStr = PartyInterface->MakeJoinInfoJson(*PrimaryUserId, PersistentParty->GetPartyId()); IOnlinePartyJoinInfoConstPtr JoinInfo = PartyInterface->MakeJoinInfoFromJson(JoinInfoStr); if (JoinInfo && JoinInfo->IsValid()) { PartyInterface->JoinParty(*SecondaryUserId, *JoinInfo, JoinDelegate); } } } } } void USocialManager::QueryPartyJoinabilityInternal(FJoinPartyAttempt& JoinAttempt) { FUniqueNetIdRepl LocalUserId = GetFirstLocalUserId(ESocialSubsystem::Primary); if (ensure(LocalUserId.IsValid()) && ensure(JoinAttempt.JoinInfo.IsValid())) { JoinAttempt.ActionTimeTracker.BeginStep(FJoinPartyAttempt::Step_QueryJoinability); IOnlinePartyPtr PartyInterface = Online::GetPartyInterfaceChecked(GetWorld()); PartyInterface->QueryPartyJoinability(*LocalUserId, *JoinAttempt.JoinInfo, FOnQueryPartyJoinabilityCompleteEx::CreateUObject(this, &USocialManager::HandleQueryJoinabilityComplete, JoinAttempt.JoinInfo->GetPartyTypeId())); } else { FinishJoinPartyAttempt(JoinAttempt, FJoinPartyResult(EJoinPartyCompletionResult::UnknownClientFailure)); } } void USocialManager::JoinPartyInternal(FJoinPartyAttempt& JoinAttempt) { IOnlinePartyPtr PartyInterface = Online::GetPartyInterfaceChecked(GetWorld()); FUniqueNetIdRepl LocalUserId = GetFirstLocalUserId(ESocialSubsystem::Primary); if (LocalUserId.IsValid()) { JoinAttempt.ActionTimeTracker.BeginStep(FJoinPartyAttempt::Step_JoinParty); PartyInterface->JoinParty(*LocalUserId, *JoinAttempt.JoinInfo, FOnJoinPartyComplete::CreateUObject(this, &USocialManager::HandleJoinPartyComplete, JoinAttempt.JoinInfo->GetPartyTypeId())); } else { UE_LOG(LogParty, Error, TEXT("USocialManager::JoinPartyInternal Invalid LocalUserId=[%s] on primary subsystem."), *LocalUserId.ToDebugString()); } } void USocialManager::FinishJoinPartyAttempt(FJoinPartyAttempt& JoinAttemptToDestroy, const FJoinPartyResult& JoinResult) { OnJoinPartyAttemptCompleteInternal(JoinAttemptToDestroy, JoinResult); JoinAttemptToDestroy.OnJoinComplete.ExecuteIfBound(JoinResult); const bool bWasPersistentPartyJoinAttempt = JoinAttemptToDestroy.PartyTypeId == IOnlinePartySystem::GetPrimaryPartyTypeId(); // JoinAttemptToDestroy is garbage after this. Be careful! JoinAttemptsByTypeId.Remove(JoinAttemptToDestroy.PartyTypeId); if (bWasPersistentPartyJoinAttempt && !JoinResult.WasSuccessful() && !GetPersistentParty()) { // Something goofed when trying to join a new persistent party, so create a replacement immediately // TODO: See if the below warning happens, and if so, flesh out this flow UE_CLOG(!GetFirstLocalUserToolkit()->CanAutoRecreatePersistentParty(), LogParty, Warning, TEXT("FinishJoinPartyAttempt creating a persistent party when we shouldn't!")); CreatePersistentParty(); } } USocialParty* USocialManager::GetPersistentPartyInternal(bool bEvenIfLeaving /*= false*/) const { auto* PersistentParty = JoinedPartiesByTypeId.Find(IOnlinePartySystem::GetPrimaryPartyTypeId()); if (PersistentParty && ensure(*PersistentParty) && (bEvenIfLeaving || !(*PersistentParty)->IsLeavingParty())) { return *PersistentParty; } return nullptr; } const USocialManager::FJoinPartyAttempt* USocialManager::GetJoinAttemptInProgress(const FOnlinePartyTypeId& PartyTypeId) const { return JoinAttemptsByTypeId.Find(PartyTypeId); } USocialParty* USocialManager::EstablishNewParty(const FUniqueNetId& LocalUserId, const FOnlinePartyId& PartyId, const FOnlinePartyTypeId& PartyTypeId) { QUICK_SCOPE_CYCLE_COUNTER(STAT_SocialManager_EstablishNewParty); IOnlinePartyPtr PartyInterface = Online::GetPartyInterfaceChecked(GetWorld()); TSubclassOf PartyClass = GetPartyClassForType(PartyTypeId); TSharedPtr OssParty = PartyInterface->GetParty(LocalUserId, PartyTypeId); if (ensure(OssParty.IsValid()) && ensure(PartyClass) && ensure(*OssParty->PartyId == PartyId)) { USocialParty* NewParty = NewObject(this, *PartyClass); NewParty->OnPartyLeaveBegin().AddUObject(this, &USocialManager::HandlePartyLeaveBegin, NewParty); NewParty->OnPartyLeft().AddUObject(this, &USocialManager::HandlePartyLeft, NewParty); NewParty->OnPartyDisconnected().AddUObject(this, &USocialManager::HandlePartyDisconnected, NewParty); // This must be done before InitializeParty(), as initialization can complete synchronously. JoinedPartiesByTypeId.Add(PartyTypeId, NewParty); { QUICK_SCOPE_CYCLE_COUNTER(STAT_SocialManager_EstablishNewParty_InitializeParty); NewParty->InitializeParty(OssParty.ToSharedRef()); } if (NewParty->IsPersistentParty()) { NewParty->OnPartyStateChanged().AddUObject(this, &USocialManager::HandlePersistentPartyStateChanged, NewParty); HandlePersistentPartyStateChanged(NewParty->GetOssPartyState(), NewParty->GetOssPartyPreviousState(), NewParty); } return NewParty; } return nullptr; } USocialParty* USocialManager::GetPartyInternal(const FOnlinePartyTypeId& PartyTypeId, bool bIncludeLeavingParties) const { auto* Party = JoinedPartiesByTypeId.Find(PartyTypeId); if (!Party && bIncludeLeavingParties) { Party = LeavingPartiesByTypeId.Find(PartyTypeId); } return Party ? *Party : nullptr; } USocialParty* USocialManager::GetPartyInternal(const FOnlinePartyId& PartyId, bool bIncludeLeavingParties) const { for (auto& TypeIdPartyPair : JoinedPartiesByTypeId) { if (TypeIdPartyPair.Value->GetPartyId() == PartyId) { return TypeIdPartyPair.Value; } } if (bIncludeLeavingParties) { for (auto& TypeIdPartyPair : LeavingPartiesByTypeId) { if (TypeIdPartyPair.Value->GetPartyId() == PartyId) { return TypeIdPartyPair.Value; } } } return nullptr; } #if PARTY_PLATFORM_SESSIONS_XBL extern TAutoConsoleVariable CVarXboxMpaEnabled; #endif TSharedPtr USocialManager::GetJoinInfoFromSession(const FOnlineSessionSearchResult& PlatformSession) { static const FName JoinInfoSettingName = PARTY_PLATFORM_SESSIONS_XBL ? SETTING_CUSTOM_JOIN_INFO : SETTING_CUSTOM; FString JoinInfoString; if (PlatformSession.Session.SessionSettings.Get(JoinInfoSettingName, JoinInfoString)) { IOnlinePartyPtr PartyInterface = Online::GetPartyInterfaceChecked(GetWorld()); #if PARTY_PLATFORM_SESSIONS_XBL if (CVarXboxMpaEnabled.GetValueOnAnyThread()) { return PartyInterface->MakeJoinInfoFromToken(JoinInfoString); } // On Xbox MPSD we encode our party data in base64 to avoid XboxLive trying to parse our json, so now we need to decode that FBase64::Decode(JoinInfoString, JoinInfoString); #endif return PartyInterface->MakeJoinInfoFromJson(JoinInfoString); } return nullptr; } void USocialManager::HandleGameViewportInitialized() { ABORT_DURING_SHUTDOWN(); UGameViewportClient::OnViewportCreated().RemoveAll(this); UGameInstance& GameInstance = GetGameInstance(); UGameViewportClient* GameViewport = GameInstance.GetGameViewportClient(); check(GameViewport); GameViewport->OnPlayerAdded().AddUObject(this, &USocialManager::HandleLocalPlayerAdded); GameViewport->OnPlayerRemoved().AddUObject(this, &USocialManager::HandleLocalPlayerRemoved); // Immediately spin up toolkits for local players that already exist const TArray& LocalPlayers = GameInstance.GetLocalPlayers(); for (int32 LocalPlayerIndex = 0; LocalPlayerIndex < LocalPlayers.Num(); ++LocalPlayerIndex) { ULocalPlayer* ExistingLocalPlayer = LocalPlayers[LocalPlayerIndex]; CreateSocialToolkit(*ExistingLocalPlayer, LocalPlayerIndex); } } void USocialManager::HandlePreClientTravel(const FString& PendingURL, ETravelType TravelType, bool bIsSeamlessTravel) { ABORT_DURING_SHUTDOWN(); RefreshCanCreatePartyObjects(); } void USocialManager::HandleWorldEstablished(UWorld* World) { ABORT_DURING_SHUTDOWN(); RefreshCanCreatePartyObjects(); if (!OnFillJoinRequestInfoHandle.IsValid()) { IOnlinePartyPtr PartyInterface = Online::GetPartyInterface(World); if (PartyInterface.IsValid()) { OnFillJoinRequestInfoHandle = PartyInterface->AddOnFillPartyJoinRequestDataDelegate_Handle(FOnFillPartyJoinRequestDataDelegate::CreateUObject(this, &USocialManager::HandleFillPartyJoinRequestData)); } } } void USocialManager::HandleLocalPlayerAdded(int32 LocalUserNum) { ABORT_DURING_SHUTDOWN(); ULocalPlayer* NewLocalPlayer = GetGameInstance().GetLocalPlayerByIndex(LocalUserNum); check(NewLocalPlayer); CreateSocialToolkit(*NewLocalPlayer, LocalUserNum); } void USocialManager::HandleLocalPlayerRemoved(int32 LocalUserNum) { ABORT_DURING_SHUTDOWN(); //GetSocialToolkit accepts a ControllerId, not a player index, so we'll access it directly if (SocialToolkits.IsValidIndex(LocalUserNum)) { if (USocialToolkit* Toolkit = SocialToolkits[LocalUserNum]) { SocialToolkits.Remove(Toolkit); OnSocialToolkitDestroyed().Broadcast(*Toolkit); Toolkit->MarkAsGarbage(); } } } void USocialManager::HandleToolkitReset(int32 LocalUserNum) { ABORT_DURING_SHUTDOWN(); JoinAttemptsByTypeId.Reset(); } void USocialManager::RestorePartyStateFromPartySystem(const FOnRestorePartyStateFromPartySystemComplete& OnRestoreComplete) { UE_LOG(LogParty, Verbose, TEXT("RestorePartyStateFromPartySystem")); FUniqueNetIdRepl LocalUserId = GetFirstLocalUserId(ESocialSubsystem::Primary); // If the player has any parties, do not try to restore if (LocalUserId.IsValid() && bCanCreatePartyObjects && JoinedPartiesByTypeId.Num() == 0 && JoinAttemptsByTypeId.Num() == 0) { IOnlinePartyPtr PartyInterface = Online::GetPartyInterfaceChecked(GetWorld()); PartyInterface->RestoreParties(*LocalUserId, FOnRestorePartiesComplete::CreateUObject(this, &USocialManager::OnRestorePartiesComplete, OnRestoreComplete)); } else { OnRestoreComplete.ExecuteIfBound(false); } } void USocialManager::OnRestorePartiesComplete(const FUniqueNetId& LocalUserId, const FOnlineError& Result, const FOnRestorePartyStateFromPartySystemComplete OnRestoreComplete) { ABORT_DURING_SHUTDOWN(); if (Result.WasSuccessful()) { // Restore our parties IOnlinePartyPtr PartyInterface = Online::GetPartyInterfaceChecked(GetWorld()); TArray> JoinedParties; PartyInterface->GetJoinedParties(LocalUserId, JoinedParties); for (const TSharedRef& PartyId : JoinedParties) { TSharedPtr Party = PartyInterface->GetParty(LocalUserId, *PartyId); check(Party.IsValid()); if (!EstablishNewParty(LocalUserId, *PartyId, Party->PartyTypeId)) { UE_LOG(LogParty, Warning, TEXT("OnRestorePartiesComplete: User=[%s] Party=[%s] Type=%d failed to establish party"), *LocalUserId.ToDebugString(), *PartyId->ToDebugString(), Party->PartyTypeId.GetValue()); } } } OnRestoreComplete.ExecuteIfBound(Result.WasSuccessful()); } void USocialManager::HandleQueryJoinabilityComplete(const FUniqueNetId& LocalUserId, const FOnlinePartyId& PartyId, const FQueryPartyJoinabilityResult& Result, FOnlinePartyTypeId PartyTypeId) { ABORT_DURING_SHUTDOWN(); if (FJoinPartyAttempt* JoinAttempt = JoinAttemptsByTypeId.Find(PartyTypeId)) { if (Result.EnumResult == EJoinPartyCompletionResult::Succeeded) { if (USocialParty* ExistingParty = GetPartyInternal(PartyTypeId, true)) { // We're currently in another party of the same type, so we have to leave that one first JoinAttempt->ActionTimeTracker.BeginStep(FJoinPartyAttempt::Step_LeaveCurrentParty); if (!ExistingParty->IsCurrentlyLeaving()) { ExistingParty->LeaveParty(USocialParty::FOnLeavePartyAttemptComplete::CreateUObject(this, &USocialManager::HandleLeavePartyForJoinComplete, ExistingParty)); } } else { if (bCreatingPersistentParty && PartyTypeId == IOnlinePartySystem::GetPrimaryPartyTypeId()) { UE_LOG(LogParty, Log, TEXT("HandleQueryJoinabilityComplete: User=[%s] Party=[%s] Delaying join until persistent party creation has completed."), *LocalUserId.ToDebugString(), *PartyId.ToDebugString()); // When the internal persistent party creation completes, the new party will be left to process the join request. JoinAttempt->ActionTimeTracker.BeginStep(FJoinPartyAttempt::Step_WaitForPersistentPartyCreation); } else { JoinPartyInternal(*JoinAttempt); } } } else { FinishJoinPartyAttempt(*JoinAttempt, FJoinPartyResult(Result.EnumResult, Result.SubCode)); } } } void USocialManager::HandleCreatePartyComplete(const FUniqueNetId& LocalUserId, const TSharedPtr& PartyId, ECreatePartyCompletionResult Result, FOnlinePartyTypeId PartyTypeId, FOnCreatePartyAttemptComplete CompletionDelegate) { ABORT_DURING_SHUTDOWN(); QUICK_SCOPE_CYCLE_COUNTER(STAT_SocialManager_HandleCreatePartyComplete); ECreatePartyCompletionResult LocalCreationResult = Result; if (Result == ECreatePartyCompletionResult::Succeeded) { if (USocialParty* NewParty = EstablishNewParty(LocalUserId, *PartyId, PartyTypeId)) { NewParty->ResetPrivacySettings(); if (UPartyMember* LocalPartyMember = NewParty->GetPartyMember(LocalUserId.AsShared())) { UE_LOG(LogParty, Verbose, TEXT("Player [%s] created party."), *LocalUserId.ToDebugString()); LocalPartyMember->GetMutableRepData().SetJoinMethod(PartyJoinMethod::Creation.ToString()); } } else { LocalCreationResult = ECreatePartyCompletionResult::UnknownClientFailure; } } UE_LOG(LogParty, Verbose, TEXT("Finished trying to create party [%s] with result [%s]"), PartyId.IsValid() ? *PartyId->ToDebugString() : TEXT("Invalid"), ToString(LocalCreationResult)); { QUICK_SCOPE_CYCLE_COUNTER(STAT_SocialManager_HandleCreatePartyComplete_Delegate); CompletionDelegate.ExecuteIfBound(LocalCreationResult); } } void USocialManager::HandleJoinPartyComplete(const FUniqueNetId& LocalUserId, const FOnlinePartyId& PartyId, EJoinPartyCompletionResult Result, int32 NotApprovedReasonCode, FOnlinePartyTypeId PartyTypeId) { ABORT_DURING_SHUTDOWN(); UE_LOG(LogParty, Log, TEXT("Attempt to join party of type [%d] completed with result [%s] and reason code [%d] by user is [%s] "), PartyTypeId.GetValue(), ToString(Result), NotApprovedReasonCode, *LocalUserId.ToDebugString()); FJoinPartyResult JoinResult(Result, NotApprovedReasonCode); FJoinPartyAttempt* JoinAttempt = JoinAttemptsByTypeId.Find(PartyTypeId); if (ensure(JoinAttempt)) { JoinAttempt->ActionTimeTracker.CompleteStep(FJoinPartyAttempt::Step_JoinParty); if (JoinResult.WasSuccessful()) { if (bCanCreatePartyObjects) { USocialParty* NewParty = EstablishNewParty(LocalUserId, PartyId, PartyTypeId); if (!NewParty) { JoinResult.SetResult(EJoinPartyCompletionResult::UnknownClientFailure); } FinishJoinPartyAttempt(*JoinAttempt, JoinResult); } else { // Not currently in an ok state to be creating new party objects (between maps or something) - update the join attempt and revisit when we're cleared to create party objects again JoinAttempt->ActionTimeTracker.BeginStep(FJoinPartyAttempt::Step_DeferredPartyCreation); } } else { FinishJoinPartyAttempt(*JoinAttempt, JoinResult); } } else { //@note DanH: Should be quite impossible, but happening in the wild without repro steps (FORT-123031) - putting in lots of mines here to make sure we see it if it happens in-house UE_LOG(LogParty, Error, TEXT("Attempt to join party of type [%d] completed with result [%s], but there is no existing FJoinPartyAttempt object."), PartyTypeId.GetValue(), ToString(Result)); if (!ensure(!JoinResult.WasSuccessful())) { UE_LOG(LogParty, Error, TEXT("Auto-bailing on party of type [%d] - cannot finish establishing it without a valid FJoinPartyAttempt."), PartyTypeId.GetValue(), ToString(Result)); IOnlinePartyPtr PartyInterface = Online::GetPartyInterfaceChecked(GetWorld()); PartyInterface->LeaveParty(LocalUserId, PartyId, true, FOnLeavePartyComplete::CreateUObject(this, &USocialManager::HandleLeavePartyForMissingJoinAttempt, PartyTypeId)); } else { // Failed to join this party in the first place - skip to the leave complete handler and to do any necessary fixup (since we were still missing the join attempt, and that's far less than ideal) HandleLeavePartyForMissingJoinAttempt(LocalUserId, PartyId, ELeavePartyCompletionResult::Succeeded, PartyTypeId); } } } void USocialManager::HandlePersistentPartyStateChanged(EPartyState NewState, EPartyState PreviousState, USocialParty* PersistentParty) { ABORT_DURING_SHUTDOWN(); UE_LOG(LogParty, Verbose, TEXT("Persistent party state changed to %s"), ToString(NewState)); if (NewState == EPartyState::Disconnected) { bIsConnectedToPartyService = false; if (PreviousState == EPartyState::Active) { if (USocialSettings::ShouldLeavePartyOnDisconnect()) { PersistentParty->LeaveParty(); } } } else if (NewState == EPartyState::Active) { bIsConnectedToPartyService = true; } } void USocialManager::HandleLeavePartyForJoinComplete(ELeavePartyCompletionResult LeaveResult, USocialParty* LeftParty) { ABORT_DURING_SHUTDOWN(); if (LeftParty) { UE_LOG(LogParty, Verbose, TEXT("Attempt to leave party [%s] for pending join completed with result [%s]"), *LeftParty->ToDebugString(), ToString(LeaveResult)); } } bool USocialManager::IsConnectedToPartyService() const { return bIsConnectedToPartyService #if !UE_BUILD_SHIPPING && !CVarForceDisconnectedToPartyService.GetValueOnAnyThread() #endif ; } void USocialManager::HandlePartyDisconnected(USocialParty* DisconnectingParty) { ABORT_DURING_SHUTDOWN(); if (DisconnectingParty) { const FOnlinePartyTypeId& PartyTypeId = DisconnectingParty->GetPartyTypeId(); JoinedPartiesByTypeId.Remove(PartyTypeId); DisconnectingParty->MarkAsGarbage(); } } void USocialManager::HandlePartyLeaveBegin(EMemberExitedReason Reason, USocialParty* LeavingParty) { ABORT_DURING_SHUTDOWN(); if (LeavingParty) { const FOnlinePartyTypeId& PartyTypeId = LeavingParty->GetPartyTypeId(); JoinedPartiesByTypeId.Remove(PartyTypeId); LeavingPartiesByTypeId.Add(PartyTypeId, LeavingParty); } } void USocialManager::HandlePartyLeft(EMemberExitedReason Reason, USocialParty* LeftParty) { ABORT_DURING_SHUTDOWN(); if (LeftParty) { const FOnlinePartyTypeId& PartyTypeId = LeftParty->GetPartyTypeId(); LeavingPartiesByTypeId.Remove(PartyTypeId); if (!ensure(!JoinedPartiesByTypeId.Contains(PartyTypeId))) { // Really shouldn't be any scenario wherein we receive a PartyLeft event without a prior PartyLeaveBegin JoinedPartiesByTypeId.Remove(PartyTypeId); } OnPartyLeftInternal(*LeftParty, Reason); LeftParty->MarkAsGarbage(); if (FJoinPartyAttempt* JoinAttempt = JoinAttemptsByTypeId.Find(PartyTypeId)) { JoinAttempt->ActionTimeTracker.CompleteStep(FJoinPartyAttempt::Step_LeaveCurrentParty); // We're in the process of joining another party of the same type - do we know where we're heading yet? if (JoinAttempt->JoinInfo.IsValid()) { // Join the new party immediately and early out JoinPartyInternal(*JoinAttempt); return; } else { // An attempt to join a party of this type has been initiated, but something/someone decided to leave the party before the attempt was ready to do so // It's not worth accounting for the potential limbo that this could put us into, so just abort the join attempt and let the explicit leave action win UE_LOG(LogParty, Verbose, TEXT("Finished leaving party [%s] before the current join attempt established join info. Cancelling join attempt."), *LeftParty->ToDebugString()); FinishJoinPartyAttempt(*JoinAttempt, FJoinPartyResult(EPartyJoinDenialReason::JoinAttemptAborted)); } } if (LeftParty->IsPersistentParty() && GetFirstLocalUserToolkit()->CanAutoRecreatePersistentParty()) { UE_LOG(LogParty, Verbose, TEXT("Finished leaving persistent party without a join/rejoin target. Creating a new persistent party now.")); // This wasn't part of a join process, so immediately create a new persistent party CreatePersistentParty(); } } } void USocialManager::HandleLeavePartyForMissingJoinAttempt(const FUniqueNetId& LocalUserId, const FOnlinePartyId& PartyId, ELeavePartyCompletionResult LeaveResult, FOnlinePartyTypeId PartyTypeId) { ABORT_DURING_SHUTDOWN(); if (PartyTypeId == IOnlinePartySystem::GetPrimaryPartyTypeId() && GetFirstLocalUserToolkit()->CanAutoRecreatePersistentParty() && !GetPersistentPartyInternal(true)) { // We just had to bail on the persistent party due to unforeseen shenanigans, so try to correct things and set another one back up CreatePersistentParty(); } } void USocialManager::HandleFillPartyJoinRequestData(const FUniqueNetId& LocalUserId, const FOnlinePartyId& PartyId, FOnlinePartyData& PartyData) { ABORT_DURING_SHUTDOWN(); FillOutJoinRequestData(PartyId, PartyData); } void USocialManager::HandleFindSessionForJoinComplete(bool bWasSuccessful, const FOnlineSessionSearchResult& FoundSession, FOnlinePartyTypeId PartyTypeId) { ABORT_DURING_SHUTDOWN(); if (FJoinPartyAttempt* JoinAttempt = JoinAttemptsByTypeId.Find(PartyTypeId)) { JoinAttempt->ActionTimeTracker.CompleteStep(FJoinPartyAttempt::Step_FindPlatformSession); if (bWasSuccessful) { JoinAttempt->JoinInfo = GetJoinInfoFromSession(FoundSession); if (JoinAttempt->JoinInfo.IsValid()) { QueryPartyJoinabilityInternal(*JoinAttempt); } else { FinishJoinPartyAttempt(*JoinAttempt, FJoinPartyResult(EPartyJoinDenialReason::PlatformSessionMissingJoinInfo)); } } else { FinishJoinPartyAttempt(*JoinAttempt, FJoinPartyResult(EPartyJoinDenialReason::TargetUserMissingPlatformSession)); } } } #if UE_ALLOW_EXEC_COMMANDS bool USocialManager::Exec(class UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Out) { if (FParse::Command(&Cmd, TEXT("SOCIAL"))) { if (SocialDebugTools && SocialDebugTools->Exec(InWorld, Cmd, Out)) { return true; } for (USocialToolkit* Toolkit : SocialToolkits) { if (Toolkit && Toolkit->Exec(InWorld, Cmd, Out)) { return true; } } return true; } return false; } #endif // UE_ALLOW_EXEC_COMMANDS USocialDebugTools* USocialManager::GetDebugTools() const { return SocialDebugTools; }