Skip to main content

Unreal Engine Module - Manage Friends - Use the Online Subsystem to unfriend and block player

Last updated on January 13, 2024

Unwrap the Subsystem

In this section, you will learn how to unfriend a friend, block a player, unblock players, and get the blocked player list using AccelByte Online Subsystem (OSS). In the Byte Wars project, there is already a Game Instance Subsystem created, namely the ManagingFriendsSubsystem class. This subsystem contains managing friends-related functionalities. In this tutorial, you will use a starter version of that subsystem, so you can implement managing friends-related functionalities from scratch.

What's in the Starter Pack

To follow this tutorial, we have prepared a starter subsystem class, namely ManagingFriendsSubsystem_Starter, for you. This class consists of the following files.

  • Header file can be found in /Source/AccelByteWars/TutorialModules/Social/ManagingFriends/ManagingFriendsSubsystem_Starter.h.
  • CPP file can be found in /Source/AccelByteWars/TutorialModules/Social/ManagingFriends/ManagingFriendsSubsystem_Starter.cpp.

The ManagingFriendsSubsystem_Starter class also has several functionalities supplied for you.

  • AccelByte OSS interfaces declaration, namely FriendsInterface and UserInterface. You will use these interfaces to implement managing friends-related functionalities later.

    protected:
    FOnlineUserAccelBytePtr UserInterface;
    FOnlineFriendsAccelBytePtr FriendsInterface;
  • Helper function to get UniqueNetId from a PlayerController. You will need these helpers for the AccelByte OSS interfaces mentioned above.

    FUniqueNetIdPtr UManagingFriendsSubsystem_Starter::GetUniqueNetIdFromPlayerController(const APlayerController* PC) const
    {
    if (!PC)
    {
    return nullptr;
    }

    ULocalPlayer* LocalPlayer = PC->GetLocalPlayer();
    if (!LocalPlayer)
    {
    return nullptr;
    }

    return LocalPlayer->GetPreferredUniqueNetId().GetUniqueNetId();
    }
  • Helper function to get the LocalUserNum from a PlayerController. You will need these helpers for the AccelByte OSS interfaces later too.

    int32 UManagingFriendsSubsystem_Starter::GetLocalUserNumFromPlayerController(const APlayerController* PC) const
    {
    int32 LocalUserNum = 0;

    if (!PC)
    {
    return LocalUserNum;
    }

    const ULocalPlayer* LocalPlayer = PC->GetLocalPlayer();
    if (LocalPlayer)
    {
    LocalUserNum = LocalPlayer->GetControllerId();
    }

    return LocalUserNum;
    }

Besides the starter subsystem, we have also prepared some constants and delegates in the /Source/AccelByteWars/TutorialModules/Social/FriendsEssentials/FriendsEssentialsModels.h file. In that file, you can find the following:

  • Delegates to be used as callback upon caching blocked players' data completed.

    DECLARE_DELEGATE_ThreeParams(FOnGetBlockedPlayerListComplete, bool /*bWasSuccessful*/, TArray<UFriendData*> /*BlockedPlayers*/, const FString& /*ErrorMessage*/);
    DECLARE_DELEGATE(FOnGetCacheBlockedPlayersDataUpdated);
  • Delegates to be used as callback upon unfriending process completed, blocking player process completed, and unblocking player process completed.

    DECLARE_DELEGATE_TwoParams(FOnUnfriendComplete, bool /*bWasSuccessful*/, const FString& /*ErrorMessage*/);
    DECLARE_DELEGATE_TwoParams(FOnBlockPlayerComplete, bool /*bWasSuccessful*/, const FString& /*ErrorMessage*/);
    DECLARE_DELEGATE_TwoParams(FOnUnblockPlayerComplete, bool /*bWasSuccessful*/, const FString& /*ErrorMessage*/);

Implement Get Blocked Players

In this section, you will implement functionalities to get the blocked player list.

  1. Open the ManagingFriendsSubsystem_Starter class header file and declare the following function.

    public:
    void GetBlockedPlayerList(const APlayerController* PC, const FOnGetBlockedPlayerListComplete& OnComplete = FOnGetBlockedPlayerListComplete());
  2. Next, let's create the definition for the function above. Open the ManagingFriendsSubsystem_Starter class CPP file and add the following code. The basic flow to get the blocked player list is to query the list from the backend. Once it's completed, the blocked player list will be cached locally.

    void UManagingFriendsSubsystem_Starter::GetBlockedPlayerList(const APlayerController* PC, const FOnGetBlockedPlayerListComplete& OnComplete)
    {
    if (!ensure(FriendsInterface))
    {
    UE_LOG_MANAGING_FRIENDS(Warning, TEXT("Cannot cache blocked player list. Friends Interface is not valid."));
    return;
    }

    const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);
    const FUniqueNetIdPtr PlayerNetId = GetUniqueNetIdFromPlayerController(PC);

    // Try to get cached blocked player list first.
    TArray<TSharedRef<FOnlineBlockedPlayer>> CachedBlockedPlayerList;
    if (FriendsInterface->GetBlockedPlayers(PlayerNetId->AsShared().Get(), CachedBlockedPlayerList))
    {
    // Then, update the cached blocked players' information by querying their user information.
    TPartyMemberArray BlockedPlayerIds;
    for (const TSharedRef<FOnlineBlockedPlayer>& CachedBlockedPlayer : CachedBlockedPlayerList)
    {
    BlockedPlayerIds.Add(CachedBlockedPlayer.Get().GetUserId());
    }

    // Create callback to handle queried blocked players' user information.
    OnQueryUserInfoCompleteDelegateHandle = UserInterface->AddOnQueryUserInfoCompleteDelegate_Handle(
    LocalUserNum,
    FOnQueryUserInfoCompleteDelegate::CreateWeakLambda(this, [this, PlayerNetId, OnComplete](int32 LocalUserNum, bool bWasSuccessful, const TArray<FUniqueNetIdRef>& UserIds, const FString& Error)
    {
    UserInterface->ClearOnQueryUserInfoCompleteDelegate_Handle(LocalUserNum, OnQueryUserInfoCompleteDelegateHandle);

    /* Refresh blocked players data with queried blocked players' user information.
    * Then, return blocked players to the callback. */
    TArray<UFriendData*> BlockedPlayers;
    TArray<TSharedRef<FOnlineBlockedPlayer>> NewCachedBlockedPlayerList;
    FriendsInterface->GetBlockedPlayers(PlayerNetId->AsShared().Get(), NewCachedBlockedPlayerList);
    for (const TSharedRef<FOnlineBlockedPlayer>& NewCachedBlockedPlayer : NewCachedBlockedPlayerList)
    {
    // Update blocked player's avatar URL based on queried friend's user information.
    FString UserAvatarURL;
    TSharedPtr<FOnlineUser> UserInfo = UserInterface->GetUserInfo(LocalUserNum, NewCachedBlockedPlayer.Get().GetUserId().Get());
    UserInfo->GetUserAttribute(ACCELBYTE_ACCOUNT_GAME_AVATAR_URL, UserAvatarURL);

    // Add the updated blocked player to the list.
    UFriendData* BlockedPlayer = UFriendData::ConvertToFriendData(NewCachedBlockedPlayer);
    BlockedPlayer->AvatarURL = UserAvatarURL;
    BlockedPlayers.Add(BlockedPlayer);
    }

    OnComplete.ExecuteIfBound(true, BlockedPlayers, TEXT(""));
    }
    ));

    // Query blocked players' user information.
    UserInterface->QueryUserInfo(LocalUserNum, BlockedPlayerIds);
    }
    // If none, request to backend then get the cached the blocked player list.
    else
    {
    OnQueryBlockedPlayersCompleteDelegateHandle = FriendsInterface->AddOnQueryBlockedPlayersCompleteDelegate_Handle(
    FOnQueryBlockedPlayersCompleteDelegate::CreateWeakLambda(this, [this, OnComplete](const FUniqueNetId& UserId, bool bWasSuccessful, const FString& Error)
    {
    if (!bWasSuccessful)
    {
    OnComplete.ExecuteIfBound(false, TArray<UFriendData*>(), Error);
    return;
    }

    TArray<TSharedRef<FOnlineBlockedPlayer>> CachedBlockedPlayer;
    FriendsInterface->GetBlockedPlayers(UserId, CachedBlockedPlayer);

    // Return blocked players to the callback.
    TArray<UFriendData*> BlockedPlayers;
    for (const TSharedRef<FOnlineBlockedPlayer>& TempData : CachedBlockedPlayer)
    {
    BlockedPlayers.Add(UFriendData::ConvertToFriendData(TempData));
    }

    OnComplete.ExecuteIfBound(true, BlockedPlayers, TEXT(""));
    }
    ));

    FriendsInterface->QueryBlockedPlayers(PlayerNetId->AsShared().Get());
    }
    }
  3. Congratulations! Your get blocked player list functionality is completed.

Implement Block Player

In this section, you will implement the functionality to block a player. Blocking a player will result in the blocked player not able to send friend request and match-make as one alliance with the blocker.

  1. Open the ManagingFriendsSubsystem_Starter class header file and declare the following functions.

    public:
    void BlockPlayer(const APlayerController* PC, const FUniqueNetIdRepl BlockedPlayerUserId, const FOnBlockPlayerComplete& OnComplete = FOnBlockPlayerComplete());
  2. You also need to create a callback function to handle when the block player process is completed.

    protected:
    void OnBlockPlayerComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& BlockedPlayerUserId, const FString& ListName, const FString& ErrorStr, const FOnBlockPlayerComplete OnComplete);
  3. Now, let's define the functions above. Open the ManagingFriendsSubsystem_Starter class CPP file and define the BlockPlayer() function. This function will block a player and call the OnBlockPlayerComplete() function to handle the callback.

    void UManagingFriendsSubsystem_Starter::BlockPlayer(const APlayerController* PC, const FUniqueNetIdRepl BlockedPlayerUserId, const FOnBlockPlayerComplete& OnComplete)
    {
    if (!ensure(FriendsInterface) || !ensure(PromptSubsystem))
    {
    UE_LOG_MANAGING_FRIENDS(Warning, TEXT("Cannot block a player. Friends Interface or Prompt Subsystem is not valid."));
    return;
    }

    PromptSubsystem->ShowLoading(BLOCK_PLAYER_MESSAGE);

    const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);
    OnBlockPlayerCompleteDelegateHandle = FriendsInterface->AddOnBlockedPlayerCompleteDelegate_Handle(LocalUserNum, FOnBlockedPlayerCompleteDelegate::CreateUObject(this, &ThisClass::OnBlockPlayerComplete, OnComplete));
    FriendsInterface->BlockPlayer(LocalUserNum, BlockedPlayerUserId.GetUniqueNetId().ToSharedRef().Get());
    }
  4. Finally, define the OnBlockPlayerComplete() function that will be called upon the block player process is completed. This function simply prints a log to show whether the block player process was successful or not and triggers the callback delegate.

    void UManagingFriendsSubsystem_Starter::OnBlockPlayerComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& BlockedPlayerUserId, const FString& ListName, const FString& ErrorStr, const FOnBlockPlayerComplete OnComplete)
    {
    PromptSubsystem->HideLoading();

    FriendsInterface->ClearOnBlockedPlayerCompleteDelegate_Handle(LocalUserNum, OnBlockPlayerCompleteDelegateHandle);

    if (bWasSuccessful)
    {
    UE_LOG_MANAGING_FRIENDS(Warning, TEXT("Success to block a player."));

    PromptSubsystem->ShowMessagePopUp(MESSAGE_PROMPT_TEXT, SUCCESS_BLOCK_PLAYER);
    OnComplete.ExecuteIfBound(true, TEXT(""));
    }
    else
    {
    UE_LOG_MANAGING_FRIENDS(Warning, TEXT("Failed to block a player. Error: %s"), *ErrorStr);

    PromptSubsystem->ShowMessagePopUp(ERROR_PROMPT_TEXT, FText::FromString(ErrorStr));
    OnComplete.ExecuteIfBound(false, ErrorStr);
    }
    }
  5. Congratulations! Your block player functionality is completed.

Implement Unblock Player

In this section, you will implement the functionality to unblock a player.

  1. Open the ManagingFriendsSubsystem_Starter class header file and declare the following functions.

    public:
    void UnblockPlayer(const APlayerController* PC, const FUniqueNetIdRepl BlockedPlayerUserId, const FOnUnblockPlayerComplete& OnComplete = FOnUnblockPlayerComplete());
  2. You also need to create a callback function to handle when the unblock player process is completed.

    protected:
    void OnUnblockPlayerComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& BlockedPlayerUserId, const FString& ListName, const FString& ErrorStr, const FOnUnblockPlayerComplete OnComplete);
  3. Now, let's define the functions above. Open the ManagingFriendsSubsystem_Starter class CPP file and define the UnblockPlayer() function. This function will unblock a player and call the OnUnblockPlayerComplete() function to handle the callback.

    void UManagingFriendsSubsystem_Starter::UnblockPlayer(const APlayerController* PC, const FUniqueNetIdRepl BlockedPlayerUserId, const FOnUnblockPlayerComplete& OnComplete)
    {
    if (!ensure(FriendsInterface) || !ensure(PromptSubsystem))
    {
    UE_LOG_MANAGING_FRIENDS(Warning, TEXT("Cannot unblock a player. Friends Interface or Prompt Subsystem is not valid."));
    return;
    }

    PromptSubsystem->ShowLoading(UNBLOCK_PLAYER_MESSAGE);

    const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);
    OnUnblockPlayerCompleteDelegateHandle = FriendsInterface->AddOnUnblockedPlayerCompleteDelegate_Handle(LocalUserNum, FOnBlockedPlayerCompleteDelegate::CreateUObject(this, &ThisClass::OnUnblockPlayerComplete, OnComplete));
    FriendsInterface->UnblockPlayer(LocalUserNum, BlockedPlayerUserId.GetUniqueNetId().ToSharedRef().Get());
    }
  4. Finally, define the OnUnblockPlayerComplete() function that will be called once the unblock player process is completed. This function simply prints a log to show whether the unblock player process was successful or not and triggers the callback delegate.

    void UManagingFriendsSubsystem_Starter::OnUnblockPlayerComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& BlockedPlayerUserId, const FString& ListName, const FString& ErrorStr, const FOnUnblockPlayerComplete OnComplete)
    {
    PromptSubsystem->HideLoading();

    FriendsInterface->ClearOnUnblockedPlayerCompleteDelegate_Handle(LocalUserNum, OnUnblockPlayerCompleteDelegateHandle);

    if (bWasSuccessful)
    {
    UE_LOG_MANAGING_FRIENDS(Warning, TEXT("Success to unblock a player."));

    PromptSubsystem->ShowMessagePopUp(MESSAGE_PROMPT_TEXT, SUCCESS_UNBLOCK_PLAYER);
    OnComplete.ExecuteIfBound(true, TEXT(""));
    }
    else
    {
    UE_LOG_MANAGING_FRIENDS(Warning, TEXT("Failed to unblock a player. Error: %s"), *ErrorStr);

    PromptSubsystem->ShowMessagePopUp(ERROR_PROMPT_TEXT, FText::FromString(ErrorStr));
    OnComplete.ExecuteIfBound(false, ErrorStr);
    }
    }
  5. Congratulations! Your unblock player functionality is completed.

Implement Unfriend

In this section, you will implement the functionality to unfriend a friend.

  1. Open the ManagingFriendsSubsystem_Starter class header file and declare the following functions.

    public:
    void Unfriend(const APlayerController* PC, const FUniqueNetIdRepl FriendUserId, const FOnUnfriendComplete& OnComplete = FOnUnfriendComplete());
  2. You also need to create a callback function to handle when the unfriend process is completed.

    protected:
    void OnUnfriendComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& FriendId, const FString& ListName, const FString& ErrorStr, const FOnUnfriendComplete OnComplete);
  3. Now, let's define the functions above. Open the ManagingFriendsSubsystem_Starter class CPP file and define the Unfriend() function. This function will unfriend a friend and call the OnUnfriendComplete() function to handle the callback.

    void UManagingFriendsSubsystem_Starter::Unfriend(const APlayerController* PC, const FUniqueNetIdRepl FriendUserId, const FOnUnfriendComplete& OnComplete)
    {
    if (!ensure(FriendsInterface) || !ensure(PromptSubsystem))
    {
    UE_LOG_MANAGING_FRIENDS(Warning, TEXT("Cannot unfriend a friend. Friends Interface or Prompt Subsystem is not valid."));
    return;
    }

    PromptSubsystem->ShowLoading(UNFRIEND_FRIEND_MESSAGE);

    const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);
    OnUnfriendCompleteDelegateHandle = FriendsInterface->AddOnDeleteFriendCompleteDelegate_Handle(LocalUserNum, FOnDeleteFriendCompleteDelegate::CreateUObject(this, &ThisClass::OnUnfriendComplete, OnComplete));
    FriendsInterface->DeleteFriend(LocalUserNum, FriendUserId.GetUniqueNetId().ToSharedRef().Get(), TEXT(""));
    }
  4. Finally, define the OnUnfriendComplete() function that will be called upon the unfriend process is completed. This function simply prints a log to show whether the unfriend process was successful or not and triggers the callback delegate.

    void UManagingFriendsSubsystem_Starter::OnUnfriendComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& FriendId, const FString& ListName, const FString& ErrorStr, const FOnUnfriendComplete OnComplete)
    {
    PromptSubsystem->HideLoading();

    FriendsInterface->ClearOnDeleteFriendCompleteDelegate_Handle(LocalUserNum, OnUnfriendCompleteDelegateHandle);

    if (bWasSuccessful)
    {
    UE_LOG_MANAGING_FRIENDS(Warning, TEXT("Success to unfriend a friend."));

    PromptSubsystem->ShowMessagePopUp(MESSAGE_PROMPT_TEXT, SUCCESS_UNFRIEND_FRIEND);
    OnComplete.ExecuteIfBound(true, TEXT(""));
    }
    else
    {
    UE_LOG_MANAGING_FRIENDS(Warning, TEXT("Failed to unfriend a friend. Error: %s"), *ErrorStr);

    PromptSubsystem->ShowMessagePopUp(ERROR_PROMPT_TEXT, FText::FromString(ErrorStr));
    OnComplete.ExecuteIfBound(false, ErrorStr);
    }
    }
  5. Congratulations! Your unfriend functionality is completed.

Listen On Blocked Player List Updated

When a player is blocked or unblocked, the cached blocked player list will be updated automatically by AccelByte OSS. In this section, you will learn how to bind a delegate to be executed when the blocked player list is updated. It will be useful when you need to update the displayed entries widgets later.

  1. Open the ManagingFriendsSubsystem_Starter class header file and create the following function declarations.

    public:
    void BindOnCachedBlockedPlayersDataUpdated(const APlayerController* PC, const FOnGetCacheBlockedPlayersDataUpdated& Delegate);
    void UnbindOnCachedBlockedPlayersDataUpdated(const APlayerController* PC);
  2. Then, open the ManagingFriendsSubsystem_Starter class CPP file and create the definitions for the functions above. Let's start with the BindOnCachedBlockedPlayersDataUpdated() function.

    void UManagingFriendsSubsystem_Starter::BindOnCachedBlockedPlayersDataUpdated(const APlayerController* PC, const FOnGetCacheBlockedPlayersDataUpdated& Delegate)
    {
    ensure(FriendsInterface);

    const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);

    // Add on blocked players changed delegate.
    OnBlockedPlayersChangeDelegateHandles.Add(LocalUserNum, FriendsInterface->AddOnBlockListChangeDelegate_Handle(LocalUserNum, FOnBlockListChangeDelegate::CreateWeakLambda(this, [Delegate](int32, const FString&) { Delegate.ExecuteIfBound(); })));
    }
  3. Next, create the definition for the UnbindOnCachedBlockedPlayersDataUpdated() function.

    void UManagingFriendsSubsystem_Starter::UnbindOnCachedBlockedPlayersDataUpdated(const APlayerController* PC)
    {
    ensure(FriendsInterface);

    const int32 LocalUserNum = GetLocalUserNumFromPlayerController(PC);

    // Clear on blocked players changed delegate.
    FDelegateHandle TempHandle = OnBlockedPlayersChangeDelegateHandles[LocalUserNum];
    if (TempHandle.IsValid())
    {
    FriendsInterface->ClearOnFriendsChangeDelegate_Handle(LocalUserNum, TempHandle);
    }
    }
  4. So basically, with these two functions you can bind a delegate to be executed when the blocked player list is updated by using the BindOnCachedBlockedPlayersDataUpdated() function. To unbind that delegate, you can use the UnbindOnCachedBlockedPlayersDataUpdated() function. You will use these functions to update displayed entries widgets in the next section.

Resources