Skip to main content

Implement Party with Unreal Engine SDK

Last updated on May 16, 2024

Introduction

danger

This topic is specific to the AccelByte Gaming Services (AGS) Starter tier.

AccelByte Gaming Services (AGS) Party allows you to enable your players to create a party with other players. This article will show you how to implement AGS Party into your Unreal Engine game with AGS Starter tier.

Prerequisites

This guide assumes that you have already implemented AGS Lobby and Friends. Ensure sure your players can successfully connect to Lobby and invite their friends before proceeding.

note

Lobby has two function types:

  • Response delegate: The delegate accesses the result response from the SDK's functions. Not necessary, but, if used, must be defined before calling the Request function.
  • Request function: This function sends a request for services to the Websocket API. Can be called separately from the Response delegate in a different function.

Since party implementation can vary for each game, you can familiarize yourself with other concepts and classes in the AccelByteLobbyModels header file inside the AGS Game SDK plugin. For some implementations, you don't need to implement the Friends service first, since party invitations only require user IDs.

Quick reference

References
#include "OnlineSessionInterfaceV2AccelByte.h"
#include "OnlineSubsystemAccelByteSessionSettings.h"
#include "OnlineSubsystemSessionSettings.h"
Party notification events
// Listen for party invitations
SessionInterface->AddOnV2SessionInviteReceivedDelegate_Handle(FOnV2SessionInviteReceivedDelegate::CreateLambda( [](const FUniqueNetId &UserId, const FUniqueNetId &, const FOnlineSessionInviteAccelByte &InviteResult)
{
if (InviteResult.SessionType == EAccelByteV2SessionType::PartySession)
{
// Handle on party session invite notifications
// InviteResult contains the party details
}
}));

// Listen for kicked from party session notifications
SessionInterface->AddOnKickedFromSessionDelegate_Handle(FOnKickedFromSessionDelegate::CreateLambda([](FName SessionName)
{
if (SessionName == NAME_PartySession)
{
// player kicked from party
}
}));

SessionInterface->AddOnSessionParticipantsChangeDelegate_Handle(FOnSessionParticipantsChangeDelegate::CreateLambda([](FName SessionName, const FUniqueNetId& Member, bool bJoined)
{
if (SessionName == NAME_PartySession)
{
// Member left party if bJoined is false
// Member joined party if bJoined is true
}
}));

SessionInterface->AddOnSessionInviteRejectedDelegate_Handle(
FOnSessionInviteRejectedDelegate::CreateLambda([](FName SessionName, const FUniqueNetId& RejecterId)
{
if (SessionName == NAME_PartySession)
{
// party invitation Rejected
}
}));

SessionInterface->AddOnSessionMemberStorageUpdateReceivedDelegate_Handle(FOnSessionMemberStorageUpdateReceivedDelegate::CreateLambda([&SessionInterface](FName SessionName, const FUniqueNetId& UpdatedMemberId)
{
if (SessionName == NAME_PartySession)
{
// Party member storage updated
const FNamedOnlineSession* PartySession = SessionInterface->GetNamedSession(SessionName);
const TSharedPtr<FOnlineSessionInfoAccelByteV2> SessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2>(PartySession->SessionInfo);
TUniqueNetIdMap<FJsonObjectWrapper> MemberStorages;
SessionInfo->GetAllSessionMemberStorage(MemberStorages);
}
}));

SessionInterface->AddOnSessionLeaderStorageUpdateReceivedDelegate_Handle(FOnSessionLeaderStorageUpdateReceivedDelegate::CreateLambda([&SessionInterface](FName SessionName)
{
if (SessionName == NAME_PartySession)
{
// Party member storage updated
const FNamedOnlineSession* PartySession = SessionInterface->GetNamedSession(SessionName);
const TSharedPtr<FOnlineSessionInfoAccelByteV2> SessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2>(PartySession->SessionInfo);
FJsonObjectWrapper LeaderStorage;
SessionInfo->GetSessionLeaderStorage(LeaderStorage);
}
}));
Create Party
SessionInterface->AddOnCreateSessionCompleteDelegate_Handle(  FOnCreateSessionCompleteDelegate::CreateLambda([](const FName SessionName, const bool bWasSuccessful)
{
if (SessionName == NAME_PartySession)
{
if (bWasSuccessful)
{
// Handle party creation
}

// Handle party creation errors
}
}));

FOnlineSessionSettings NewSessionSettings;
NewSessionSettings.NumPrivateConnections = 4;

NewSessionSettings.Set(SETTING_SESSION_JOIN_TYPE, TEXT("INVITE_ONLY"));
NewSessionSettings.Set(SETTING_SESSION_TYPE, SETTING_SESSION_TYPE_PARTY_SESSION);
NewSessionSettings.Set(SETTING_SESSION_TEMPLATE_NAME, TEXT("TemplateNameFromAdminPortal"));

SessionInterface->CreateSession(LocalPlayerId.ToSharedRef().Get(), NAME_PartySession, NewSessionSettings);
Invite to Party
SessionInterface->AddOnSendSessionInviteCompleteDelegate_Handle(FOnSendSessionInviteCompleteDelegate::CreateLambda([](const FUniqueNetId& LocalSenderId, FName SessionName, bool bWasSuccessful, const FUniqueNetId& InviteeId)
{
if (SessionName == NAME_PartySession && bWasSuccessful)
{
// Send party invite success
}
}));

SessionInterface->SendSessionInviteToFriend(LocalPlayerId.ToSharedRef().Get(), NAME_PartySession, OtherUserPlayerId.ToSharedRef().Get());
Leave party
auto PartySession = SessionInterface->GetNamedSession(NAME_PartySession);
auto PartySessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2> (PartySession->SessionInfo);
FString PartyId = PartySessionInfo->GetBackendSessionDataAsPartySession()->ID;

FOnLeaveSessionComplete OnLeavePartyComplete =
FOnLeaveSessionComplete::CreateLambda([](const bool bWasSuccessful, FString SessionId)
{
if (bWasSuccessful)
{
// Successfully left a party
}
});

SessionInterface->LeaveSession(LocalPlayerId.ToSharedRef().Get(), EAccelByteV2SessionType::PartySession, PartyId, OnLeavePartyComplete);
Promote to Party leader
// Listen for party session updates
SessionInterface->AddOnSessionUpdateReceivedDelegate_Handle(FOnSessionUpdateReceivedDelegate::CreateLambda([SessionInterface](FName SessionName)
{
if (SessionName == NAME_PartySession)
{
// there is an update notification for the party
auto LatestPartySession = SessionInterface->GetNamedSession(NAME_PartySession);
}
}));

// Promote other user
FOnPromotePartySessionLeaderComplete OnPromotePartySessionLeaderComplete =
FOnPromotePartySessionLeaderComplete::CreateLambda([](const FUniqueNetId &PromotedUserId, const FOnlineError &Result)
{
if (Result.bSucceeded)
{
// successfully promoted new party leader
}
});

SessionInterface->PromotePartySessionLeader(LocalPlayerId.ToSharedRef().Get(), NAME_PartySession, OtherUserPlayerId.ToSharedRef().Get(), OnPromotePartySessionLeaderComplete);
Kick from Party
// Listen for kicked from party session notifications
SessionInterface->AddOnKickedFromSessionDelegate_Handle(FOnKickedFromSessionDelegate::CreateLambda([](FName SessionName)
{
if (SessionName == NAME_PartySession)
{
// player kicked from party
}
}));

// Kick player from party session
FOnKickPlayerComplete OnKickPlayerComplete =
FOnKickPlayerComplete::CreateLambda([](const bool bWasSuccessful, const FUniqueNetId &KickedPlayerId)
{
if (bWasSuccessful)
{
// Player successfully kicked from the party
}
});

SessionInterface->KickPlayer(LocalPlayerId.ToSharedRef().Get(), NAME_PartySession, PlayerIdToKick.ToSharedRef().Get());
Join Party
// Listen for party invitations
SessionInterface->AddOnV2SessionInviteReceivedDelegate_Handle(FOnV2SessionInviteReceivedDelegate::CreateLambda( [](const FUniqueNetId &UserId, const FUniqueNetId &, const FOnlineSessionInviteAccelByte &InviteResult)
{
if (InviteResult.SessionType == EAccelByteV2SessionType::PartySession)
{
// Handle on party session invite notifications
// InviteResult contains the party details
}
}));

// Add delegate on join party session completed
SessionInterface->AddOnJoinSessionCompleteDelegate_Handle( FOnJoinSessionCompleteDelegate::CreateLambda([](FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
if (SessionName == NAME_PartySession)
{
// Join party session completed, check the Result
}
}));

// Join the party session
FOnlineSessionInviteAccelByte Invitation; // The invitation from the notification
SessionInterface->JoinSession(*LocalPlayerId.Get(), NAME_PartySession, Invitation.Session);
Reject Party invitation
FOnRejectSessionInviteComplete OnRejectSessionInviteComplete = FOnRejectSessionInviteComplete::CreateLambda([](const bool bWasSuccessful)
{
if (bWasSuccessful)
{
// successfully reject party invitation
}
});

// Join the party session
FOnlineSessionInviteAccelByte Invitation; // The invitation from the notification
SessionInterface->RejectInvite(LocalPlayerId.ToSharedRef().Get(), Invitation, OnRejectSessionInviteComplete);
Get current Party info
const FNamedOnlineSession* PartySession = SessionInterface->GetNamedSession(NAME_PartySession);
const TSharedPtr<FOnlineSessionInfoAccelByteV2> SessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2>(PartySession->SessionInfo);
TSharedPtr<FAccelByteModelsV2BaseSession> SessionData = SessionInfo->GetBackendSessionData();
Get user storage
const FNamedOnlineSession* PartySession = SessionInterface->GetNamedSession(NAME_PartySession);
const TSharedPtr<FOnlineSessionInfoAccelByteV2> SessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2>(PartySession->SessionInfo);
TUniqueNetIdMap<FJsonObjectWrapper> MemberStorages;
SessionInfo->GetAllSessionMemberStorage(MemberStorages);
FJsonObjectWrapper LeaderStorage;
SessionInfo->GetSessionLeaderStorage(LeaderStorage);

Quick start

Follow the steps in these sections to get started with AGS Party quickly.

Create a party implementation

  1. Set the Maximum Party Member value and Auto kick on disconnect features from the Party Configurations in the Admin Portal.

  2. Create a User Widget C++ class called AccelByteParty to accommodate the functions for the Party Menu widget.

  3. Add the following header to ensure the functions work correctly for AGS Lobby features:

    .cpp

    ...
    #include "OnlineSessionInterfaceV2AccelByte.h"
    #include "OnlineSubsystemAccelByteSessionSettings.h"
    #include "OnlineSubsystemSessionSettings.h"
    ...

Create a party

This function returns a result from the FNamedOnlineSession class type, which contains party data such as:

  • PartyId
  • LeaderId, the leader's userId
  • Members, array of the party members' userIds
  • Invitees, array of the invitees' userIds
  • InvitationToken, the party invitation's token
void UAccelByteParty::OnClickedCreateParty()
{
SessionInterface->AddOnCreateSessionCompleteDelegate_Handle( FOnCreateSessionCompleteDelegate::CreateLambda([](const FName SessionName, const bool bWasSuccessful)
{
if (SessionName == NAME_PartySession)
{
if (bWasSuccessful)
{
UE_LOG(LogTemp, Log, TEXT("Create Party Success!"));
}

UE_LOG(LogTemp, Error, TEXT("Create Party Error!"));
}
}));

FOnlineSessionSettings NewSessionSettings;
NewSessionSettings.NumPrivateConnections = 4;

NewSessionSettings.Set(SETTING_SESSION_JOIN_TYPE, TEXT("INVITE_ONLY"));
NewSessionSettings.Set(SETTING_SESSION_TYPE, SETTING_SESSION_TYPE_PARTY_SESSION);
NewSessionSettings.Set(SETTING_SESSION_TEMPLATE_NAME, TEXT("TemplateNameFromAdminPortal"));

SessionInterface->CreateSession(LocalPlayerId.ToSharedRef().Get(), NAME_PartySession, NewSessionSettings);
}

Invite friend to a party

To send an invitation, you need the UserID of the player you want to invite, so add inviteeUserId as the function's parameter. Other party members can also invite people to the party.

void UAccelByteParty::OnClickedInviteParty()
{
SessionInterface->AddOnSendSessionInviteCompleteDelegate_Handle(FOnSendSessionInviteCompleteDelegate::CreateLambda([](const FUniqueNetId& LocalSenderId, FName SessionName, bool bWasSuccessful, const FUniqueNetId& InviteeId)
{
if (SessionName == NAME_PartySession && bWasSuccessful)
{
UE_LOG(LogTemp, Log, TEXT("Send Party Invite Success!"));
}
}));

SessionInterface->SendSessionInviteToFriend(LocalPlayerId.ToSharedRef().Get(), NAME_PartySession, OtherUserPlayerId.ToSharedRef().Get());
}

Leave a party

Use this function to leave a party.

void UAccelByteParty::OnClickedLeaveParty()
{
auto PartySession = SessionInterface->GetNamedSession(NAME_PartySession);
auto PartySessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2> (PartySession->SessionInfo);
FString PartyId = PartySessionInfo->GetBackendSessionDataAsPartySession()->ID;

FOnLeaveSessionComplete OnLeavePartyComplete =
FOnLeaveSessionComplete::CreateLambda([](const bool bWasSuccessful, FString SessionId)
{
if (bWasSuccessful)
{
UE_LOG(LogTemp, Log, TEXT("Leave Party Success!"));
}
});

SessionInterface->LeaveSession(LocalPlayerId.ToSharedRef().Get(), EAccelByteV2SessionType::PartySession, PartyId, OnLeavePartyComplete);
}

Promote a friend to party leader

You need the UserID of the Party member you want to promote to the party leader.

void UAccelByteParty::OnClickedPartyLeader()
{
FOnPromotePartySessionLeaderComplete OnPromotePartySessionLeaderComplete =
FOnPromotePartySessionLeaderComplete::CreateLambda([](const FUniqueNetId &PromotedUserId, const FOnlineError &Result)
{
if (Result.bSucceeded)
{
UE_LOG(LogTemp, Log, TEXT("Promote Party Leader Success!"));
}
});

SessionInterface->PromotePartySessionLeader(LocalPlayerId.ToSharedRef().Get(), NAME_PartySession, OtherUserPlayerId.ToSharedRef().Get(), OnPromotePartySessionLeaderComplete);
}

Kick a friend from a party

You need the UserID of the party member you want kicked from the party.

void UAccelByteParty::OnClickedKickParty()
{
FOnKickPlayerComplete OnKickPlayerComplete =
FOnKickPlayerComplete::CreateLambda([](const bool bWasSuccessful, const FUniqueNetId &KickedPlayerId)
{
if (bWasSuccessful)
{
UE_LOG(LogTemp, Log, TEXT("Kick Party Member Success!"));
}
});

SessionInterface->KickPlayer(LocalPlayerId.ToSharedRef().Get(), NAME_PartySession, PlayerIdToKick.ToSharedRef().Get());
}

  1. Most of the Party Invitation UIs will be spawnable widgets. Create a new user widget class called AccelBytePartyInvitationPopUp and add the following header on the top:

    .cpp

    ...
    #include "OnlineSessionInterfaceV2AccelByte.h"
    #include "OnlineSubsystemAccelByteSessionSettings.h"
    #include "OnlineSubsystemSessionSettings.h"
    ...

    This class will hold functions that handle events related to the Party Invitation. You can parent this class to your Party Invitation widget later on.

  2. Add the following functions to handle party invitations in the AccelBytePartyInvitationPopUp class:

Handle party invitations

Follow the steps in this section to implement handling party invitation.

Accept party invitation (join party)

This function requires partyId and invitationToken which are contained in a PartyInvitation object.

  void UAccelBytePartyInvitationPopUp::OnClickedAcceptPartyInvitation()
{
SessionInterface->AddOnJoinSessionCompleteDelegate_Handle( FOnJoinSessionCompleteDelegate::CreateLambda([](FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
if (SessionName == NAME_PartySession)
{
UE_LOG(LogTemp, Log, TEXT("Join Party Success!"));
}
}));

// Join the party session
FOnlineSessionInviteAccelByte Invitation; // The invitation from the notification
SessionInterface->JoinSession(*LocalPlayerId.Get(), NAME_PartySession, Invitation.Session);
}

Reject party invitation

This function requires partyId and invitationToken, which are contained in a PartyInvitation object.

void UAccelBytePartyInvitationPopUp::OnClickedRejectPartyInvitation()
{
FOnRejectSessionInviteComplete OnRejectSessionInviteComplete = FOnRejectSessionInviteComplete::CreateLambda([](const bool bWasSuccessful)
{
if (bWasSuccessful)
{
UE_LOG(LogTemp, Log, TEXT("Reject Party Success!"));
}
else
{
UE_LOG(LogTemp, Error, TEXT("Reject Party Error! %d: %s"), ErrorCode, *ErrorMessage);
}
});

FOnlineSessionInviteAccelByte Invitation; // The invitation from notification
SessionInterface->RejectInvite(LocalPlayerId.ToSharedRef().Get(), Invitation, OnRejectSessionInviteComplete);
}

  1. Use the result value from the SessionInterface->AddOnV2SessionInviteReceivedDelegate_Handle() event for the FOnlineSessionInviteAccelByte value.

  2. To send a notification to a player on any activity related to the current party, go back to the AccelByteParty class and create a new function to get all Party Notification Events with AGS Lobby.

  3. Add a log for each event:

    void UAccelByteParty::SetNotificationDelegate()
    {
    // Listen for party invitation
    SessionInterface->AddOnV2SessionInviteReceivedDelegate_Handle(FOnV2SessionInviteReceivedDelegate::CreateLambda( [](const FUniqueNetId &UserId, const FUniqueNetId &, const FOnlineSessionInviteAccelByte &InviteResult)
    {
    if (InviteResult.SessionType == EAccelByteV2SessionType::PartySession)
    {
    UE_LOG(LogTemp, Log, TEXT("Received a party invitation!"));
    }
    }));

    // Listen for kicked from party session notification
    SessionInterface->AddOnKickedFromSessionDelegate_Handle(FOnKickedFromSessionDelegate::CreateLambda([](FName SessionName)
    {
    if (SessionName == NAME_PartySession)
    {
    UE_LOG(LogTemp, Log, TEXT("Kicked from a party!"));
    }
    }));

    SessionInterface->AddOnSessionParticipantsChangeDelegate_Handle(FOnSessionParticipantsChangeDelegate::CreateLambda([](FName SessionName, const FUniqueNetId& Member, bool bJoined)
    {
    if (SessionName == NAME_PartySession)
    {
    if(bJoined)
    {
    UE_LOG(LogTemp, Log, TEXT("User joined party!"));
    }
    else
    {
    UE_LOG(LogTemp, Log, TEXT("User left party!"));
    }
    }
    }));

    SessionInterface->AddOnSessionInviteRejectedDelegate_Handle(
    FOnSessionInviteRejectedDelegate::CreateLambda([](FName SessionName, const FUniqueNetId& RejecterId)
    {
    if (SessionName == NAME_PartySession)
    {
    UE_LOG(LogTemp, Log, TEXT("Invitation rejected!"));
    }
    }));
    }
  4. You may wish to retrieve the party data. The code snippet below shows how to get party data from SessionInterface:

    void UAccelByteParty::SetNotificationDelegate()
    {
    ...
    SessionInterface->AddOnSessionUpdateReceivedDelegate_Handle(FOnSessionUpdateReceivedDelegate::CreateLambda([SessionInterface](FName SessionName)
    {
    if (SessionName == NAME_PartySession)
    {
    // there is update notification for party
    auto LatestPartySession = SessionInterface->GetNamedSession(NAME_PartySession);
    UE_LOG(LogTemp, Log, TEXT("On any party data update"));
    }
    }));
    }
  5. You may wish to unbind delegates from SessionInterface using the method below:

    ...
    SessionInterface->ClearOnSessionParticipantsChangeDelegate_Handle(OnSessionParticipantsChangeDelegate)
    SessionInterface->ClearOnSessionInviteRejectedDelegate_Handle(OnSessionInviteRejectedDelegate);
    ...

Congratulations! You have successfully implemented AGS Party.

Step-by-Step guide

Follow the steps in this section for more detailed step-by-step instructions for implementing AGS Party for AGS Starter tier.

Implement the UI

  1. Create a widget blueprint class called WB_Party and re-parent the WB_Party widget blueprint to the AccelByteParty C++ class.

  2. Add the following objects to the Party widget blueprint:

    • Text box for Party Id.
    • Button to create a party.
    • Any box for the party members list.
    • Button to leave the party.
  3. To display a party member's information, create a widget blueprint class called WB_PartyPlayerEntry.

  4. Add the following objects to the WB_PartyPlayerEntry widget blueprint:

    • Text box to display the player's username.
    • Button to promote a party leader.
    • Button to kick a player from the party.
    • Image for the party leader icon.

    To send a party invitation, you will need a spawnable widget.

  5. Create a widget blueprint class called WB_InvitationParty.

  6. Re-parent the WB_InvitationParty widget blueprint to the AccelBytePartyInvitationPopUp C++ class.

  7. Add the following objects to the Party Invitation PopUp widget blueprint:

    • Text box for invitation text.
    • Button to accept the party invitation.
    • Button to reject the party invitation.
  8. To prepare the Notification Box widget, start by creating a new widget blueprint class called WB_LobbyNotificationEntry.

  9. Add a text box for the notification text to the WB_LobbyNotificationEntry widget blueprint.

  10. Create another widget blueprint class called WB_Notification.

  11. Add the following objects to the WB_Notification widget blueprint:

    • Text box for the subheader text.
    • List view to add the Lobby Notification entry dynamically.
    note

    Don't forget to set the Entry Widget Class under the List Entries component with WB_LobbyNotificationEntry.

  12. Since the party functionality is part of the Lobby Menu in this example, add the following object components in WB_LobbyMenu:

    • Scroll box to add the WB_PartyMenu.
    • Border for the Notification Box widget blueprint to display the notification update to players.

Implement the code

Add the code from the examples in this section.

Tutorial menu HUD

Before you move on, prepare all of the party-related widgets so they are accessible to other classes via the TutorialMenuHUD class.

  1. Do this by adding all of the party-related classes to the top of the TutorialMenuHUD class.

    ...
    #include "AccelByte/Party/AccelByteParty.h"
    ...
  2. Initialize the pointer to refer to the Party widget classes in the BeginPlay() function.

    void ATutorialMenuHUD::BeginPlay()
    {
    ...
    check(PartyClass != nullptr);

    ...
    PartyMenu = CreateWidget<UAccelByteParty>(PlayerController, PartyClass.Get());
    ...
    }
  3. Add a getter function for the Party Menu widgets' pointer.

    /**
    * @brief Getter for Party Menu widget
    */
    UAccelByteParty* GetPartyMenu() const {return PartyMenu; }

Party's player entry

  1. Create a user widget C++ class named AccelBytePartyPlayerEntry and set the class as the parent class of your Party Player Entry widget blueprint (in this case, WB_PartyPlayerEntry).

  2. Open the AccelBytePartyPlayerEntry class and add the following to the the top of the class:

    ...
    #include "AccelByteParty.h"
    #include "OnlineSessionInterfaceV2AccelByte.h"
    #include "OnlineSubsystemAccelByteSessionSettings.h"
    #include "OnlineSubsystemSessionSettings.h"
    #include "Components/Image.h"
    #include "Components/TextBlock.h"
    #include "Components/Button.h"
    ...
  3. Specify all the widget components in the AccelBytePartyPlayerEntry header file.

    /**
    * @brief Image to display leader status.
    */
    UPROPERTY(meta = (BindWidget))
    UImage* Img_LeaderIcon;

    /**
    * @brief Text Box for Player Username.
    */
    UPROPERTY(meta = (BindWidget))
    UTextBlock* Tb_PlayerUsername;

    /**
    * @brief Button for Promoting Party Leader.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_PartyLeader;

    /**
    * @brief Button for Kick Party Member.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_KickPlayer;

  4. Create a variable called UserData to store the user data required by the party functionalities. To get UserData from other scripts, create a function that will return the variable.

    FSimpleUserData UAccelBytePartyPlayerEntry::GetUserData() const
    {
    return UserData;
    }

  5. Move the OnClickedPartyLeader() function and the OnClickedKickParty() function from AccelByteParty to the AccelBytePartyPlayerEntry class.

  6. Once you have done that, modify both functions using CreateUObject() instead of CreateWeakLambda() for better readability.

    void UAccelBytePartyPlayerEntry::OnClickedPartyLeader()
    {
    FOnPromotePartySessionLeaderComplete OnPromotePartySessionLeaderComplete =
    FOnPromotePartySessionLeaderComplete::CreateLambda([](const FUniqueNetId &PromotedUserId, const FOnlineError &Result)
    {
    if (Result.bSucceeded)
    {
    UE_LOG(LogTemp, Log, TEXT("Promote Party Leader Success!"));
    }
    else
    {
    UE_LOG(LogTemp, Error, TEXT("Promote Party Leader Failed!"));
    GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, "Unable to Promote Party Leader!");
    }
    });

    SessionInterface->PromotePartySessionLeader(LocalPlayerId.ToSharedRef().Get(), NAME_PartySession, FUniqueNetIdAccelByteUser::Create(UserData.UserId).Get(), OnPromotePartySessionLeaderComplete);
    }

    void UAccelBytePartyPlayerEntry::OnClickedKickParty()
    {
    FOnKickPlayerComplete OnKickPlayerComplete =
    FOnKickPlayerComplete::CreateLambda([](const bool bWasSuccessful, const FUniqueNetId &KickedPlayerId)
    {
    if (bWasSuccessful)
    {
    // Player successfully kicked from the party
    }
    });

    SessionInterface->KickPlayer(LocalPlayerId.ToSharedRef().Get(), NAME_PartySession, FUniqueNetIdAccelByteUser::Create(UserData.UserId).Get());
    }

  7. Add the NativeConstruct() override function and set up the button widgets to be called by their functions on the button clicked.

    void UAccelBytePartyPlayerEntry::NativeConstruct()
    {
    Super::NativeConstruct();

    Btn_PartyLeader->OnClicked.AddUniqueDynamic(this, &UAccelBytePartyPlayerEntry::OnClickedPartyLeader);
    Btn_KickPlayer->OnClicked.AddUniqueDynamic(this, &UAccelBytePartyPlayerEntry::OnClickedKickParty);
    }
  8. Create a function to display the player's display name when the party's Player Entry initializes. Use the User SDK's GetUserByUserId() function to get the display name data.

    void UAccelBytePartyPlayerEntry::InitData(const FString& PlayerId)
    {
    UserData.UserId = PlayerId;

    IdentityInterface->GetApiClient(USER_LOCAL_NUM)->User.GetUserByUserId(
    PlayerId,
    THandler<FSimpleUserData>::CreateUObject(this, &UAccelBytePartyPlayerEntry::OnSuccessGetUserId),
    FErrorHandler::CreateUObject(this, &UAccelBytePartyPlayerEntry::OnFailedGetUserId));
    }
  9. Define what to do after the GetUserByUserId() query result is received in both of the response functions you declared earlier.

    void UAccelBytePartyPlayerEntry::OnSuccessGetUserId(const FSimpleUserData& Data)
    {
    UserData = Data;
    Tb_PlayerUsername->SetText(FText::FromString(UserData.DisplayName));
    }

    void UAccelBytePartyPlayerEntry::OnFailedGetUserId(int32 ErrorCode, const FString& ErrorMessage)
    {
    UE_LOG(LogTemp, Error, TEXT("Get User Id Failed: %d , %s"), ErrorCode, *ErrorMessage);
    }
  10. Create a function to enable visibility of the Promote Leader and Kick Party Member buttons if the player is the party leader.

    void UAccelBytePartyPlayerEntry::EnableAllComponents(bool bIsEnable) const
    {
    if (bEnable)
    {
    Btn_KickPlayer->SetVisibility(ESlateVisibility::Visible);
    Btn_PartyLeader->SetVisibility(ESlateVisibility::Visible);
    }
    else
    {
    Btn_KickPlayer->SetVisibility(ESlateVisibility::Hidden);
    Btn_PartyLeader->SetVisibility(ESlateVisibility::Hidden);
    }
    }

  11. Create a function that will show the party leader's icon image.

    void UAccelBytePartyPlayerEntry::SetImageIconLeader(bool bIsLeader) const
    {
    if (bIsLeader)
    {
    Img_LeaderIcon->SetVisibility(ESlateVisibility::Visible);
    }
    else
    {
    Img_LeaderIcon->SetVisibility(ESlateVisibility::Hidden);
    }
    }

Party menu

  1. Open the AccelByteParty header and class, then add the following includes at the top of the header and class:

    #include "AccelBytePartyPlayerEntry.h"
    #include "Components/ScrollBox.h"
    #include "Components/Button.h"
    #include "Components/Image.h"
    #include "Components/TextBlock.h"
    #include "TutorialProject/TutorialMenuHUD.h"

  2. Specify all the widgets components in the AccelByteParty header file.

    /**
    * @brief Scroll Box to spawn the player entry.
    */
    UPROPERTY(meta = (BindWidget))
    UScrollBox* Sb_Player;

    /**
    * @brief Button for Create party.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_CreateParty;

    /**
    * @brief Button for Leave party.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_LeaveParty;

    /**
    * @brief Text Block for displaying the party ID.
    */
    UPROPERTY(meta = (BindWidget))
    UTextBlock* Tb_PartyID;

  3. Declare a NativeConstruct() override function to initialize the widget components and the TotalPartyMember based on your Max Party Member configuration in the AGS Admin Portal.

    void UAccelByteParty::NativeConstruct()
    {
    Super::NativeConstruct();

    TutorialMenuHUD = Cast<ATutorialMenuHUD>(GetOwningPlayer()->GetHUD());

    Btn_CreateParty->OnClicked.AddUniqueDynamic(this, &UAccelByteParty::OnClickedCreateParty);
    Btn_LeaveParty->OnClicked.AddUniqueDynamic(this, &UAccelByteParty::OnClickedLeaveParty);

    TotalPartyMember = 4;

    SetNotificationDelegate();
    }
  4. Create a new function to enable and disable the create party button and the leave party button, depending on the situation.

    void UAccelByteParty::SetCreatePartyButtonEnabled(bool bIsCreateParty) const
    {
    Btn_CreateParty->SetIsEnabled(bIsCreateParty);
    Btn_LeaveParty->SetIsEnabled(!bIsCreateParty);
    }

  5. Also create a function to display the party leader icon if the player is a party member. To check on each player in the party, create an array that will hold the party's player entry data.

    void UAccelByteParty::SetUpdateLeaderIcon(const FString& LeaderId) const
    {
    for (const TWeakObjectPtr<UAccelBytePartyPlayerEntry>& PlayerEntry : PartyPlayerEntryWidgetArray)
    {
    // Leader entry
    if (LeaderId == PlayerEntry->GetUserData().UserId)
    {
    PlayerEntry->SetImageIconLeader(true);
    PlayerEntry->EnableAllComponents(false);
    }
    // Other entries when local player is leader
    else if (LeaderId == IdentityInterface->GetApiClient(USER_LOCAL_NUM)->CredentialsRef->GetUserId())
    {
    PlayerEntry->SetImageIconLeader(false);

    if (PlayerEntry->GetUserData().UserId.IsEmpty())
    {
    PlayerEntry->EnableAllComponents(false);
    }
    else
    {
    PlayerEntry->EnableAllComponents(true);
    }
    }
    // Other entries when local player is not leader
    else
    {
    PlayerEntry->SetImageIconLeader(false);
    PlayerEntry->EnableAllComponents(false);
    }
    }
    }
  6. Prepare a new function to refresh the Party's Player Entry list. Since you are also going to add the Party Player Entry widget, create a class reference to the WB_PartyPlayerEntry's class.

    void UAccelByteParty::RefreshPartyList()
    {
    Sb_Player->ClearChildren();
    PartyPlayerEntryWidgetArray.Empty();

    if (PartyInfo.PartyId.IsEmpty())
    {
    return;
    }

    for (int i = 0; i < TotalPartyMember; ++i)
    {
    TWeakObjectPtr<UAccelBytePartyPlayerEntry> PlayerEntry = MakeWeakObjectPtr<UAccelBytePartyPlayerEntry>(CreateWidget<UAccelBytePartyPlayerEntry>(this, PartyPlayerEntryClass.Get()));

    PlayerEntry->EnableAllComponents(false);

    PartyPlayerEntryWidgetArray.Add(PlayerEntry);

    if (i < PartyInfo.Members.Num())
    {
    PlayerEntry->InitData(PartyInfo.Members[i]);
    }

    Sb_Player->AddChild(PlayerEntry.Get());
    }

    Tb_PartyID->SetText(FText::FromString(PartyInfo.PartyId));

    SetCreatePartyButtonEnabled(false);
    SetUpdateLeaderIcon(PartyInfo.Leader);
    }

  7. Then, create a new function that will refresh all of the Party Widgets state.

    void UAccelByteParty::RefreshPartyEntries()
    {
    Tb_PartyID->SetText(FText::FromString("##############"));
    SetCreatePartyButtonEnabled(true);
    RefreshPartyList();
    }
  8. Also prepare a new function that will reset the current stored Party Info.

    void UAccelByteParty::ResetPartyInfo()
    {
    const FAccelByteModelsPartyDataNotif EmptyData;
    PartyInfo = EmptyData;
    bIsInParty = false;

    RefreshPartyEntries();
    }
  9. Remove SetCreatePartyResponseDelegate, SetLeavePartyResponseDelegate, and SetInvitePartyResponseDelegate from their current function. Put them in the SetNotificationDelegate() function instead.

  10. Also, modify the SetNotificationDelegate() function by changing the CreateWeakLambda with CreateUObject for readability.

    void UAccelByteParty::OnClickedCreateParty()
    {
    FOnlineSessionSettings NewSessionSettings;
    NewSessionSettings.NumPrivateConnections = 4;

    NewSessionSettings.Set(SETTING_SESSION_JOIN_TYPE, TEXT("INVITE_ONLY"));
    NewSessionSettings.Set(SETTING_SESSION_TYPE, SETTING_SESSION_TYPE_PARTY_SESSION);
    NewSessionSettings.Set(SETTING_SESSION_TEMPLATE_NAME, TEXT("TemplateNameFromAdminPortal"));

    SessionInterface->CreateSession(LocalPlayerId.ToSharedRef().Get(), NAME_PartySession, NewSessionSettings);
    }

    void UAccelByteParty::OnClickedLeaveParty()
    {
    auto PartySession = SessionInterface->GetNamedSession(NAME_PartySession);
    auto PartySessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2> (PartySession->SessionInfo);
    FString PartyId = PartySessionInfo->GetBackendSessionDataAsPartySession()->ID;

    SessionInterface->LeaveSession(LocalPlayerId.ToSharedRef().Get(), EAccelByteV2SessionType::PartySession, PartyId, OnLeavePartyComplete);
    }

    void UAccelByteParty::OnClickedInviteParty(const FString& UserId)
    {
    SessionInterface->SendSessionInviteToFriend(LocalPlayerId.ToSharedRef().Get(), NAME_PartySession, FUniqueNetIdAccelByteUser::Create(UserId).Get());
    }

    void UAccelByteParty::SetNotificationDelegate()
    {
    SessionInterface->OnCreateSessionCompleteDelegates.AddUObject(this, &UAccelByteParty::OnCreateSessionComplete);

    SessionInterface->OnDestroySessionCompleteDelegates.AddUObject(this, &UAccelByteParty::OnLeaveSessionComplete);

    SessionInterface->OnSendSessionInviteCompleteDelegates.AddUObject(this, &UAccelByteParty::OnSendSessionInviteComplete);

    SessionInterface->OnV2SessionInviteReceivedDelegates.AddUObject(this, &UAccelByteParty::OnSessionInviteReceived);

    SessionInterface->OnKickedFromSessionDelegates.AddUObject(this, &UAccelByteParty::OnKickedFromParty);

    SessionInterface->OnSessionParticipantsChangeDelegates.AddUObject(this, &UAccelByteParty::OnPartyMembersChange);

    SessionInterface->OnSessionUpdateReceivedDelegates.AddUObject(this, &UAccelByteParty::OnPartySessionUpdateReceived);
    }
  11. Now, define the response delegate functions you declared earlier for each create party, leave party, and invite to party response query.

    Create Party's Success and Failed Response

    void UAccelByteParty::OnCreateSessionComplete(FName SessionName, bool bSucceeded)
    {
    if (SessionName != NAME_PartySession)
    {
    return;
    }

    if(bSucceeded)
    {
    UE_LOG(LogTemp, Log, TEXT("Create Party Success!"));
    }
    else
    {
    UE_LOG(LogTemp, Error, TEXT("Create Party Failed"));
    }
    }

    Leave Party's Success and Failed Response

    void UAccelByteParty::OnLeaveSessionComplete(FName SessionName, bool bSucceeded)
    {
    if (SessionName != NAME_PartySession)
    {
    return;
    }

    if (bSucceeded)
    {
    UE_LOG(LogTemp, Log, TEXT("Leave Party Success!"));
    ResetPartyInfo();
    }
    else
    {
    UE_LOG(LogTemp, Error, TEXT("Leave Party Failed"));
    }
    }

    Invite to Party's Success and Failed Response

    void UAccelByteParty::OnSendSessionInviteComplete(const FUniqueNetId& LocalSenderId, FName SessionName, bool bSucceeded, const FUniqueNetId& InviteeId)
    {
    if (SessionName != NAME_PartySession)
    {
    return;
    }

    if (bSucceeded)
    {
    UE_LOG(LogTemp, Log, TEXT("Invite to Party Success!"));
    }
    else
    {
    UE_LOG(LogTemp, Error, TEXT("Invite to Party Failed: %d , %s"), ErrorCode, *ErrorMessage);
    }
    }
  12. Define the notification events' callback functions to update your widgets based on each event updates:

    • OnInvitePartyGetInviteNotice()
    void UAccelByteParty::OnSessionInviteReceived(const FUniqueNetId& UserId, const FUniqueNetId& FromId, const FOnlineSessionInviteAccelByte& Invite)
    {
    if (Invite.SessionType != EAccelByteV2SessionType::PartySession)
    {
    return;
    }

    UE_LOG(LogTemp, Log, TEXT("On Get Invite to Party Notice!"));
    }

    • OnInvitePartyKickedNotice()
    void UAccelByteParty::OnKickedFromParty(FName SessionName)
    {
    if (SessionName != NAME_PartySession)
    {
    return;
    }

    UE_LOG(LogTemp, Log, TEXT("You have been kicked from the party!"));
    ResetPartyInfo();
    }

    • OnLeavePartyNotice()
    void UAccelByteParty::OnPartyMembersChange(FName SessionName, const FUniqueNetId& Member, bool bJoined)
    {
    if (SessionName != NAME_PartySession)
    {
    return;
    }

    if (bJoined)
    {
    UE_LOG(LogTemp, Log, TEXT("On Some Player Joined Notice!"));
    }
    else
    {
    UE_LOG(LogTemp, Log, TEXT("On Some Player Leaved Notice!"));
    }
    }

    • OnPartyDataUpdateResponse()
    void UAccelByteParty::OnPartySessionUpdateReceived(FName SessionName)
    {
    if (SessionName != NAME_PartySession)
    {
    return;
    }

    UE_LOG(LogTemp, Log, TEXT("On Party Data Update Response!"));
    }

Party invitation pop-up

  1. Open the AccelBytePartyInvitationPopUp class and add the widget-related header at the top of the class

    ...
    #include "Components/Button.h"
    #include "TutorialProject/TutorialMenuHUD.h"
    #include "TutorialProject/TutorialProjectUtilities.h"

  2. Specify all the widget components in the AccelBytePartyInvitationPopUp header file.

    /**
    * @brief Text Box for displaying Invitation.
    */
    UPROPERTY(meta = (BindWidget))
    UTextBlock* Tb_InvitationText;

    /**
    * @brief Button for Accepting Party Invitation.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_AcceptParty;

    /**
    * @brief Button for Rejecting Party Invitation.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_RejectParty;

  3. Add the NativeConstruct() override function to initialize the TutorialMenuHUD object and widgets.

    void UAccelBytePartyInvitationPopUp::NativeConstruct()
    {
    Super::NativeConstruct();

    Btn_AcceptParty->SetIsEnabled(true);
    Btn_RejectParty->SetIsEnabled(true);

    TutorialMenuHUD = Cast<ATutorialMenuHUD>(GetOwningPlayer()->GetHUD());

    Btn_AcceptParty->OnClicked.AddUniqueDynamic(this, &UAccelBytePartyInvitationPopUp::OnClickedAcceptPartyInvitation);
    Btn_RejectParty->OnClicked.AddUniqueDynamic(this, &UAccelBytePartyInvitationPopUp::OnClickedRejectPartyInvitation);
    }
  4. Create a function that initializes the local InvitationData you created in the QuickStart section if you haven't created a new local variable.

    void UAccelBytePartyInvitationPopUp::InitData(const FAccelByteModelsPartyGetInvitedNotice& Result)
    {
    InvitationData = Result;
    }

  5. Modify the OnClickedAcceptPartyInvitation() function by adding code to disable the button after clicking it.

    void UAccelBytePartyInvitationPopUp::OnClickedAcceptPartyInvitation()
    {
    Btn_AcceptParty->SetIsEnabled(false);

    SessionInterface->AddOnJoinSessionCompleteDelegate_Handle( FOnJoinSessionCompleteDelegate::CreateLambda([](FName SessionName, EOnJoinSessionCompleteResult::Type Result)
    {
    if (SessionName == NAME_PartySession && Result == EOnJoinSessionCompleteResult::Success)
    {
    UE_LOG(LogTemp, Log, TEXT("Join Party Success!"));
    }
    }));

    // Join the party session
    FOnlineSessionInviteAccelByte Invitation; // The invitation from the notification
    SessionInterface->JoinSession(*LocalPlayerId.Get(), NAME_PartySession, Invitation.Session);
    }

  6. Modify the OnClickedRejectPartyInvitation() function by adding widget implementation.

    void UAccelBytePartyInvitationPopUp::OnClickedRejectPartyInvitation()
    {
    Btn_RejectParty->SetIsEnabled(false);

    FOnRejectSessionInviteComplete OnRejectSessionInviteComplete = FOnRejectSessionInviteComplete::CreateLambda([](const bool bWasSuccessful)
    {
    if (bWasSuccessful)
    {
    UE_LOG(LogTemp, Log, TEXT("Reject Party Success!"));
    RemoveFromParent();
    }
    else
    {
    UE_LOG(LogTemp, Error, TEXT("Reject Party Error! %d: %s"), ErrorCode, *ErrorMessage);
    RemoveFromParent();
    }
    });

    // Join the party session
    FOnlineSessionInviteAccelByte Invitation; // The invitation from the notification
    SessionInterface->RejectInvite(LocalPlayerId.ToSharedRef().Get(), Invitation, OnRejectSessionInviteComplete);
    }
  7. Open the AccelByteParty class, create a reference to the party invitation pop-up class and set the AccelBytePartyInvitationPopUp class as the reference.

  8. Also, in the OnInvitePartyGetInviteNotice() function, add the code to create a new party invitation pop-up widget.

    void UAccelByteParty::OnInvitePartyGetInviteNotice(const FAccelByteModelsPartyGetInvitedNotice& Result)
    {
    ...

    const TWeakObjectPtr<UAccelBytePartyInvitationPopUp> InvitationPopUpWidget = MakeWeakObjectPtr<UAccelBytePartyInvitationPopUp>(
    CreateWidget<UAccelBytePartyInvitationPopUp>(this, PartyInvitationPopUpClass.Get()));

    InvitationPopUpWidget.Get()->InitData(Result);
    InvitationPopUpWidget->AddToViewport();
    }

Now that you have AGS Party implemented:

  1. Open your AccelByteFriendEntry class and add the following code inside the OnClickedInviteParty() function. Make sure the TutorialMenuHUD pointer and UserData exist.

    ...
    #include "../Party/AccelByteParty.h"

    ...
    UAccelByteFriendEntry::OnClickedInviteParty()
    {
    TutorialMenuHUD->GetPartyMenu()->OnClickedInviteParty(UserData.UserId);
    }
  2. Next, open the AccelByteLobby header class and include the AccelByteParty header file.


  3. Still in the AccelByteLobby class, in the SetLobbyNotificationDelegate() function, under the Lobby's SetConnectSuccessDelegate() lambda, add some code to prepare the party widgets in the lobby page.

    void UAccelByteLobby::SetLobbyNotificationDelegate()
    {
    IdentityInterface->AddAccelByteOnConnectLobbyCompleteDelegate_Handle(USER_LOCAL_NUM
    , FAccelByteOnConnectLobbyCompleteDelegate::CreateLambda([](int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FOnlineErrorAccelByte& Error)
    {
    ...
    TutorialMenuHUD->GetPartyMenu()->SetCreatePartyButtonEnabled(true);
    TutorialMenuHUD->GetPartyMenu()->RefreshPartyEntries();
    }));
    ...
    }

  4. Specify the party's box widget in AccelByteLobby class, then create a function to spawn the party widget to lobby page.

    void UAccelByteLobby::AddPartyChild(UAccelByteParty* PartyMenu)
    {
    Sb_Party->AddChild(PartyMenu);
    }
  5. Open the TutorialMenuHUD class, then call Lobby's AddPartyChild() function in the InitMainMenu() function.

    void ATutorialMenuHUD::InitMainMenu()
    {
    ...
    LobbyMenu->AddPartyChild(PartyMenu);
    ...
    }
  6. In the TutorialMenuHUD class, refresh the party list when opening the lobby menu.

    void ATutorialMenuHUD::OpenLobbyMenu()
    {
    ...
    PartyMenu->RefreshPartyList();
    }

  7. Also, in the TutorialMenuHUD class, create a new function to refresh the party entries on closing the lobby menu.

    void ATutorialMenuHUD::OnCloseLobbyMenu()
    {
    PartyMenu->RefreshPartyEntries();
    CloseLobbyMenu();
    }

  8. Back to AccelByteLobby.cpp, call the OnCloseLobbyMenu() you created earlier inside Lobby's notification delegate on the connection closed and disconnected.

    void UAccelByteLobby::SetLobbyNotificationDelegate()
    {
    ...
    IdentityInterface->AddAccelByteOnLobbyConnectionClosedDelegate_Handle(USER_LOCAL_NUM
    , FAccelByteOnLobbyConnectionClosedDelegate::CreateLambda([](int32 LocalUserNum, const FUniqueNetId& UserId, int32 StatusCode, const FString& Reason, bool bWasClean)
    {
    TutorialMenuHUD->OnCloseLobbyMenu();
    }));
    ...
    }

Congratulations! You have now fully implemented AGS Party.

Full code for reference

AccelBytePartyInvitationPopup.h
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Models/AccelByteLobbyModels.h"
#include "AccelBytePartyInvitationPopUp.generated.h"

class UTextBlock;
class UButton;

/**
* Pop-up window when the player gets a party invitation from a friend.
* This code covers AccelByte services including:
*
* - Get User Data by User ID
* - Accept Party Invitation
* - Reject Party Invitation
*/

UCLASS()
class TUTORIALPROJECT_API UAccelBytePartyInvitationPopUp : public UUserWidget
{
GENERATED_BODY()

virtual void NativeConstruct() override;

/**
* @brief Text Box displaying Invitation.
*/
UPROPERTY(meta = (BindWidget))
UTextBlock* Tb_InvitationText;
/**
* @brief Button for Accepting Party Invitation.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_AcceptParty;
/**
* @brief Button for Rejecting Party Invitation.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_RejectParty;

public:
/**
* @brief Initialize ResultInvited data to get player response to party host.
*/
UFUNCTION()
void InitData(const FAccelByteModelsPartyGetInvitedNotice& Result);
/**
* @brief Callback for accepting party invitation.
*/
UFUNCTION()
void OnClickedAcceptPartyInvitation();
/**
* @brief Callback for rejecting party invitation.
*/
UFUNCTION()
void OnClickedRejectPartyInvitation();

/**
* @brief Response when Accepting party invitation.
*/
void OnInvitePartyJoinResponse(const FAccelByteModelsPartyJoinResponse& Result);
/**
* @brief Response when Declining party invitation.
*/
void OnInvitePartyRejectResponse(const FAccelByteModelsPartyRejectResponse& Result);

/**
* @brief Get Invitation notice data to pass player accepting / rejecting
* party invitation request.
*/
FAccelByteModelsPartyGetInvitedNotice InvitationData;
};
AccelBytePartyInvitationPopup.cpp
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#include "AccelBytePartyInvitationPopUp.h"
// AccelByte Services
#include "Api/AccelByteLobbyApi.h"
#include "Core/AccelByteRegistry.h"
// Widget Components
#include "Components/Button.h"

void UAccelBytePartyInvitationPopUp::NativeConstruct()
{
Super::NativeConstruct();

Btn_AcceptParty->OnClicked.AddUniqueDynamic(this, &UAccelBytePartyInvitationPopUp::OnClickedAcceptPartyInvitation);
Btn_RejectParty->OnClicked.AddUniqueDynamic(this, &UAccelBytePartyInvitationPopUp::OnClickedRejectPartyInvitation);
}

void UAccelBytePartyInvitationPopUp::InitData(const FAccelByteModelsPartyGetInvitedNotice& Result)
{
InvitationData = Result;
}

void UAccelBytePartyInvitationPopUp::OnClickedAcceptPartyInvitation()
{
SessionInterface->AddOnJoinSessionCompleteDelegate_Handle( FOnJoinSessionCompleteDelegate::CreateLambda([](FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
if (SessionName == NAME_PartySession)
{
UE_LOG(LogTemp, Log, TEXT("Join Party Success!"));
}
}));

// Join the party session
FOnlineSessionInviteAccelByte Invitation; // The invitation from the notification
SessionInterface->JoinSession(*LocalPlayerId.Get(), NAME_PartySession, Invitation.Session);
}

void UAccelBytePartyInvitationPopUp::OnClickedRejectPartyInvitation()
{
FOnRejectSessionInviteComplete OnRejectSessionInviteComplete = FOnRejectSessionInviteComplete::CreateLambda([](const bool bWasSuccessful)
{
if (bWasSuccessful)
{
UE_LOG(LogTemp, Log, TEXT("Reject Party Success!"));
}
else
{
UE_LOG(LogTemp, Error, TEXT("Reject Party Error! %d: %s"), ErrorCode, *ErrorMessage);
}
});

FOnlineSessionInviteAccelByte Invitation; // The invitation from the notification
SessionInterface->RejectInvite(LocalPlayerId.ToSharedRef().Get(), Invitation, OnRejectSessionInviteComplete);
}

void UAccelBytePartyInvitationPopUp::OnInvitePartyJoinResponse(const FAccelByteModelsPartyJoinReponse& Result)
{
UE_LOG(LogTemp, Log, TEXT("Join Party Success! Member: %d"), Result.Members.Num());
if (Result.Code != "0")
{
UE_LOG(LogTemp, Warning, TEXT("Join Party Failed!"));
}
this->RemoveFromParent();
}

void UAccelBytePartyInvitationPopUp::OnInvitePartyRejectResponse(const FAccelByteModelsPartyRejectResponse& Result)
{
UE_LOG(LogTemp, Log, TEXT("Reject Party Success!"));
if (Result.Code != "0")
{
UE_LOG(LogTemp, Warning, TEXT("Reject Party Failed!"));
}
this->RemoveFromParent();
}
AccelBytePartyPlayerEntry.h
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Models/AccelByteLobbyModels.h"
#include "Models/AccelByteUserModels.h"
#include "AccelBytePartyPlayerEntry.generated.h"

class UImage;
class UTextBlock;
class UButton;

/**
* Party Player entry. This will hold individual data searched from the party member.
* This code covers AccelByte services including :
*
* - Get User by User ID
* - Kick Player
* - Promote Leader
*/
UCLASS()
class TUTORIALPROJECT_API UAccelBytePartyPlayerEntry : public UUserWidget
{
GENERATED_BODY()

virtual void NativeConstruct() override;

public:
/**
* @brief Initialize User ID and convert to FSimpleUserData in here.
*/
void InitData(const FString& PlayerId);

/**
* @brief Enabling button to Promote Leader and Kick Party member.
* @param bEnable Enable all button components.
*/
void EnableAllComponents(bool bIsEnable) const;

/**
* @brief Set Image Icon for Party Member.
* @param bIsLeader Checks if the player is the leader of the party.
*/
void SetImageIconLeader(bool bIsLeader) const;

/**
* @brief Get User Data stored in this entry.
*/
FSimpleUserData GetUserData() const;

private:
/**
* @brief Image to display leader status.
*/
UPROPERTY(meta = (BindWidget))
UImage* Img_LeaderIcon;
/**
* @brief Text Box for Player Username.
*/
UPROPERTY(meta = (BindWidget))
UTextBlock* Tb_PlayerUsername;
/**
* @brief Button for Promoting Party Leader.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_PartyLeader;
/**
* @brief Button for Kick Party Member.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_KickPlayer;

/**
* @brief Callback for OnClicked Party Leader Button.
*/
UFUNCTION()
void OnClickedPartyLeader();
/**
* @brief Callback for OnClicked Kick Party.
*/
UFUNCTION()
void OnClickedKickParty();

/**
* @brief Response for when party member is promoted to leader.
*/
void OnPartyPromoteLeaderResponse(const FAccelByteModelsPartyPromoteLeaderResponse& Result);
/**
* @brief Response fpr when kicked from party.
*/
void OnInvitePartyKickMemberResponse(const FAccelByteModelsKickPartyMemberResponse& Result);

/**
* @brief Callback for successfully getting the user ID.
*/
void OnSuccessGetUserId(const FSimpleUserData& Data);
/**
* @brief Callback for failed getting user ID.
*/
void OnFailedGetUserId(int32 ErrorCode, const FString& ErrorMessage);

/**
* @brief Models to store User Data contained in this entry.
*/
FSimpleUserData UserData;
};
AccelBytePartyPlayerEntry.cpp
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#include "AccelBytePartyPlayerEntry.h"
#include "AccelByteParty.h"
// AccelByte Services
#include "Api/AccelByteLobbyApi.h"
#include "Api/AccelByteUserApi.h"
#include "Core/AccelByteRegistry.h"
// Widget Components
#include "Components/Image.h"
#include "Components/TextBlock.h"
#include "Components/Button.h"
// Menu HUD
#include "TutorialProject/TutorialMenuHUD.h"

void UAccelBytePartyPlayerEntry::NativeConstruct()
{
Super::NativeConstruct();

Btn_PartyLeader->OnClicked.AddUniqueDynamic(this, &UAccelBytePartyPlayerEntry::OnClickedPartyLeader);
Btn_KickPlayer->OnClicked.AddUniqueDynamic(this, &UAccelBytePartyPlayerEntry::OnClickedKickParty);
}

void UAccelBytePartyPlayerEntry::InitData(const FString& PlayerId)
{
UserData.UserId = PlayerId;

IdentityInterface->GetApiClient(USER_LOCAL_NUM)->User.GetUserByUserId(
PlayerId,
THandler<FSimpleUserData>::CreateUObject(this, &UAccelBytePartyPlayerEntry::OnSuccessGetUserId),
FErrorHandler::CreateUObject(this, &UAccelBytePartyPlayerEntry::OnFailedGetUserId));
}

void UAccelBytePartyPlayerEntry::EnableAllComponents(bool bIsEnable) const
{
if (bEnable)
{
Btn_KickPlayer->SetVisibility(ESlateVisibility::Visible);
Btn_PartyLeader->SetVisibility(ESlateVisibility::Visible);
}
else
{
Btn_KickPlayer->SetVisibility(ESlateVisibility::Hidden);
Btn_PartyLeader->SetVisibility(ESlateVisibility::Hidden);
}
}

void UAccelBytePartyPlayerEntry::SetImageIconLeader(bool bIsLeader) const
{
if (bIsLeader)
{
Img_LeaderIcon->SetColorAndOpacity(FLinearColor::Yellow);
}
else
{
Img_LeaderIcon->SetColorAndOpacity(FLinearColor::White);
}
}

FSimpleUserData UAccelBytePartyPlayerEntry::GetUserData() const
{
return UserData;
}

void UAccelBytePartyPlayerEntry::OnClickedPartyLeader()
{
FOnPromotePartySessionLeaderComplete OnPromotePartySessionLeaderComplete =
FOnPromotePartySessionLeaderComplete::CreateLambda([](const FUniqueNetId &PromotedUserId, const FOnlineError &Result)
{
if (Result.bSucceeded)
{
UE_LOG(LogTemp, Log, TEXT("Promote Party Leader Success!"));
}
else
{
UE_LOG(LogTemp, Error, TEXT("Promote Party Leader Failed!"));
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, "Unable to Promote Party Leader!");
}
});

SessionInterface->PromotePartySessionLeader(LocalPlayerId.ToSharedRef().Get(), NAME_PartySession, FUniqueNetIdAccelByteUser::Create(UserData.UserId).Get(), OnPromotePartySessionLeaderComplete);
}

void UAccelBytePartyPlayerEntry::OnClickedKickParty()
{
SessionInterface->KickPlayer(LocalPlayerId.ToSharedRef().Get(), NAME_PartySession, FUniqueNetIdAccelByteUser::Create(UserData.UserId).Get());
}

void UAccelBytePartyPlayerEntry::OnPartyPromoteLeaderResponse(const FAccelByteModelsPartyPromoteLeaderResponse& Result)
{
if (Result.Code == "0")
{
UE_LOG(LogTemp, Log, TEXT("Promote Party Leader Success!"));
SetImageIconLeader(true);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Promote Party Leader Failed!"));
}
}

void UAccelBytePartyPlayerEntry::OnInvitePartyKickMemberResponse(const FAccelByteModelsKickPartyMemberResponse& Result)
{
if (Result.Code == "0")
{
UE_LOG(LogTemp, Log, TEXT("Kick Party Member Success!"));
Cast<ATutorialMenuHUD>(GetOwningPlayer()->GetHUD())->GetPartyMenu()->CreatePartyList();
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Kick Party Member Failed!"));
}
}

void UAccelBytePartyPlayerEntry::OnSuccessGetUserId(const FSimpleUserData& Data)
{
UserData = Data;
Tb_PlayerUsername->SetText(FText::FromString(UserData.DisplayName));
}

void UAccelBytePartyPlayerEntry::OnFailedGetUserId(int32 ErrorCode, const FString& ErrorMessage)
{
const FString DebugMessage = FString::Printf(TEXT("Get User Id Failed: %d , %s"), ErrorCode, *ErrorMessage);
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, DebugMessage);

UE_LOG(LogTemp, Warning, TEXT("Get User Id Failed: Code: %d; Message: %s"), ErrorCode, *ErrorMessage);
}
AccelByteParty.h
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#pragma once

#include "CoreMinimal.h"
#include "AccelBytePartyInvitationPopUp.h"
#include "Blueprint/UserWidget.h"
#include "Models/AccelByteLobbyModels.h"
#include "AccelByteParty.generated.h"

class UAccelBytePartyPlayerEntry;
class UScrollBox;
class UButton;
class UTextBlock;

/**
* Party setup
* This code covers AGS features including:
*
* - Create Party
* - Leave Party
* - Invite to Party
* - Get Notification when leaving party
* - Get Notification when joining party
* - Get Notification when someone kicked from party
* - Get Notification when someone promoted to be a party leader
* - Update party data
*/
UCLASS()
class TUTORIALPROJECT_API UAccelByteParty : public UUserWidget
{
GENERATED_BODY()

private:
virtual void NativeConstruct() override;

private:
/**
* @brief Scroll Box to spawn the player entry.
*/
UPROPERTY(meta = (BindWidget))
UScrollBox* Sb_Player;
/**
* @brief Button for Create Party.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_CreateParty;
/**
* @brief Button for Leave Party.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_LeaveParty;
/**
* @brief Text Block for displaying Party ID.
*/
UPROPERTY(meta = (BindWidget))
UTextBlock* Tb_PartyID;

public:
/**
* @brief Callback when creating party.
*/
UFUNCTION()
void OnClickedCreateParty();
/**
* @brief Callback when leaving party.
*/
UFUNCTION()
void OnClickedLeaveParty();
/**
* @brief Callback when inviting to party.
* @param UserId user ID to invited.
*/
UFUNCTION()
void OnClickedInviteParty(const FString& UserId);

private:
/**
* @brief Response for creating party.
*/
void OnCreatePartyResponse(const FAccelByteModelsCreatePartyResponse& Result);
/**
* @brief Response for failing to create party.
*/
void OnCreatePartyFailed(int32 ErrorCode, const FString& ErrorMessage);

/**
* @brief Response for leaving party.
*/
void OnLeavePartyResponse(const FAccelByteModelsLeavePartyResponse& Result);
/**
* @brief Response for failing leaving party.
*/
void OnLeavePartyFailed(int32 ErrorCode, const FString& ErrorMessage);

/**
* @brief Response for inviting friend to party.
*/
void OnInvitePartyResponse(const FAccelByteModelsPartyInviteResponse& Result);
/**
* @brief Response for failing inviting friend to party.
*/
void OnInvitePartyFailed(int32 ErrorCode, const FString& ErrorMessage);

/**
* @brief Response for getting notification for invitation to party.
*/
void OnInvitePartyGetInviteNotice(const FAccelByteModelsPartyGetInvitedNotice& Result);
/**
* @brief Response for notification when someone kicked from the party.
*/
void OnInvitePartyKickedNotice(const FAccelByteModelsGotKickedFromPartyNotice& Result);

/**
* @brief Response for notification when someone leaves party.
*/
void OnLeavePartyNotice(const FAccelByteModelsLeavePartyNotice& Result);
/**
* @brief Response for updating party data when someone does a party-related action.
*/
void OnPartyDataUpdateResponse(const FAccelByteModelsPartyDataNotif& Result);

public:
/**
* @brief Create an Empty Party List with UMG.
*/
void CreatePartyList();
/**
* @brief Function where all the notifications are being set.
*/
void SetNotificationDelegate();
/**
* @brief Refresh party entries.
*/
void SetRefreshPartyEntries();
/**
* @brief Update the leader icon.
* @param LeaderId The ID of the current party leader.
*/
void SetUpdateLeaderIcon(const FString& LeaderId) const;
/**
* @brief Set enabled / disabled state of the Create Party & Leave Party button.
*/
void SetCreatePartyButtonEnabled(bool bIsCreateParty) const;

private:
/**
* @brief Copy Array of player entry.
*/
TArray<TWeakObjectPtr<UAccelBytePartyPlayerEntry>> PartyPlayerEntryWidgetArray;
/**
* @brief Reference to Party Player Entry Class.
*/
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelBytePartyPlayerEntry> PartyPlayerEntryClass;
/**
* @brief Reference to Party Invitation Pop Up Class.
*/
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelBytePartyInvitationPopUp> PartyInvitationPopUpClass;

/**
* @brief Define maximum party member allowed.
*/
UPROPERTY(EditDefaultsOnly)
int MaximumPartyMember;
};
AccelByteParty.cpp
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#include "AccelByteParty.h"
#include "AccelBytePartyPlayerEntry.h"
// AccelByte Services
#include "Api/AccelByteLobbyApi.h"
#include "Core/AccelByteRegistry.h"
// Widget Components
#include "Components/ScrollBox.h"
#include "Components/Button.h"
#include "Components/Image.h"
#include "Components/TextBlock.h"
// Menu HUD
#include "TutorialProject/TutorialMenuHUD.h"

void UAccelByteParty::NativeConstruct()
{
Super::NativeConstruct();

Btn_CreateParty->OnClicked.AddUniqueDynamic(this, &UAccelByteParty::OnClickedCreateParty);
Btn_LeaveParty->OnClicked.AddUniqueDynamic(this, &UAccelByteParty::OnClickedLeaveParty);

SetNotificationDelegate();
}

void UAccelByteParty::OnClickedCreateParty()
{
FOnlineSessionSettings NewSessionSettings;
NewSessionSettings.NumPrivateConnections = 4;

NewSessionSettings.Set(SETTING_SESSION_JOIN_TYPE, TEXT("INVITE_ONLY"));
NewSessionSettings.Set(SETTING_SESSION_TYPE, SETTING_SESSION_TYPE_PARTY_SESSION);
NewSessionSettings.Set(SETTING_SESSION_TEMPLATE_NAME, TEXT("TemplateNameFromAdminPortal"));

SessionInterface->CreateSession(LocalPlayerId.ToSharedRef().Get(), NAME_PartySession, NewSessionSettings);
}

void UAccelByteParty::OnClickedLeaveParty()
{
auto PartySession = SessionInterface->GetNamedSession(NAME_PartySession);
auto PartySessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2> (PartySession->SessionInfo);
FString PartyId = PartySessionInfo->GetBackendSessionDataAsPartySession()->ID;

SessionInterface->LeaveSession(LocalPlayerId.ToSharedRef().Get(), EAccelByteV2SessionType::PartySession, PartyId, OnLeavePartyComplete);
}

void UAccelByteParty::OnClickedInviteParty(const FString& UserId)
{
SessionInterface->SendSessionInviteToFriend(LocalPlayerId.ToSharedRef().Get(), NAME_PartySession, FUniqueNetIdAccelByteUser::Create(UserId).Get());
}

void UAccelByteParty::SetNotificationDelegate()
{
SessionInterface->OnCreateSessionCompleteDelegates.AddUObject(this, &UAccelByteParty::OnCreateSessionComplete);

SessionInterface->OnDestroySessionCompleteDelegates.AddUObject(this, &UAccelByteParty::OnLeaveSessionComplete);

SessionInterface->OnSendSessionInviteCompleteDelegates.AddUObject(this, &UAccelByteParty::OnSendSessionInviteComplete);

SessionInterface->OnV2SessionInviteReceivedDelegates.AddUObject(this, &UAccelByteParty::OnSessionInviteReceived);

SessionInterface->OnKickedFromSessionDelegates.AddUObject(this, &UAccelByteParty::OnKickedFromParty);

SessionInterface->OnSessionParticipantsChangeDelegates.AddUObject(this, &UAccelByteParty::OnPartyMembersChange);

SessionInterface->OnSessionUpdateReceivedDelegates.AddUObject(this, &UAccelByteParty::OnPartySessionUpdateReceived);
}

void UAccelByteParty::CreatePartyList()
{
// Clear any entries when going back and forth in the menu.
Sb_Player->ClearChildren();
PartyPlayerEntryWidgetArray.Empty();

for (int i = 0; i < MaximumPartyMember; ++i)
{
PartyPlayerEntryWidgetArray.Add(MakeWeakObjectPtr<UAccelBytePartyPlayerEntry>(
CreateWidget<UAccelBytePartyPlayerEntry>(this, PartyPlayerEntryClass.Get())
));

PartyPlayerEntryWidgetArray[i]->EnableAllComponents(false);

Sb_Player->AddChild(PartyPlayerEntryWidgetArray[i].Get());
}
}

void UAccelByteParty::SetRefreshPartyEntries()
{
Tb_PartyID->SetText(FText::FromString("##############"));
SetCreatePartyButtonEnabled(true);
CreatePartyList();
}

void UAccelByteParty::SetUpdateLeaderIcon(const FString& LeaderId) const
{
const FString& LocalPlayerId = Cast<ATutorialMenuHUD>(GetOwningPlayer()->GetHUD())->PlayerInfo.UserId;

for (const TWeakObjectPtr<UAccelBytePartyPlayerEntry>& PlayerEntry : PartyPlayerEntryWidgetArray)
{
// Check if the local player is the leader.
if (LeaderId == LocalPlayerId)
{
if (LeaderId == PlayerEntry->GetUserData().UserId)
{
PlayerEntry->EnableAllComponents(false);
}
else if (PlayerEntry->GetUserData().UserId.IsEmpty())
{
PlayerEntry->EnableAllComponents(false);
}
else
{
PlayerEntry->EnableAllComponents(true);
}
}
else
{
PlayerEntry->EnableAllComponents(false);
}

// Set leader icon's color.
if (LeaderId == PlayerEntry->GetUserData().UserId)
{
PlayerEntry->SetImageIconLeader(true);
}
else
{
PlayerEntry->SetImageIconLeader(false);
}
}
}

void UAccelByteParty::SetCreatePartyButtonEnabled(bool bIsCreateParty) const
{
Btn_CreateParty->SetIsEnabled(bIsCreateParty);
Btn_LeaveParty->SetIsEnabled(!bIsCreateParty);
}

void UAccelByteParty::OnCreatePartyResponse(const FAccelByteModelsCreatePartyResponse& Result)
{
UE_LOG(LogTemp, Log, TEXT("Create Party Success!"));
}

void UAccelByteParty::OnCreatePartyFailed(int32 ErrorCode, const FString& ErrorMessage)
{
const FString DebugMessage = FString::Printf(TEXT("Create Party Failed: %d , %s"), ErrorCode, *ErrorMessage);
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, DebugMessage);
}

void UAccelByteParty::OnLeavePartyResponse(const FAccelByteModelsLeavePartyResponse& Result)
{
SetRefreshPartyEntries();

UE_LOG(LogTemp, Log, TEXT("Leave Party Success!"));
}

void UAccelByteParty::OnLeavePartyFailed(int32 ErrorCode, const FString& ErrorMessage)
{
const FString DebugMessage = FString::Printf(TEXT("Leave Party Failed: %d , %s"), ErrorCode, *ErrorMessage);
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, DebugMessage);

UE_LOG(LogTemp, Warning, TEXT("Leave Party Failed: Code: %d, Reason: %s"), ErrorCode, *ErrorMessage);
}

void UAccelByteParty::OnInvitePartyResponse(const FAccelByteModelsPartyInviteResponse& Result)
{
UE_LOG(LogTemp, Log, TEXT("Invite to Party Success!"));
}

void UAccelByteParty::OnInvitePartyFailed(int32 ErrorCode, const FString& ErrorMessage)
{
const FString DebugMessage = FString::Printf(TEXT("Invite to Party Failed: %d , %s"), ErrorCode, *ErrorMessage);
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, DebugMessage);

UE_LOG(LogTemp, Warning, TEXT("Invite to Party Failed: Code: %d, Reason: %s"), ErrorCode, *ErrorMessage);
}

void UAccelByteParty::OnInvitePartyGetInviteNotice(const FAccelByteModelsPartyGetInvitedNotice& Result)
{
UE_LOG(LogTemp, Log, TEXT("Accept Party Invitation!"));

/** Create a weak object pointer that refers to the pop-up widget **/
const TWeakObjectPtr<UAccelBytePartyInvitationPopUp> InvitationPopUpWidget = MakeWeakObjectPtr<UAccelBytePartyInvitationPopUp>(
CreateWidget<UAccelBytePartyInvitationPopUp>(this, PartyInvitationPopUpClass.Get()));

/** Initialize pop-up widget and add it to viewport **/
InvitationPopUpWidget.Get()->InitData(Result);
InvitationPopUpWidget->AddToViewport();
}

void UAccelByteParty::OnInvitePartyKickedNotice(const FAccelByteModelsGotKickedFromPartyNotice& Result)
{
SetRefreshPartyEntries();
}

void UAccelByteParty::OnLeavePartyNotice(const FAccelByteModelsLeavePartyNotice& Result)
{
SetRefreshPartyEntries();
}

void UAccelByteParty::OnPartyDataUpdateResponse(const FAccelByteModelsPartyDataNotif& Result)
{
if (!Result.PartyId.IsEmpty())
{
for (int i = 0; i < Result.Members.Num(); ++i)
{
PartyPlayerEntryWidgetArray[i]->InitData(Result.Members[i]);
}

Tb_PartyID->SetText(FText::FromString(Result.PartyId));

SetCreatePartyButtonEnabled(false);
SetUpdateLeaderIcon(Result.Leader);
}
}
TutorialMenuHUD.h
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "TutorialMenuHUD.generated.h"

class UAccelByteAuth;
class UAccelByteLobby;
class UAccelByteFriends;
class UAccelByteFindFriend;
class UAccelByteParty;

/**
* Menu Widget Controller. All Widget functionality is controlled here.
*/
UCLASS()
class TUTORIALPROJECT_API ATutorialMenuHUD : public AHUD
{
GENERATED_BODY()

protected:

virtual void BeginPlay() override;

public:

/**
* @brief Shows Login Menu on screen
*/
void OpenLoginMenu();

/**
* @brief Shows Main Menu on screen and destroys Login Menu
*/
void OpenMainMenu();

/**
* @brief Initialize the construct function on the first time the main menu opens;
*/
void InitMainMenu();

/**
* @brief Shows Lobby Menu, which adds Party Menu in Sb_Party and destroys Main Menu
*/
UFUNCTION()
void OpenLobbyMenu();

/**
* @brief Shows Friends Menu on screen
*/
void OpenFriendsMenu();

/**
* @brief Shows Find Friends Menu on screen
*/
void OpenFindFriendsMenu();

/**
* @brief Destroys Main Menu widget
*/
void CloseMainMenu();

/**
* @brief Destroys Lobby Menu widget and shows Main Menu
*/
UFUNCTION()
void CloseLobbyMenu();

/**
* @brief Destroys Friend Menu widget
*/
void CloseFriendMenu();

/**
* @brief Destroys Find Friend Menu widget
*/
void CloseFindFriendsMenu();

protected:

/**
* @brief Login Menu widget class
*/
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelByteAuth> LoginMenuClass;

/**
* @brief Lobby Menu widget class
*/
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelByteLobby> LobbyMenuClass;

/**
* @brief Main Menu widget class
*/
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelByteMainMenu> MainMenuClass;

/**
* @brief Friends Menu widget class
*/
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelByteFriends> FriendsMenuClass;

/**
* @brief Find Friends Menu widget class
*/
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelByteFindFriend> FindFriendsMenuClass;

/**
* @brief Party Menu widget class
*/
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelByteParty> PartyClass;

public:

/**
* @brief Getter for Login Menu widget
*/
UAccelByteAuth* GetLoginMenu() const {return LoginMenu; }

/**
* @brief Getter for Main Menu widget
*/
UAccelByteMainMenu* GetMainMenu() const {return MainMenu; }

/**
* @brief Getter for Lobby Menu widget
*/
UAccelByteLobby* GetLobbyMenu() const {return LobbyMenu; }

/**
* @brief Getter for Friends Menu widget
*/
UAccelByteFriends* GetFriendsMenu() const {return FriendsMenu; }

/**
* @brief Getter for Find Friends Menu widget
*/
UAccelByteFindFriend* GetFindFriendsMenu() const {return FindFriendsMenu; }

/**
* @brief Getter for Party Menu widget
*/
UAccelByteParty* GetPartyMenu() const {return PartyMenu; }

private:

/**
* @brief Login Menu widget pointer
*/
UPROPERTY()
UAccelByteAuth* LoginMenu;

/**
* @brief Lobby Menu widget pointer
*/
UPROPERTY()
UAccelByteLobby* LobbyMenu;

/**
* @brief Main Menu widget pointer
*/
UPROPERTY()
UAccelByteMainMenu* MainMenu;

/**
* @brief Friends Menu widget pointer
*/
UPROPERTY()
UAccelByteFriends* FriendsMenu;

/**
* @brief Find Friends Menu widget pointer
*/
UPROPERTY()
UAccelByteFindFriend* FindFriendsMenu;

/**
* @brief Party Menu widget pointer
*/
UPROPERTY()
UAccelByteParty* PartyMenu;
};
TutorialMenuHUD.cpp
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#include "TutorialMenuHUD.h"
#include "AccelByte/Authentication/AccelByteAuth.h"
#include "AccelByte/Lobby/AccelByteLobby.h"
#include "AccelByte/AccelByteMainMenu.h"
#include "AccelByte/Friends/AccelbyteFindFriend.h"
#include "AccelByte/Friends/AccelByteFriends.h"
#include "AccelByte/Party/AccelByteParty.h"

void ATutorialMenuHUD::BeginPlay()
{
Super::BeginPlay();

APlayerController* PlayerController = GetOwningPlayerController();

check(LoginMenuClass != nullptr);
check(MainMenuClass != nullptr);
check(LobbyMenuClass != nullptr);
check(FriendsMenuClass != nullptr);
check(FindFriendsMenuClass != nullptr);
check(PartyClass != nullptr);

LoginMenu = CreateWidget<UAccelByteAuth>(PlayerController, LoginMenuClass.Get());
MainMenu = CreateWidget<UAccelByteMainMenu>(PlayerController, MainMenuClass.Get());
LobbyMenu = CreateWidget<UAccelByteLobby>(PlayerController, LobbyMenuClass.Get());
FriendsMenu = CreateWidget<UAccelByteFriends>(PlayerController, FriendsMenuClass.Get());
FindFriendsMenu = CreateWidget<UAccelByteFindFriend>(PlayerController, FindFriendsMenuClass.Get());
PartyMenu = CreateWidget<UAccelByteParty>(PlayerController, PartyClass.Get());
}

void ATutorialMenuHUD::OpenLoginMenu()
{
LoginMenu->AddToViewport();
}

void ATutorialMenuHUD::OpenMainMenu()
{
MainMenu->AddToViewport();
}

void ATutorialMenuHUD::InitMainMenu()
{
// Add Lobby to Viewport and set its visibility as collapsed
LobbyMenu->AddToViewport();
LobbyMenu->AddPartyChild(PartyMenu);
LobbyMenu->ConnectToLobby();
LobbyMenu->SetVisibility(ESlateVisibility::Collapsed);

OpenMainMenu();
}

void ATutorialMenuHUD::OpenLobbyMenu()
{
LobbyMenu->SetVisibility(ESlateVisibility::Visible);
}

void ATutorialMenuHUD::OpenFriendsMenu()
{
if (!FriendsMenu->IsInViewport())
{
FriendsMenu->AddToViewport();
}
else
{
FriendsMenu->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
FriendsMenu->RefreshFriendsList();
}
}

void ATutorialMenuHUD::OpenFindFriendsMenu()
{
FindFriendsMenu->AddToViewport();
}

void ATutorialMenuHUD::CloseMainMenu()
{
LoginMenu->AddToViewport();
MainMenu->RemoveFromViewport();
LobbyMenu->RemoveFromParent();
}

void ATutorialMenuHUD::CloseFindFriendsMenu()
{
FindFriendsMenu->RemoveFromParent();
}

void ATutorialMenuHUD::CloseLobbyMenu()
{
LobbyMenu->SetVisibility(ESlateVisibility::Collapsed);
}

void ATutorialMenuHUD::CloseFriendMenu()
{
if (!LobbyMenu->IsInViewport())
{
LobbyMenu->AddToViewport();
}
FriendsMenu->SetVisibility(ESlateVisibility::Collapsed);
}
AccelByteLobby.h
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#pragma once

#include "CoreMinimal.h"
#include "OnlineIdentityInterfaceAccelByte.h"
#include "../Party/AccelByteParty.h"

class ATutorialMenuHUD;
class UButton;
class UScrollBox;

/**
* Component for Join to AccelByte Lobby.
* This code covers AccelByte services including :
*
* - Join Lobby
* - Leave Lobby
*/
UCLASS()
class TUTORIALPROJECT_API UAccelByteLobby : public UUserWidget
{
GENERATED_BODY()

#pragma region Initialization

protected:

virtual void NativeConstruct() override;

#pragma endregion

#pragma region Widget Components

protected:

/**
* @brief Button for Leave Lobby and back to Main Menu.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_LeaveLobby;

/**
* @brief Button for go to Friend Management Menu.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_FriendsManagement;

/**
* @brief Scroll Box to spawn Party Widget.
*/
UPROPERTY(meta = (BindWidget))
UScrollBox* Sb_Party;

#pragma endregion

#pragma region Widget Callbacks

private:

/**
* @brief Button callback on Open Friends Management.
*/
UFUNCTION()
void OnClickedOpenFriendsManagement();

#pragma endregion

#pragma region Utilities

public:

/**
* @brief Connect to AGS Lobby.
*/
UFUNCTION()
void ConnectToLobby();

/**
* @brief Add new Party Menu to the party scroll box widget child.
* @param PartyMenu Party Menu that gonna add to party scroll box widget child.
*/
UFUNCTION()
void AddPartyChild(UAccelByteParty* PartyMenu) const;

/**
* @brief Set Lobby services notification delegates.
*/
void SetLobbyNotificationDelegate();

private:

/**
* @brief Tutorial Menu HUD pointer reference.
*/
UPROPERTY()
ATutorialMenuHUD* TutorialMenuHUD;

#pragma endregion
};
AccelByteLobby.cpp
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#include "OnlineIdentityInterfaceAccelByte.h"
#include "Components/Button.h"
#include "TutorialProject/TutorialMenuHUD.h"

#pragma region Initialization

void UAccelByteLobby::NativeConstruct()
{
Super::NativeConstruct();

check(GetOwningPlayer() != nullptr);
check(GetOwningPlayer()->GetHUD() != nullptr);

TutorialMenuHUD = Cast<ATutorialMenuHUD>(GetOwningPlayer()->GetHUD());

Btn_LeaveLobby->OnClicked.AddUniqueDynamic(TutorialMenuHUD, &ATutorialMenuHUD::CloseLobbyMenu);
Btn_FriendsManagement->OnClicked.AddUniqueDynamic(this, &UAccelByteLobby::OnClickedOpenFriendsManagement);
}

#pragma endregion
#pragma region Widget Callbacks

void UAccelByteLobby::OnClickedOpenFriendsManagement()
{
TutorialMenuHUD->OpenFriendsMenu();
}

#pragma endregion
#pragma region Utilities

void UAccelByteLobby::ConnectToLobby()
{
SetLobbyNotificationDelegate();
IdentityInterface->ConnectAccelByteLobby(USER_LOCAL_NUM);
}

void UAccelByteLobby::AddPartyChild(UAccelByteParty* PartyMenu) const
{
Sb_Party->AddChild(PartyMenu);
}

void UAccelByteLobby::SetLobbyNotificationDelegate()
{

}

#pragma endregion