Implement a party session - Introduction to party - (Unreal Engine module)
Unwrap the online session
Creating a party is essentially just creating a new session of type "party", so managing a party is similar to managing an online session. In this section, you will learn how to set up essential party features using the AccelByte Gaming Services (AGS) Online Subsystem (OSS).
In the Byte Wars project, there is already an online session class created named PartyOnlineSession
. This class is the child of the USessionEssentialsOnlineSession
class, which is the class you used to create a session in the previous Byte Wars module (Session Essentials). The PartyOnlineSession
class contains party-related features, but in this tutorial, you will use a starter version of it so you can implement party-related features from scratch.
What's in the starter pack
To follow this tutorial, starter online session class named PartyOnlineSession_Starter
has been prepared for you. This class consists of the following files:
- The Header file, which can be found in
/Source/AccelByteWars/TutorialModules/PartyEssentials/PartyOnlineSession_Starter.h
. - The CPP file, can be found in
/Source/AccelByteWars/TutorialModules/PartyEssentials/PartyOnlineSession_Starter.cpp
.
The PartyOnlineSession_Starter
class has several functions provided.
There are several functions to retrieve from AGS OSS interfaces. First is the
UAccelByteWarsOnlineSessionBase::GetABSessionInt()
function, which retrieves the AGS Session interface. Then, theUAccelByteWarsOnlineSessionBase::GetUserInt()
function, which retrieves the AGS user interface (UI). You will use these to implement party features later.FOnlineSessionV2AccelBytePtr UAccelByteWarsOnlineSessionBase::GetABSessionInt()
{
return StaticCastSharedPtr<FOnlineSessionV2AccelByte>(GetSessionInt());
}IOnlineUserPtr UAccelByteWarsOnlineSessionBase::GetUserInt() const
{
const UWorld* World = GetWorld();
if (!ensure(World))
{
return nullptr;
}
return Online::GetUserInterface(World);
}A helper function to trigger an event when the player leaves the party session. You will need this for several party features later. For example, to create or join a new party, the player must leave any party session first. To do that, you can bind the create or join party event to this function.
void UPartyOnlineSession_Starter::OnLeavePartyToTriggerEvent(FName SessionName, bool bSucceeded, const TDelegate<void(bool bWasSuccessful)> OnComplete)
{
// Abort if not a party session.
if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
{
OnComplete.ExecuteIfBound(false);
return;
}
OnComplete.ExecuteIfBound(bSucceeded);
}Helper functions to query party member information, such as a display name and avatar.
void UStartupSubsystem::QueryUserInfo(
const int32 LocalUserNum,
const TArray<FUniqueNetIdRef>& UserIds,
const FOnQueryUsersInfoCompleteDelegate& OnComplete)
{
// Abort if the user interface is invalid.
if (!UserInterface)
{
UE_LOG_STARTUP(Warning, TEXT("User interface is invalid"))
GetWorld()->GetTimerManager().SetTimerForNextTick(FTimerDelegate::CreateWeakLambda(this, [this, OnComplete]()
{
OnComplete.ExecuteIfBound(
FOnlineError::CreateError(
TEXT(""),
EOnlineErrorResult::RequestFailure,
TEXT(""),
FText::FromString(TEXT(""))),
{});
}));
return;
}
// Call query right away since the it will check for cache first internally
if (OnQueryUserInfoCompleteDelegateHandle.IsValid())
{
UserInterface->OnQueryUserInfoCompleteDelegates->Remove(OnQueryUserInfoCompleteDelegateHandle);
OnQueryUserInfoCompleteDelegateHandle.Reset();
}
OnQueryUserInfoCompleteDelegateHandle = UserInterface->OnQueryUserInfoCompleteDelegates->AddWeakLambda(
this, [OnComplete, this](
int32 LocalUserNum,
bool bSucceeded,
const TArray<FUniqueNetIdRef>& UserIds,
const FString& ErrorMessage)
{
OnQueryUserInfoComplete(LocalUserNum, bSucceeded, UserIds, ErrorMessage, OnComplete);
});
if (!UserInterface->QueryUserInfo(LocalUserNum, UserIds))
{
OnQueryUserInfoComplete(LocalUserNum, false, UserIds, "", OnComplete);
}
}void UStartupSubsystem::OnQueryUserInfoComplete(
int32 LocalUserNum,
bool bSucceeded,
const TArray<FUniqueNetIdRef>& UserIds,
const FString& ErrorMessage,
const FOnQueryUsersInfoCompleteDelegate& OnComplete)
{
// reset delegate handle
UserInterface->OnQueryUserInfoCompleteDelegates->Remove(OnQueryUserInfoCompleteDelegateHandle);
OnQueryUserInfoCompleteDelegateHandle.Reset();
if (bSucceeded)
{
// Retrieve the result from cache.
TArray<TSharedPtr<FUserOnlineAccountAccelByte>> OnlineUsers;
for (const FUniqueNetIdRef& UserId : UserIds)
{
TSharedPtr<FOnlineUser> OnlineUser = UserInterface->GetUserInfo(0, UserId.Get());
if (!OnlineUser.IsValid() || !OnlineUser->GetUserId()->IsValid())
{
continue;
}
OnlineUsers.Add(StaticCastSharedPtr<FUserOnlineAccountAccelByte>(OnlineUser));
}
OnComplete.ExecuteIfBound(FOnlineError::Success(), OnlineUsers);
}
else
{
OnComplete.ExecuteIfBound(
FOnlineError::CreateError(
TEXT(""),
EOnlineErrorResult::RequestFailure,
TEXT(""),
FText::FromString(ErrorMessage)),
{});
}
}A helper function to retrieve the prompt system that can be used to push notifications for party events, such as notifications for party members joining, party members leaving, and party invitations.
UPromptSubsystem* UPartyOnlineSession_Starter::GetPromptSubystem()
{
UAccelByteWarsGameInstance* GameInstance = Cast<UAccelByteWarsGameInstance>(GetGameInstance());
if (!GameInstance)
{
return nullptr;
}
return GameInstance->GetSubsystem<UPromptSubsystem>();
}There are two pre-defined delegates that you can use to bind and unbind your party event delegates later. These functions will be called when the online session is initialized and deinitialized respectively.
void UPartyOnlineSession_Starter::RegisterOnlineDelegates()
{
Super::RegisterOnlineDelegates();
InitializePartyGeneratedWidgets();
// ...
}void UPartyOnlineSession_Starter::ClearOnlineDelegates()
{
Super::ClearOnlineDelegates();
DeinitializePartyGeneratedWidgets();
// ...
}
Implement party utilities
In this section, you will implement some party utilities that you can use later, such as displaying party members. You will implement getting the party member list and getting the current party leader.
First, declare several functions. Open the
PartyOnlineSession_Starter
class Header file and add the following code:public:
// ...
virtual TArray<FUniqueNetIdRef> GetPartyMembers() override;
virtual FUniqueNetIdPtr GetPartyLeader() override;
virtual bool IsInParty(const FUniqueNetIdPtr UserId);
virtual bool IsPartyLeader(const FUniqueNetIdPtr UserId) override;Then, open the
PartyOnlineSession_Starter
class CPP file and define the functions as follows:TArray<FUniqueNetIdRef> UPartyOnlineSession_Starter::GetPartyMembers()
{
if (GetABSessionInt())
{
const FNamedOnlineSession* PartySession = GetABSessionInt()->GetNamedSession(GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession));
if (PartySession)
{
return PartySession->RegisteredPlayers;
}
}
return TArray<FUniqueNetIdRef>();
}FUniqueNetIdPtr UPartyOnlineSession_Starter::GetPartyLeader()
{
if (GetABSessionInt())
{
const FNamedOnlineSession* PartySession = GetABSessionInt()->GetNamedSession(GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession));
if (PartySession)
{
const TSharedPtr<FOnlineSessionInfoAccelByteV2> SessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2>(PartySession->SessionInfo);
if (!SessionInfo)
{
return nullptr;
}
return GetABSessionInt()->GetSessionLeaderId(PartySession);
}
}
return nullptr;
}bool UPartyOnlineSession_Starter::IsInParty(const FUniqueNetIdPtr UserId)
{
if (!UserId)
{
return false;
}
const TPartyMemberArray Members = GetPartyMembers();
for (const auto& Member : Members)
{
if (!Member.Get().IsValid())
{
continue;
}
if (Member.Get() == UserId.ToSharedRef().Get())
{
return true;
}
}
return false;
}bool UPartyOnlineSession_Starter::IsPartyLeader(const FUniqueNetIdPtr UserId)
{
return GetPartyLeader() && UserId && UserId.ToSharedRef().Get() == GetPartyLeader().ToSharedRef().Get();
}
Implement party creation
In this section, you will implement features to create a new party session.
First, open the
PartyOnlineSession_Starter
class Header file. Then, create a new variable that defines your party session template. Make sure its value is the same as the party session template you created in the previous tutorial (Set up a party session).private:
// ...
const FString PartySessionTemplate = FString("unreal-party");In the same file, declare a function to create a party session.
public:
// ...
virtual void CreateParty(const int32 LocalUserNum) override;Then, declare a callback function that will be called when the party creation process completes.
protected:
// ...
virtual void OnCreatePartyComplete(FName SessionName, bool bSucceeded) override;Next, declare a delegate that will be called when the party creation process completes. You can use this delegate to trigger events when the party is created. For example, you can bind a UI event to display the newly created party's members.
public:
// ...
virtual FOnCreateSessionComplete* GetOnCreatePartyCompleteDelegates()
{
return &OnCreatePartyCompleteDelegates;
}private:
// ...
FOnCreateSessionComplete OnCreatePartyCompleteDelegates;Now, define these functions starting with
CreateParty()
. Open thePartyOnlineSession_Starter
class CPP file and add the following code. This function leaves any party session first before creating a new party session.void UPartyOnlineSession_Starter::CreateParty(const int32 LocalUserNum)
{
const FName SessionName = GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession);
// Abort if the session interface is invalid.
if (!GetABSessionInt())
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot create a party. The session interface is not valid."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
{
OnCreatePartyComplete(SessionName, false);
}));
return;
}
// Always create a new party. Thus, leave any left-over party session first.
if (GetABSessionInt()->IsInPartySession())
{
UE_LOG_PARTYESSENTIALS(Log, TEXT("Party found. Leave old party before creating a new one."));
if (OnLeaveSessionForTriggerDelegateHandle.IsValid())
{
GetOnLeaveSessionCompleteDelegates()->Remove(OnLeaveSessionForTriggerDelegateHandle);
OnLeaveSessionForTriggerDelegateHandle.Reset();
}
OnLeaveSessionForTriggerDelegateHandle = GetOnLeaveSessionCompleteDelegates()->AddUObject(
this,
&ThisClass::OnLeavePartyToTriggerEvent,
TDelegate<void(bool)>::CreateWeakLambda(this, [this, LocalUserNum, SessionName](bool bWasSuccessful)
{
GetOnLeaveSessionCompleteDelegates()->Remove(OnLeaveSessionForTriggerDelegateHandle);
if (bWasSuccessful)
{
UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to leave old party destroyed. Try creating a new party."));
CreateParty(LocalUserNum);
}
else
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot create a new party. Failed to leave old party."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
{
OnCreatePartyComplete(SessionName, false);
}));
}
}
));
LeaveSession(SessionName);
return;
}
// Create a new party session. Override party session template name if applicable.
UE_LOG_PARTYESSENTIALS(Log, TEXT("Create a new party."));
CreateSession(
LocalUserNum,
SessionName,
FOnlineSessionSettings(),
EAccelByteV2SessionType::PartySession,
UTutorialModuleOnlineUtility::GetPartySessionTemplateOverride().IsEmpty() ?
PartySessionTemplate : UTutorialModuleOnlineUtility::GetPartySessionTemplateOverride());
}Next, define the
OnCreatePartyComplete()
function by adding the code below. This function broadcasts the on-complete delegate you defined in the Header file earlier.void UPartyOnlineSession_Starter::OnCreatePartyComplete(FName SessionName, bool bSucceeded)
{
if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
{
return;
}
if (bSucceeded)
{
UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to create a party"));
}
else
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to create a party"));
}
// Cache the party leader.
LastPartyLeader = GetPartyLeader();
// Reset the party member status cache.
PartyMemberStatus.Empty();
OnCreatePartyCompleteDelegates.Broadcast(SessionName, bSucceeded);
}Then, you need to bind the
OnCreatePartyComplete()
so it will be called when the party creation process completes. You can do this by adding the code below in the predefinedRegisterOnlineDelegates()
function, which is the first function to be called when the online session is initialized.void UPartyOnlineSession_Starter::RegisterOnlineDelegates()
{
// ...
if (GetABSessionInt())
{
// ...
GetABSessionInt()->OnCreateSessionCompleteDelegates.AddUObject(this, &ThisClass::OnCreatePartyComplete);
// ...
}
// ...
}Finally, when the online session is deinitialized, you need to unbind to stop listening to the party creation process completion event. You can do this by adding the following code in the predefined
ClearOnlineDelegates()
function, which is the first function to be called when the online session is deinitialized.void UPartyOnlineSession_Starter::ClearOnlineDelegates()
{
// ...
if (GetABSessionInt())
{
// ...
GetABSessionInt()->OnCreateSessionCompleteDelegates.RemoveAll(this);
// ...
}
// ...
}
Implement leave party
In this section, you will implement the feature to leave a party session.
Open the
PartyOnlineSession_Starter
class Header file and declare a function to leave a party.public:
// ...
virtual void LeaveParty(const int32 LocalUserNum) override;In the same file, declare a callback function that will be called when the leave party process completes.
protected:
// ...
virtual void OnLeavePartyComplete(FName SessionName, bool bSucceeded) override;Next, declare a delegate that will be called when the leave party process completes. You can use this delegate's wrapper to trigger events when the leave party process completes.
public:
// ...
virtual FOnDestroySessionComplete* GetOnLeavePartyCompleteDelegates()
{
return &OnLeavePartyCompleteDelegates;
}private:
// ...
FOnDestroySessionComplete OnLeavePartyCompleteDelegates;Now, define these functions starting with
LeaveParty()
. Open thePartyOnlineSession_Starter
class CPP file and add the code below. This function allows the player to leave the party session.void UPartyOnlineSession_Starter::LeaveParty(const int32 LocalUserNum)
{
const FName SessionName = GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession);
if (!GetABSessionInt())
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot leave a party. The session interface is not valid."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
{
OnLeavePartyComplete(SessionName, false);
}));
return;
}
if (!GetABSessionInt()->IsInPartySession())
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot leave a party. Not in any party."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
{
OnLeavePartyComplete(SessionName, false);
}));
return;
}
// Leave party.
UE_LOG_PARTYESSENTIALS(Log, TEXT("Leave party."));
LeaveSession(SessionName);
}Next, define the
OnLeavePartyComplete()
function by adding the code below. This function broadcasts the on-complete delegate you defined in the Header file.void UPartyOnlineSession_Starter::OnLeavePartyComplete(FName SessionName, bool bSucceeded)
{
if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
{
return;
}
if (bSucceeded)
{
UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to leave a party"));
}
else
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to leave a party"));
}
OnLeavePartyCompleteDelegates.Broadcast(SessionName, bSucceeded);
}Then, you need to bind the
OnLeavePartyComplete()
so it will be called when the leave party process completes. You can do this by adding the following code in the predefinedRegisterOnlineDelegates()
function, which is the first function to be called when the online session is initialized.void UPartyOnlineSession_Starter::RegisterOnlineDelegates()
{
// ...
if (GetABSessionInt())
{
// ...
GetABSessionInt()->OnDestroySessionCompleteDelegates.AddUObject(this, &ThisClass::OnLeavePartyComplete);
// ...
}
// ...
}Finally, when the online session is deinitialized, you need to unbind to stop listening to the leave party process completion event. You can do this by adding the code below in the predefined
ClearOnlineDelegates()
function, which is the first function to be called when the online session is deinitialized.void UPartyOnlineSession_Starter::ClearOnlineDelegates()
{
// ...
if (GetABSessionInt())
{
// ...
GetABSessionInt()->OnDestroySessionCompleteDelegates.RemoveAll(this);
// ...
}
// ...
}
Implement party invitation sending
In this section, you will implement the feature to send party invitations.
Open the
PartyOnlineSession_Starter
class Header file and declare a function to send a party invitation.public:
// ...
virtual void SendPartyInvite(const int32 LocalUserNum, const FUniqueNetIdPtr& Invitee) override;In the same file, declare a callback function that will be called when the send party invitation process completes.
protected:
// ...
virtual void OnSendPartyInviteComplete(const FUniqueNetId& Sender, FName SessionName, bool bWasSuccessful, const FUniqueNetId& Invitee) override;Next, declare a delegate that will be called when the send party invitation process completes. You can use this delegate's wrapper to trigger events when the send party invitation completes.
public:
// ...
virtual FOnSendSessionInviteComplete* GetOnSendPartyInviteCompleteDelegates() override
{
return &OnSendPartyInviteCompleteDelegates;;
}private:
// ...
FOnSendSessionInviteComplete OnSendPartyInviteCompleteDelegates;Now, define these functions starting with
SendPartyInvite()
. Open thePartyOnlineSession_Starter
class CPP file and add the code below. This function sends a party invitation to the target invitee. If the inviter is not in a party, this function creates a new party first before sending the party invitation.void UPartyOnlineSession_Starter::SendPartyInvite(const int32 LocalUserNum, const FUniqueNetIdPtr& Invitee)
{
if (!GetABSessionInt())
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot send a party invitation. The session interface is not valid."));
return;
}
const APlayerController* SenderPC = GetPlayerControllerByLocalUserNum(LocalUserNum);
if (!SenderPC)
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot send a party invitation. Sender's PlayerController is not valid."));
return;
}
const FUniqueNetIdPtr SenderId = GetLocalPlayerUniqueNetId(SenderPC);
if (!SenderId)
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot send a party invitation. Sender's NetId is not valid."));
return;
}
const FName SessionName = GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession);
if (!Invitee)
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot send a party invitation. Invitee's NetId is not valid."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SenderId, SessionName, Invitee]()
{
OnSendPartyInviteComplete(SenderId.ToSharedRef().Get(), SessionName, false, Invitee.ToSharedRef().Get());
}));
return;
}
// Create a new party first before inviting.
if (!GetABSessionInt()->IsInPartySession())
{
UE_LOG_PARTYESSENTIALS(Log, TEXT("Not in a party session. Creating a new party before sending a party invitation."));
if (OnCreatePartyToInviteMemberDelegateHandle.IsValid())
{
GetOnCreateSessionCompleteDelegates()->Remove(OnCreatePartyToInviteMemberDelegateHandle);
OnCreatePartyToInviteMemberDelegateHandle.Reset();
}
OnCreatePartyToInviteMemberDelegateHandle = GetOnCreateSessionCompleteDelegates()->AddUObject(this, &ThisClass::OnCreatePartyToInviteMember, LocalUserNum, SenderId, Invitee);
CreateParty(LocalUserNum);
return;
}
// Send party invitation.
UE_LOG_PARTYESSENTIALS(Log, TEXT("Send party invitation."));
GetABSessionInt()->SendSessionInviteToFriend(
SenderId.ToSharedRef().Get(),
SessionName,
Invitee.ToSharedRef().Get());
}Next, define the
OnSendPartyInviteComplete()
function by adding the code below. This function broadcasts the on-complete delegate you defined in the Header file and displays a push notification.void UPartyOnlineSession_Starter::OnSendPartyInviteComplete(const FUniqueNetId& Sender, FName SessionName, bool bWasSuccessful, const FUniqueNetId& Invitee)
{
// Abort if not a party session.
if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
{
return;
}
const FUniqueNetIdAccelByteUserRef InviteeABId = StaticCastSharedRef<const FUniqueNetIdAccelByteUser>(Invitee.AsShared());
if (bWasSuccessful)
{
UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to send party invitation to %s"),
InviteeABId->IsValid() ? *InviteeABId->GetAccelByteId() : TEXT("Unknown"));
}
else
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to send party invitation to %s"),
InviteeABId->IsValid() ? *InviteeABId->GetAccelByteId() : TEXT("Unknown"));
}
// Display push notification.
if (GetPromptSubystem())
{
GetPromptSubystem()->PushNotification(bWasSuccessful ? SUCCESS_SEND_PARTY_INVITE : FAILED_SEND_PARTY_INVITE);
}
UpdatePartyGeneratedWidgets();
OnSendPartyInviteCompleteDelegates.Broadcast(Sender, SessionName, bWasSuccessful, Invitee);
}Next, open the
PartyOnlineSession_Starter
class Header file and declare the function and the delegate below to make sure party is created before sending a party invitation.protected:
void OnCreatePartyToInviteMember(FName SessionName, bool bWasSuccessful, const int32 LocalUserNum, const FUniqueNetIdPtr SenderId, const FUniqueNetIdPtr InviteeId);private:
FDelegateHandle OnCreatePartyToInviteMemberDelegateHandle;Next, define the function above to handle party creation when sending a party invitation.
void UPartyOnlineSession_Starter::OnCreatePartyToInviteMember(FName SessionName, bool bWasSuccessful, const int32 LocalUserNum, const FUniqueNetIdPtr SenderId, const FUniqueNetIdPtr InviteeId)
{
// Abort if not a party session.
if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
{
return;
}
GetOnCreateSessionCompleteDelegates()->Remove(OnCreatePartyToInviteMemberDelegateHandle);
if (!bWasSuccessful)
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot send a party invitation. Failed to create a new party."));
OnSendPartyInviteComplete(SenderId.ToSharedRef().Get(), SessionName, false, InviteeId.ToSharedRef().Get());
}
else
{
UE_LOG_PARTYESSENTIALS(Log, TEXT("Party created. Try sending a party invitation."));
SendPartyInvite(LocalUserNum, InviteeId);
}
}Then, you need to bind the
OnSendPartyInviteComplete()
so it will be called when the send party invitation process completes. You can do this by adding the code below in the predefinedRegisterOnlineDelegates()
function, which is the first function to be called when the online session is initialized.void UPartyOnlineSession_Starter::RegisterOnlineDelegates()
{
// ...
if (GetABSessionInt())
{
// ...
GetABSessionInt()->OnSendSessionInviteCompleteDelegates.AddUObject(this, &ThisClass::OnSendPartyInviteComplete);
// ...
}
// ...
}Finally, when the online session is deinitialized, you need to unbind it to stop listening to the send party invitation process completion event. You can do this by adding the code below in the predefined
ClearOnlineDelegates()
function, which is the first function to be called when the online session is deinitialized.void UPartyOnlineSession_Starter::ClearOnlineDelegates()
{
// ...
if (GetABSessionInt())
{
// ...
GetABSessionInt()->OnSendSessionInviteCompleteDelegates.RemoveAll(this);
// ...
}
// ...
}
Implement invitation acceptance and party joining
In this section, you will implement the feature to accept a party invitation and join a party session.
Open the
PartyOnlineSession_Starter
class Header file and declare a function to join a party.public:
// ...
virtual void JoinParty(const int32 LocalUserNum, const FOnlineSessionSearchResult& PartySessionResult) override;In the same file, declare a callback function that will be called when the join party process completes.
protected:
// ...
virtual void OnJoinPartyComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result) override;Next, declare a delegate that will be called when the join party process completes. You can use this delegate's wrapper to trigger events when the join party process completes.
public:
// ...
virtual FOnJoinSessionComplete* GetOnJoinPartyCompleteDelegates()
{
return &OnJoinPartyCompleteDelegates;
}private:
// ...
FOnJoinSessionComplete OnJoinPartyCompleteDelegates;Now, define these functions starting with
JoinParty()
. Open thePartyOnlineSession_Starter
class CPP file and add the code below. This function leaves any party session first before joining a new party session.void UPartyOnlineSession_Starter::JoinParty(const int32 LocalUserNum, const FOnlineSessionSearchResult& PartySessionResult)
{
const FName SessionName = GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession);
if (!GetABSessionInt())
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot join a party. The session interface is not valid."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
{
OnJoinPartyComplete(SessionName, EOnJoinSessionCompleteResult::Type::UnknownError);
}));
return;
}
// Always leave any party before joining a new party.
if (GetABSessionInt()->IsInPartySession())
{
UE_LOG_PARTYESSENTIALS(Log, TEXT("Party found. Leave old party before joining a new one."));
if (OnLeaveSessionForTriggerDelegateHandle.IsValid())
{
GetOnLeaveSessionCompleteDelegates()->Remove(OnLeaveSessionForTriggerDelegateHandle);
OnLeaveSessionForTriggerDelegateHandle.Reset();
}
OnLeaveSessionForTriggerDelegateHandle = GetOnLeaveSessionCompleteDelegates()->AddUObject(
this,
&ThisClass::OnLeavePartyToTriggerEvent,
TDelegate<void(bool)>::CreateWeakLambda(this, [this, LocalUserNum, PartySessionResult, SessionName](bool bWasSuccessful)
{
GetOnLeaveSessionCompleteDelegates()->Remove(OnLeaveSessionForTriggerDelegateHandle);
if (bWasSuccessful)
{
UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to leave old party. Try to joining a new party."));
JoinParty(LocalUserNum, PartySessionResult);
}
else
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot joining a new party. Failed to leave old party."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
{
OnJoinPartyComplete(SessionName, EOnJoinSessionCompleteResult::Type::UnknownError);
}));
}
}
));
LeaveSession(SessionName);
return;
}
// Join a new party.
UE_LOG_PARTYESSENTIALS(Log, TEXT("Join a new party."));
JoinSession(LocalUserNum, SessionName, PartySessionResult);
}Next, define the
OnJoinPartyComplete()
function by adding the code below. This function broadcasts the on-complete delegate you defined in the Header file.void UPartyOnlineSession_Starter::OnJoinPartyComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
{
return;
}
if (Result == EOnJoinSessionCompleteResult::Type::Success)
{
UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to join a party"));
}
else
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to join a party"));
}
// Cache the party leader.
LastPartyLeader = GetPartyLeader();
// Reset the party member status cache.
PartyMemberStatus.Empty();
OnJoinPartyCompleteDelegates.Broadcast(SessionName, Result);
}Then, you need to bind the
OnJoinPartyComplete()
so it will be called when the join party process completes. You can do this by adding the following code in the predefinedRegisterOnlineDelegates()
function, which is the first function to be called when the online session is initialized.void UPartyOnlineSession_Starter::RegisterOnlineDelegates()
{
// ...
if (GetABSessionInt())
{
// ...
GetABSessionInt()->OnJoinSessionCompleteDelegates.AddUObject(this, &ThisClass::OnJoinPartyComplete);
// ...
}
// ...
}Finally, when the online session is deinitialized, you need to unbind it to stop listening to the join party process completion event. You can do this by adding the code below in the predefined
ClearOnlineDelegates()
function, which is the first function to be called when the online session is deinitialized.void UPartyOnlineSession_Starter::ClearOnlineDelegates()
{
// ...
if (GetABSessionInt())
{
// ...
GetABSessionInt()->OnJoinSessionCompleteDelegates.RemoveAll(this);
// ...
}
// ...
}
Implement party invitation rejections
In this section, you will implement the feature to reject a party invitation.
Open the
PartyOnlineSession_Starter
class Header file and declare the following function.public:
// ...
virtual void RejectPartyInvite(const int32 LocalUserNum, const FOnlineSessionInviteAccelByte& PartyInvite) override;In the same file, declare a callback function that will be called when the reject party invitation process completes.
protected:
// ...
virtual void OnRejectPartyInviteComplete(bool bWasSuccessful) override;Next, declare a delegate that will be called when the reject party invitation process completes. You can use this delegate's wrapper to trigger events when the reject party invitation process completes.
public:
// ...
virtual FOnRejectSessionInviteComplete* GetOnRejectPartyInviteCompleteDelegate() override
{
return &OnRejectPartyInviteCompleteDelegate;
}private:
// ...
FOnRejectSessionInviteComplete OnRejectPartyInviteCompleteDelegate;Now, define these functions starting with the
RejectPartyInvite()
. Open thePartyOnlineSession_Starter
class CPP file and add the code below. This function rejects a party invitation received by the player. The callback will be handled by theOnRejectPartyInviteComplete()
function.void UPartyOnlineSession_Starter::RejectPartyInvite(const int32 LocalUserNum, const FOnlineSessionInviteAccelByte& PartyInvite)
{
if (!GetABSessionInt())
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot reject a party invitation. The session interface is not valid."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this]()
{
OnRejectPartyInviteComplete(false);
}));
return;
}
const APlayerController* RejecterPC = GetPlayerControllerByLocalUserNum(LocalUserNum);
if (!RejecterPC)
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot reject a party invitation. Rejecter's PlayerController is not valid."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this]()
{
OnRejectPartyInviteComplete(false);
}));
return;
}
const FUniqueNetIdPtr RejecterId = GetLocalPlayerUniqueNetId(RejecterPC);
if (!RejecterId)
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot reject a party invitation. Rejecter's NetId is not valid."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this]()
{
OnRejectPartyInviteComplete(false);
}));
return;
}
UE_LOG_PARTYESSENTIALS(Log, TEXT("Reject party invitation."));
GetABSessionInt()->RejectInvite(
RejecterId.ToSharedRef().Get(),
PartyInvite,
FOnRejectSessionInviteComplete::CreateUObject(this, &ThisClass::OnRejectPartyInviteComplete));
}Next, define the
OnRejectPartyInviteComplete()
function by adding the code below. This function executes the on-complete delegate you defined in the Header file.void UPartyOnlineSession_Starter::OnRejectPartyInviteComplete(bool bWasSuccessful)
{
if (bWasSuccessful)
{
UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to reject party invitation"));
}
else
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to reject party invitation"));
}
OnRejectPartyInviteCompleteDelegate.ExecuteIfBound(bWasSuccessful);
OnRejectPartyInviteCompleteDelegate.Unbind();
}Now, create some functions to listen for when the player party invitation is rejected so the invitation sender can be informed. Open the
PartyOnlineSession_Starter
class Header file and declare the following function:protected:
// ...
virtual void OnPartyInviteRejected(FName SessionName, const FUniqueNetId& RejecterId) override;Next, declare a delegate that will be called when the party invitation is rejected. You can use this delegate's wrapper to trigger events when the party invitation is rejected.
public:
// ...
virtual FOnSessionInviteRejected* GetOnPartyInviteRejectedDelegates() override
{
return &OnPartyInviteRejectedDelegates;
}private:
// ...
FOnSessionInviteRejected OnPartyInviteRejectedDelegates;Now, define the
OnPartyInviteRejected()
function. Open thePartyOnlineSession_Starter
class CPP file and add the code below. This function broadcasts the on-complete delegate you have defined in the Header file and also pushes a notification to display who rejects the party invitation.void UPartyOnlineSession_Starter::OnPartyInviteRejected(FName SessionName, const FUniqueNetId& RejecterId)
{
// Abort if not a party session.
if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
{
return;
}
const FUniqueNetIdAccelByteUserRef RejecterABId = StaticCastSharedRef<const FUniqueNetIdAccelByteUser>(RejecterId.AsShared());
UE_LOG_PARTYESSENTIALS(Log, TEXT("Party invitation is rejected by %s"),
RejecterABId->IsValid() ? *RejecterABId->GetAccelByteId() : TEXT("Unknown"));
// Display push notification to show who rejected the invitation.
if (UStartupSubsystem* StartupSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UStartupSubsystem>())
{
StartupSubsystem->QueryUserInfo(
0,
TPartyMemberArray{ RejecterABId },
FOnQueryUsersInfoCompleteDelegate::CreateWeakLambda(this, [this, RejecterABId](
const FOnlineError& Error, const TArray<TSharedPtr<FUserOnlineAccountAccelByte>>& UsersInfo)
{
if (UsersInfo.IsEmpty() || !UsersInfo[0] || !GetPromptSubystem())
{
return;
}
FUserOnlineAccountAccelByte MemberInfo = *UsersInfo[0];
const FText NotifMessage = FText::Format(PARTY_INVITE_REJECTED_MESSAGE, FText::FromString(
MemberInfo.GetDisplayName().IsEmpty() ?
UTutorialModuleOnlineUtility::GetUserDefaultDisplayName(RejecterABId.Get()) :
MemberInfo.GetDisplayName()
));
FString AvatarURL;
MemberInfo.GetUserAttribute(ACCELBYTE_ACCOUNT_GAME_AVATAR_URL, AvatarURL);
GetPromptSubystem()->PushNotification(NotifMessage, AvatarURL, true);
}));
}
OnPartyInviteRejectedDelegates.Broadcast(SessionName, RejecterId);
}Then, you need to bind the
OnPartyInviteRejected()
so it will be called when the party invitation is rejected. You can do this by adding the code below in the predefinedRegisterOnlineDelegates()
function, which is the first function to be called when the online session is initialized.void UPartyOnlineSession_Starter::RegisterOnlineDelegates()
{
// ...
if (GetABSessionInt())
{
// ...
GetABSessionInt()->OnSessionInviteRejectedDelegates.AddUObject(this, &ThisClass::OnPartyInviteRejected);
// ...
}
// ...
}Finally, when the online session is deinitialized, you need to unbind it to stop listening to the party invitation rejected event. You can do this by adding the code below in the predefined
ClearOnlineDelegates()
function, which is the first function to be called when the online session is deinitialized.void UPartyOnlineSession_Starter::ClearOnlineDelegates()
{
// ...
if (GetABSessionInt())
{
// ...
GetABSessionInt()->OnSessionInviteRejectedDelegates.RemoveAll(this);
// ...
}
// ...
}
Handle received party invitations
In the previous sections, you have implemented accepting and rejecting party invitations. In this section, you will implement the features to handle the received party invitation. This implementation will display a prompt to let your player choose to accept or reject a party invitation.
Open the
PartyOnlineSession_Starter
class Header file and declare the following function:protected:
// ...
virtual void OnPartyInviteReceived(const FUniqueNetId& UserId, const FUniqueNetId& FromId, const FOnlineSessionInviteAccelByte& PartyInvite) override;Next, declare a delegate that will be called when the party invitation is received. You can use this delegate's wrapper to trigger events when the party invitation is received.
public:
// ...
virtual FOnV2SessionInviteReceivedDelegate* GetOnPartyInviteReceivedDelegate() override
{
return &OnPartyInviteReceivedDelegate;
}private:
// ...
FOnV2SessionInviteReceivedDelegate OnPartyInviteReceivedDelegate;Now, define this function. Open the
PartyOnlineSession_Starter
class CPP file and add the code below. What this function does is push a notification to inform the player to accept or reject the party invitation. It also broadcasts the on-complete delegate you defined in the Header file.void UPartyOnlineSession_Starter::OnPartyInviteReceived(const FUniqueNetId& UserId, const FUniqueNetId& FromId, const FOnlineSessionInviteAccelByte& PartyInvite)
{
// Abort if not a party session.
if (UserId == FromId || PartyInvite.SessionType != EAccelByteV2SessionType::PartySession)
{
return;
}
const APlayerController* PC = GetPlayerControllerByUniqueNetId(UserId.AsShared());
if (!PC)
{
return;
}
const FUniqueNetIdAccelByteUserRef SenderABId = StaticCastSharedRef<const FUniqueNetIdAccelByteUser>(FromId.AsShared());
UE_LOG_PARTYESSENTIALS(Log, TEXT("Receives party invitation from %s"),
SenderABId->IsValid() ? *SenderABId->GetAccelByteId() : TEXT("Unknown"));
const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);
// Display push notification to allow player to accept/reject the party invitation.
if (UStartupSubsystem* StartupSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UStartupSubsystem>())
{
StartupSubsystem->QueryUserInfo(
0,
TPartyMemberArray{ SenderABId },
FOnQueryUsersInfoCompleteDelegate::CreateWeakLambda(this, [this, SenderABId, PartyInvite, LocalUserNum](
const FOnlineError& Error, const TArray<TSharedPtr<FUserOnlineAccountAccelByte>>& UsersInfo)
{
if (UsersInfo.IsEmpty() || !UsersInfo[0] || !GetPromptSubystem())
{
return;
}
FUserOnlineAccountAccelByte MemberInfo = *UsersInfo[0];
const FText NotifMessage = FText::Format(PARTY_INVITE_RECEIVED_MESSAGE, FText::FromString(
MemberInfo.GetDisplayName().IsEmpty() ?
UTutorialModuleOnlineUtility::GetUserDefaultDisplayName(SenderABId.Get()) :
MemberInfo.GetDisplayName()
));
FString AvatarURL;
MemberInfo.GetUserAttribute(ACCELBYTE_ACCOUNT_GAME_AVATAR_URL, AvatarURL);
GetPromptSubystem()->PushNotification(
NotifMessage,
AvatarURL,
true,
ACCEPT_PARTY_INVITE_MESSAGE,
REJECT_PARTY_INVITE_MESSAGE,
FText::GetEmpty(),
FPushNotificationDelegate::CreateWeakLambda(this, [this, LocalUserNum, PartyInvite](EPushNotificationActionResult ActionButtonResult)
{
switch (ActionButtonResult)
{
// Show accept party invitation confirmation.
case EPushNotificationActionResult::Button1:
DisplayJoinPartyConfirmation(LocalUserNum, PartyInvite);
break;
// Reject party invitation.
case EPushNotificationActionResult::Button2:
RejectPartyInvite(LocalUserNum, PartyInvite);
break;
}
}
));
}));
}
OnPartyInviteReceivedDelegate.ExecuteIfBound(UserId, FromId, PartyInvite);
OnPartyInviteReceivedDelegate.Unbind();
}If the player accepts the invitation, the function above will call a new function named
DisplayJoinPartyConfirmation()
. Create its declaration first. Open thePartyOnlineSession_Starter
class Header file and add the following code.protected:
// ...
void DisplayJoinPartyConfirmation(const int32 LocalUserNum, const FOnlineSessionInviteAccelByte& PartyInvite);Next, define this function. Open the
PartyOnlineSession_Starter
class CPP file and add the code below. This function displays a dialog to ask a player that's already in a party whether they want to leave and join a new party.void UPartyOnlineSession_Starter::DisplayJoinPartyConfirmation(const int32 LocalUserNum, const FOnlineSessionInviteAccelByte& PartyInvite)
{
// Join the party if not in any party yet.
if (!GetABSessionInt()->IsInPartySession() || GetPartyMembers().Num() <= 1)
{
JoinParty(LocalUserNum, PartyInvite.Session);
return;
}
// Show confirmation to leave current party and join the new party.
GetPromptSubystem()->ShowDialoguePopUp(
PARTY_POPUP_MESSAGE,
JOIN_NEW_PARTY_CONFIRMATION_MESSAGE,
EPopUpType::ConfirmationYesNo,
FPopUpResultDelegate::CreateWeakLambda(this, [this, LocalUserNum, PartyInvite](EPopUpResult Result)
{
switch (Result)
{
case EPopUpResult::Confirmed:
// If confirmed, join the new party.
JoinParty(LocalUserNum, PartyInvite.Session);
break;
case EPopUpResult::Declined:
// If declined, reject the party invitation.
RejectPartyInvite(LocalUserNum, PartyInvite);
break;
}
}
));
}Then, you need to bind the
OnPartyInviteReceived()
so it will be called when the party invitation is received. You can do this by adding the code below in the predefinedRegisterOnlineDelegates()
function, which is the first function to be called when the online session is initialized.void UPartyOnlineSession_Starter::RegisterOnlineDelegates()
{
// ...
if (GetABSessionInt())
{
// ...
GetABSessionInt()->OnV2SessionInviteReceivedDelegates.AddUObject(this, &ThisClass::OnPartyInviteReceived);
// ...
}
// ...
}Finally, when the online session is deinitialized, you need to unbind to stop listening to the party invitation received event. You can do this by adding the following code in the predefined
ClearOnlineDelegates()
function, which is the first function to be called when the online session is deinitialized.void UPartyOnlineSession_Starter::ClearOnlineDelegates()
{
// ...
if (GetABSessionInt())
{
// ...
GetABSessionInt()->OnV2SessionInviteReceivedDelegates.RemoveAll(this);
// ...
}
// ...
}
Implement removing (kicking) party members
In this section, you will implement the feature to kick a player from a party.
Open the
PartyOnlineSession_Starter
class Header file and declare the following function:public:
// ...
virtual void KickPlayerFromParty(const int32 LocalUserNum, const FUniqueNetIdPtr& KickedPlayer) override;In the same file, declare a callback function that will be called when the kick party member process completes.
protected:
// ...
virtual void OnKickPlayerFromPartyComplete(bool bWasSuccessful, const FUniqueNetId& KickedPlayer) override;Next, declare a delegate that will be called when the kick player from the party process completes. You can use this delegate's wrapper to trigger events when the kick player from the party process completes.
public:
// ...
virtual FOnKickPlayerComplete* GetOnKickPlayerFromPartyDelegate() override
{
return &OnKickPlayerFromPartyCompleteDelegate;
}private:
// ...
FOnKickPlayerComplete OnKickPlayerFromPartyCompleteDelegate;Now, define these functions starting with
KickPlayerFromParty()
. Open thePartyOnlineSession_Starter
class CPP file and add the code below. This function kicks the target player from the party. The callback will be handled by theOnKickPlayerFromPartyComplete()
function.void UPartyOnlineSession_Starter::KickPlayerFromParty(const int32 LocalUserNum, const FUniqueNetIdPtr& KickedPlayer)
{
if (!GetABSessionInt())
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot kick a player from the party. The session interface is not valid."));
return;
}
if (!KickedPlayer)
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot kick a player from the party. KickedPlayer's NetId is not valid."));
return;
}
const APlayerController* PC = GetPlayerControllerByLocalUserNum(LocalUserNum);
if (!PC)
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot kick a player from the party. Kicker's PlayerController is not valid."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, KickedPlayer]()
{
OnKickPlayerFromPartyComplete(false, KickedPlayer.ToSharedRef().Get());
}));
return;
}
const FUniqueNetIdPtr PlayerNetId = GetLocalPlayerUniqueNetId(PC);
if (!PlayerNetId)
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot kick a player from the party. Kicker's NetId is not valid."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, KickedPlayer]()
{
OnKickPlayerFromPartyComplete(false, KickedPlayer.ToSharedRef().Get());
}));
return;
}
UE_LOG_PARTYESSENTIALS(Log, TEXT("Kick party member."));
GetABSessionInt()->KickPlayer(
PlayerNetId.ToSharedRef().Get(),
GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession),
KickedPlayer.ToSharedRef().Get(),
FOnKickPlayerComplete::CreateUObject(this, &ThisClass::OnKickPlayerFromPartyComplete));
}Next, define the
OnKickPlayerFromPartyComplete()
function by adding the code below. This function executes the on-complete delegate you defined in the Header file earlier.void UPartyOnlineSession_Starter::OnKickPlayerFromPartyComplete(bool bWasSuccessful, const FUniqueNetId& KickedPlayer)
{
const FUniqueNetIdAccelByteUserRef KickedPlayerABId = StaticCastSharedRef<const FUniqueNetIdAccelByteUser>(KickedPlayer.AsShared());
if (bWasSuccessful)
{
UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to kick %s from the party."),
KickedPlayerABId->IsValid() ? *KickedPlayerABId->GetAccelByteId() : TEXT("Unknown"));
}
else
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to kick %s from the party."),
KickedPlayerABId->IsValid() ? *KickedPlayerABId->GetAccelByteId() : TEXT("Unknown"));
}
OnKickPlayerFromPartyCompleteDelegate.ExecuteIfBound(bWasSuccessful, KickedPlayer);
OnKickPlayerFromPartyCompleteDelegate.Unbind();
}You have implemented the kicking of players from a party feature. Now, create several functions to listen for when the player is kicked so the kicked player can be informed. Open the
PartyOnlineSession_Starter
class Header file and declare the following function:protected:
// ...
virtual void OnKickedFromParty(FName SessionName) override;Next, declare a delegate that will be called when the player is kicked from the party. You can use this delegate's wrapper to trigger events when the player is kicked from the party.
public:
// ...
virtual FOnKickedFromSession* GetOnKickedFromPartyDelegates() override
{
return &OnKickedFromPartyDelegates;
}private:
// ...
FOnKickedFromSession OnKickedFromPartyDelegates;Now, define the
OnKickedFromParty()
function. Open thePartyOnlineSession_Starter
class CPP file and add the following code. This function broadcasts the on-complete delegate you defined in the Header file and pushes a notification to the player kicked from the party.void UPartyOnlineSession_Starter::OnKickedFromParty(FName SessionName)
{
// Abort if not a party session.
if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
{
return;
}
UE_LOG_PARTYESSENTIALS(Log, TEXT("Current logged player is kicked from the party"));
// Display push notification.
if (GetPromptSubystem())
{
GetPromptSubystem()->PushNotification(KICKED_FROM_PARTY_MESSAGE);
}
OnKickedFromPartyDelegates.Broadcast(SessionName);
}Then, you need to bind the
OnKickedFromParty()
function so it will be called when the player is kicked from the party session. You can do this by adding the code below in the predefinedRegisterOnlineDelegates()
function, which is the first function to be called when the online session is initialized.void UPartyOnlineSession_Starter::RegisterOnlineDelegates()
{
// ...
if (GetABSessionInt())
{
// ...
GetABSessionInt()->OnKickedFromSessionDelegates.AddUObject(this, &ThisClass::OnKickedFromParty);
// ...
}
// ...
}Finally, when the online session is deinitialized, you need to unbind it to stop listening to the player kicked from the party session event. You can do this by adding the code below in the predefined
ClearOnlineDelegates()
function, which is the first function to be called when the online session is deinitialized.void UPartyOnlineSession_Starter::ClearOnlineDelegates()
{
// ...
if (GetABSessionInt())
{
// ...
GetABSessionInt()->OnKickedFromSessionDelegates.RemoveAll(this);
// ...
}
// ...
}
Implement promoting a party member to leader
In this section, you will implement the feature to promote a new party leader.
Open the
PartyOnlineSession_Starter
class Header file and declare the following function:public:
// ...
virtual void PromotePartyLeader(const int32 LocalUserNum, const FUniqueNetIdPtr& NewLeader) override;In the same file, declare a callback function that will be called when the promote party leader process completes.
protected:
// ...
virtual void OnPromotePartyLeaderComplete(const FUniqueNetId& NewLeader, const FOnlineError& Result) override;Next, declare a delegate that will be called when the promote party leader process completes. You can use this delegate's wrapper to trigger events when the promote party leader process completes.
public:
// ...
virtual FOnPromotePartySessionLeaderComplete* GetOnPromotePartyLeaderCompleteDelegate() override
{
return &OnPromotePartyLeaderCompleteDelegate;
}private:
// ...
FOnPromotePartySessionLeaderComplete OnPromotePartyLeaderCompleteDelegate;Now, define these functions starting with the
PromotePartyLeader()
function. Open thePartyOnlineSession_Starter
class CPP file and add the code below. This function promotes a new party leader. Then, the callback will be handled by theOnPromotePartyLeaderComplete()
function.void UPartyOnlineSession_Starter::PromotePartyLeader(const int32 LocalUserNum, const FUniqueNetIdPtr& NewLeader)
{
if (!GetABSessionInt())
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot promote new party leader. The session interface is not valid."));
return;
}
if (!NewLeader)
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot promote new party leader. New Leader NetId is not valid."));
return;
}
const APlayerController* PC = GetPlayerControllerByLocalUserNum(LocalUserNum);
if (!PC)
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot promote new party leader. Promoter's PlayerController is not valid."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, NewLeader]()
{
OnPromotePartyLeaderComplete(NewLeader.ToSharedRef().Get(), FOnlineError(false));
}));
return;
}
const FUniqueNetIdPtr PlayerNetId = GetLocalPlayerUniqueNetId(PC);
if (!PlayerNetId)
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot promote new party leader. Promoter's NetId is not valid."));
ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, NewLeader]()
{
OnPromotePartyLeaderComplete(NewLeader.ToSharedRef().Get(), FOnlineError(false));
}));
return;
}
UE_LOG_PARTYESSENTIALS(Log, TEXT("Promote a new party leader."));
GetABSessionInt()->PromotePartySessionLeader(
PlayerNetId.ToSharedRef().Get(),
GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession),
NewLeader.ToSharedRef().Get(),
FOnPromotePartySessionLeaderComplete::CreateUObject(this, &ThisClass::OnPromotePartyLeaderComplete));
}Next, define the
OnPromotePartyLeaderComplete()
function by adding the code below. This function executes the on-complete delegate you defined in the Header file.void UPartyOnlineSession_Starter::OnPromotePartyLeaderComplete(const FUniqueNetId& NewLeader, const FOnlineError& Result)
{
const FUniqueNetIdAccelByteUserRef NewLeaderABId = StaticCastSharedRef<const FUniqueNetIdAccelByteUser>(NewLeader.AsShared());
if (Result.bSucceeded)
{
UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to promote %s as the new party leader."),
NewLeaderABId->IsValid() ? *NewLeaderABId->GetAccelByteId() : TEXT("Unknown"));
}
else
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to promote %s as the new party leader."),
NewLeaderABId->IsValid() ? *NewLeaderABId->GetAccelByteId() : TEXT("Unknown"));
}
OnPromotePartyLeaderCompleteDelegate.ExecuteIfBound(NewLeader, Result);
OnPromotePartyLeaderCompleteDelegate.Unbind();
}The feature to promote the party leader is complete. When a new party leader is set, the party session update event will be called. You can use this event to inform other party members that a new leader is set. You will learn how to handle party session update events in the next tutorial. For now, create a function to show that a new party leader is set. To do this, you can cache the last party member and compare it with the new party leader. If they are not the same, then a new party leader is set. Open the
PartyOnlineSession_Starter
class Header file and you declare the following variable. You will use this to cache the last party leader.private:
// ...
FUniqueNetIdPtr LastPartyLeader;When the player creates or joins a new party, cache the current party leader. This acts as the initial value for the
LastPartyLeader
variable. Now, open thePartyOnlineSession_Starter
class CPP file and make sure the following highlighted code is included:void UPartyOnlineSession_Starter::OnCreatePartyComplete(FName SessionName, bool bSucceeded)
{
if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
{
return;
}
if (bSucceeded)
{
UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to create a party"));
}
else
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to create a party"));
}
// Cache the party leader.
LastPartyLeader = GetPartyLeader();
// Reset the party member status cache.
PartyMemberStatus.Empty();
OnCreatePartyCompleteDelegates.Broadcast(SessionName, bSucceeded);
}void UPartyOnlineSession_Starter::OnJoinPartyComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
{
return;
}
if (Result == EOnJoinSessionCompleteResult::Type::Success)
{
UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to join a party"));
}
else
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to join a party"));
}
// Cache the party leader.
LastPartyLeader = GetPartyLeader();
// Reset the party member status cache.
PartyMemberStatus.Empty();
OnJoinPartyCompleteDelegates.Broadcast(SessionName, Result);
}Now, create a function to display a notification if a new party leader is set. Open the
PartyOnlineSession_Starter
class Header file and declare the following function. Later, you will call this function when the party session update event is invoked.protected:
// ...
void DisplayCurrentPartyLeader();Next, open the
PartyOnlineSession_Starter
class CPP file and define the function above. This function pushes a notification to show the new party leader information.void UPartyOnlineSession_Starter::DisplayCurrentPartyLeader()
{
// Abort if the party leader is the same.
if (LastPartyLeader && IsPartyLeader(LastPartyLeader))
{
return;
}
LastPartyLeader = GetPartyLeader();
const FUniqueNetIdAccelByteUserPtr LeaderABId = StaticCastSharedPtr<const FUniqueNetIdAccelByteUser>(LastPartyLeader);
// Query party leader information and then display a notification.
if (UStartupSubsystem* StartupSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UStartupSubsystem>())
{
StartupSubsystem->QueryUserInfo(
0,
TPartyMemberArray{ LeaderABId.ToSharedRef() },
FOnQueryUsersInfoCompleteDelegate::CreateWeakLambda(this, [this, LeaderABId](
const FOnlineError& Error, const TArray<TSharedPtr<FUserOnlineAccountAccelByte>>& UsersInfo)
{
if (UsersInfo.IsEmpty() || !UsersInfo[0] || !GetPromptSubystem())
{
return;
}
FUserOnlineAccountAccelByte MemberInfo = *UsersInfo[0];
const FText NotifMessage = FText::Format(PARTY_NEW_LEADER_MESSAGE, FText::FromString(
MemberInfo.GetDisplayName().IsEmpty() ?
UTutorialModuleOnlineUtility::GetUserDefaultDisplayName(LeaderABId.ToSharedRef().Get()) :
MemberInfo.GetDisplayName()
));
FString AvatarURL;
MemberInfo.GetUserAttribute(ACCELBYTE_ACCOUNT_GAME_AVATAR_URL, AvatarURL);
GetPromptSubystem()->PushNotification(NotifMessage, AvatarURL, true);
}));
}
}
Handle party session updates
The party session will be updated when party-related events occur. For example, when a new party member joins or leaves, a new party leader is set, etc. In this section, you will implement some features to handle those party session updates.
Open the
PartyOnlineSession_Starter
class Header file and declare the following variable:private:
// ...
TMap<FString, bool> PartyMemberStatus;In the same file, declare the following functions:
protected:
// ...
virtual void OnPartyMembersChange(FName SessionName, const FUniqueNetId& Member, bool bJoined) override;
virtual void OnPartySessionUpdateReceived(FName SessionName) override;In the same file, declare delegates and their wrapper to be broadcasted by the function you just declared above.
public:
// ...
virtual FOnSessionParticipantsChange* GetOnPartyMembersChangeDelegates() override
{
return &OnPartyMembersChangeDelegates;
}
virtual FOnSessionUpdateReceived* GetOnPartySessionUpdateReceivedDelegates() override
{
return &OnPartySessionUpdateReceivedDelegates;
}private:
// ...
FOnSessionParticipantsChange OnPartyMembersChangeDelegates;
FOnSessionUpdateReceived OnPartySessionUpdateReceivedDelegates;Define these functions starting with
OnPartyMembersChange()
by adding the code below. This function will be called when the party member changes (e.g., when the party member has joined or left). This function will push a notification to show which party member has joined or left the party. It also calls the function you created in the previous section (Implement promoting a party member to leader) to show notifications regarding the new party leader. It will also broadcast the on-complete delegate you defined in the Header file.void UPartyOnlineSession_Starter::OnPartyMembersChange(FName SessionName, const FUniqueNetId& Member, bool bJoined)
{
// Abort if not a party session.
if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
{
return;
}
// Store status whether the member is the current logged-in player.
FUniqueNetIdPtr UserId = nullptr;
if (GetIdentityInt())
{
UserId = GetIdentityInt()->GetUniquePlayerId(0);
}
const bool bIsMemberTheLoggedInPlayer = UserId && UserId.ToSharedRef().Get() == Member;
const FUniqueNetIdAccelByteUserRef MemberABId = StaticCastSharedRef<const FUniqueNetIdAccelByteUser>(Member.AsShared());
const FString MemberABIdStr = MemberABId->GetAccelByteId();
/* Since this event could be called multiple times, we cache the party member status.
* This cache is used to execute the following functionalities only when the party member status is changed (not the same status).*/
if (PartyMemberStatus.Contains(MemberABIdStr))
{
if (PartyMemberStatus[MemberABIdStr] == bJoined)
{
// Abort if the status is the same.
return;
}
PartyMemberStatus[MemberABIdStr] = bJoined;
}
if (!PartyMemberStatus.Contains(MemberABIdStr))
{
PartyMemberStatus.Add(MemberABIdStr, bJoined);
}
UE_LOG_PARTYESSENTIALS(Log, TEXT("Party participant %s %s to/from the party"),
MemberABId->IsValid() ? *MemberABId->GetAccelByteId() : TEXT("Unknown"),
bJoined ? TEXT("joined") : TEXT("left"));
// Query member information then display a push notification to show who joined/left the party.
if (!bIsMemberTheLoggedInPlayer)
{
if (UStartupSubsystem* StartupSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UStartupSubsystem>())
{
StartupSubsystem->QueryUserInfo(
0,
TPartyMemberArray{ MemberABId },
FOnQueryUsersInfoCompleteDelegate::CreateWeakLambda(this, [this, MemberABId, bJoined](
const FOnlineError& Error,
const TArray<TSharedPtr<FUserOnlineAccountAccelByte>>& UsersInfo)
{
if (UsersInfo.IsEmpty() || !UsersInfo[0] || !GetPromptSubystem())
{
return;
}
FUserOnlineAccountAccelByte MemberInfo = *UsersInfo[0];
const FString MemberDisplayName = MemberInfo.GetDisplayName().IsEmpty() ?
UTutorialModuleOnlineUtility::GetUserDefaultDisplayName(MemberABId.Get()) :
MemberInfo.GetDisplayName();
const FText NotifMessage = bJoined ?
FText::Format(PARTY_MEMBER_JOINED_MESSAGE, FText::FromString(MemberDisplayName)) :
FText::Format(PARTY_MEMBER_LEFT_MESSAGE, FText::FromString(MemberDisplayName));
FString AvatarURL;
MemberInfo.GetUserAttribute(ACCELBYTE_ACCOUNT_GAME_AVATAR_URL, AvatarURL);
GetPromptSubystem()->PushNotification(NotifMessage, AvatarURL, true);
}));
}
}
// Show notification if a new party leader is set.
DisplayCurrentPartyLeader();
OnPartyMembersChangeDelegates.Broadcast(SessionName, Member, bJoined);
}The function above caches the party member status (either joined or left) in the
PartyMemberStatus
variable. This variable is a helper to make sure we handleOnPartyMembersChange()
only if the party member has actually changed. Since it is a cache, we need to clean it up when necessary. Thus, make sure the following highlighted code is included:void UPartyOnlineSession_Starter::OnCreatePartyComplete(FName SessionName, bool bSucceeded)
{
if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
{
return;
}
if (bSucceeded)
{
UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to create a party"));
}
else
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to create a party"));
}
// Cache the party leader.
LastPartyLeader = GetPartyLeader();
// Reset the party member status cache.
PartyMemberStatus.Empty();
OnCreatePartyCompleteDelegates.Broadcast(SessionName, bSucceeded);
}void UPartyOnlineSession_Starter::OnJoinPartyComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
{
return;
}
if (Result == EOnJoinSessionCompleteResult::Type::Success)
{
UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to join a party"));
}
else
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to join a party"));
}
// Cache the party leader.
LastPartyLeader = GetPartyLeader();
// Reset the party member status cache.
PartyMemberStatus.Empty();
OnJoinPartyCompleteDelegates.Broadcast(SessionName, Result);
}Next, define the
OnPartySessionUpdateReceived()
function. This function will be called when the party session is updated, such as when a new party leader is set or when it receives any session updates from the backend. This tutorial uses this event to also push a notification to show who the new party leader is. It will then broadcast the on-complete delegate you defined in the Header file.void UPartyOnlineSession_Starter::OnPartySessionUpdateReceived(FName SessionName)
{
// Abort if not a party session.
if (SessionName != GetPredefinedSessionNameFromType(EAccelByteV2SessionType::PartySession))
{
return;
}
UE_LOG_PARTYESSENTIALS(Log, TEXT("Party session is updated"));
// Show notification if a new party leader is set.
DisplayCurrentPartyLeader();
OnPartySessionUpdateReceivedDelegates.Broadcast(SessionName);
}Now, you need to bind the functions you just defined so they will be called by the respective party events. You can do this by adding the code below in the predefined
RegisterOnlineDelegates()
function, which is the first function to be called when the online session is initialized.void UPartyOnlineSession_Starter::RegisterOnlineDelegates()
{
// ...
if (GetABSessionInt())
{
// ...
GetABSessionInt()->OnSessionParticipantsChangeDelegates.AddUObject(this, &ThisClass::OnPartyMembersChange);
GetABSessionInt()->OnSessionUpdateReceivedDelegates.AddUObject(this, &ThisClass::OnPartySessionUpdateReceived);
}
// ...
}Finally, you need to unbind those functions so they won't be called when the online session is deinitialized. You can do this by adding the code below in the predefined
ClearOnlineDelegates()
function, which is the first function to be called when the online session is deinitialized.void UPartyOnlineSession_Starter::ClearOnlineDelegates()
{
// ...
if (GetABSessionInt())
{
// ...
GetABSessionInt()->OnSessionParticipantsChangeDelegates.RemoveAll(this);
GetABSessionInt()->OnSessionUpdateReceivedDelegates.RemoveAll(this);
}
// ...
}
Handle restored party session
When the player is in a party but then quits the game application and logs in to the game again, you can restore the last player's party session. Then, you can decide whether your game can rejoin the restored party or leave it. Byte Wars leaves the restored party sessions so the players can create a new party when they log in to the game again. In this section, you will learn how to do that.
Open the
PartyOnlineSession_Starter
class Header file and declare the following function:protected:
// ...
void OnConnectLobbyComplete(int32 LocalUserNum, bool bSucceeded, const FUniqueNetId& UserId, const FString& Error);Next, open the
PartyOnlineSession_Starter
class CPP file and define theOnConnectLobbyComplete()
function. The game will call this function when the player connects to the lobby, which is the event when the player has successfully logged in and is ready to use AccelByte Gaming Services (AGS) online sessions. This function then restores and leaves the restored party sessions if there are any.void UPartyOnlineSession_Starter::OnConnectLobbyComplete(int32 LocalUserNum, bool bSucceeded, const FUniqueNetId& UserId, const FString& Error)
{
if (!bSucceeded)
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Cannot restore and leave restored party session. Failed to connect to lobby. Error: %s."), *Error);
return;
}
// Restore and leave old party session.
GetABSessionInt()->RestoreActiveSessions(
UserId,
FOnRestoreActiveSessionsComplete::CreateWeakLambda(this, [this](const FUniqueNetId& LocalUserId, const FOnlineError& Result)
{
// Abort if failed to restore party sessions.
if (!Result.bSucceeded)
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to restore party session. Error: %s"), *Result.ErrorMessage.ToString());
return;
}
// Abort if the session interface is invalid.
if (!GetABSessionInt())
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to restore party session. The session interface is not valid."));
return;
}
const TArray<FOnlineRestoredSessionAccelByte> RestoredParties = GetABSessionInt()->GetAllRestoredPartySessions();
// If no restored party session, do nothing.
if (RestoredParties.IsEmpty())
{
UE_LOG_PARTYESSENTIALS(Log, TEXT("No restored party session found. Do nothing."));
return;
}
// Leave the first restored active party session.
UE_LOG_PARTYESSENTIALS(Log, TEXT("Restored party session found. Leave the restored party session."));
GetABSessionInt()->LeaveRestoredSession(
LocalUserId,
RestoredParties[0],
FOnLeaveSessionComplete::CreateWeakLambda(this, [](bool bWasSuccessful, FString SessionId)
{
if (bWasSuccessful)
{
UE_LOG_PARTYESSENTIALS(Log, TEXT("Success to leave restored party session."));
}
else
{
UE_LOG_PARTYESSENTIALS(Warning, TEXT("Failed to leave restored party session."));
}
}
));
})
);
}tipIf you want your game to rejoin the restored party session, you can use the
JoinParty()
function you created before.Now, you need to bind the function you just defined so it will be called by the event. You can do this by adding the code below in the predefined
RegisterOnlineDelegates()
function, which is the first function to be called when the online session is initialized.void UPartyOnlineSession_Starter::RegisterOnlineDelegates()
{
// ...
if (GetABIdentityInt())
{
GetABIdentityInt()->OnConnectLobbyCompleteDelegates->AddUObject(this, &ThisClass::OnConnectLobbyComplete);
}
}Finally, you need to unbind that function so it won't be called when the online session is deinitialized. You can do this by adding the code below in the predefined
ClearOnlineDelegates()
function, which is the first function to be called when the online session is deinitialized.void UPartyOnlineSession_Starter::ClearOnlineDelegates()
{
// ...
if (GetABIdentityInt())
{
GetABIdentityInt()->OnConnectLobbyCompleteDelegates->RemoveAll(this);
}
}
Resources
The files used in this tutorial are available in the Unreal Byte Wars GitHub repository.