Use the Online Subsystem to unfriend and block player - Manage friends - (Unreal Engine module)
Unwrap the Subsystem
In this section, you will learn how to implement the functionality to unfriend a friend, block a player, unblock players, and get the blocked player list using AccelByte Gaming Services (AGS) Online Subsystem (OSS). In the Byte Wars project, there is already a Game Instance Subsystem created named ManagingFriendsSubsystem
. 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, a starter subsystem class named ManagingFriendsSubsystem_Starter
has been prepared for you. This class is available in the Resources section and consists of the following files:
- Header file:
/Source/AccelByteWars/TutorialModules/Social/ManagingFriends/ManagingFriendsSubsystem_Starter.h
- CPP file:
/Source/AccelByteWars/TutorialModules/Social/ManagingFriends/ManagingFriendsSubsystem_Starter.cpp
The ManagingFriendsSubsystem_Starter
class also has several functionalities provided:
AGS OSS interfaces declarations:
FriendsInterface
andUserInterface
. You will use these interfaces to implement managing friends-related functionalities later.protected:
// ...
FOnlineUserAccelBytePtr UserInterface;
FOnlineFriendsAccelBytePtr FriendsInterface;Helper functions to get
UniqueNetId
from aPlayerController
. You will need these helpers for the AGS 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 functions to get the
LocalUserNum
from aPlayerController
. You will need these helpers for the AGS 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, there are prepared 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 a callback upon the caching blocked players' data completing.
// ...
DECLARE_DELEGATE_ThreeParams(FOnGetBlockedPlayerListComplete, bool /*bWasSuccessful*/, TArray<UFriendData*> /*BlockedPlayers*/, const FString& /*ErrorMessage*/);
DECLARE_DELEGATE(FOnGetCacheBlockedPlayersDataUpdated);Delegates to be used as callback upon the unfriending process completing, the blocking player process completing, and the unblocking player process completing.
// ...
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.
Open the
ManagingFriendsSubsystem_Starter
class Header file and declare the following function:public:
// ...
void GetBlockedPlayerList(const APlayerController* PC, const FOnGetBlockedPlayerListComplete& OnComplete = FOnGetBlockedPlayerListComplete());Create the definition for the function above. Open the
ManagingFriendsSubsystem_Starter
class CPP file and add the code below. 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());
}
// Query blocked players' user information.
if (UStartupSubsystem* StartupSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UStartupSubsystem>())
{
StartupSubsystem->QueryUserInfo(
LocalUserNum,
BlockedPlayerIds,
FOnQueryUsersInfoCompleteDelegate::CreateWeakLambda(this, [this, PlayerNetId, OnComplete, LocalUserNum](
const FOnlineError& Error,
const TArray<TSharedPtr<FUserOnlineAccountAccelByte>>& UsersInfo)
{
/* 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(""));
}));
}
}
// 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());
}
}
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 being able to send a friend request to or match-make with the blocker.
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());You also need to create a callback function to handle when the block player process completes.
protected:
// ...
void OnBlockPlayerComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& BlockedPlayerUserId, const FString& ListName, const FString& ErrorStr, const FOnBlockPlayerComplete OnComplete);Define the functions above. Open the
ManagingFriendsSubsystem_Starter
class CPP file and define theBlockPlayer()
function. This function will block a player and call theOnBlockPlayerComplete()
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());
}Define the
OnBlockPlayerComplete()
function that will be called when the block player process completes. 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);
}
}
Implement Unblock Player
In this section, you will implement the functionality to unblock a player.
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());You also need to create a callback function to handle when the unblock player process completes.
protected:
// ...
void OnUnblockPlayerComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& BlockedPlayerUserId, const FString& ListName, const FString& ErrorStr, const FOnUnblockPlayerComplete OnComplete);Define the functions above. Open the
ManagingFriendsSubsystem_Starter
class CPP file and define theUnblockPlayer()
function. This function will unblock a player and call theOnUnblockPlayerComplete()
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());
}Define the
OnUnblockPlayerComplete()
function that will be called once the unblock player process completes. 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);
}
}
Implement Unfriend
In this section, you will implement the functionality to unfriend a friend.
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());You also need to create a callback function to handle when the unfriend process completes.
protected:
void OnUnfriendComplete(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& FriendId, const FString& ListName, const FString& ErrorStr, const FOnUnfriendComplete OnComplete);Define the functions above. Open the
ManagingFriendsSubsystem_Starter
class CPP file and define theUnfriend()
function. This function will unfriend a friend and call theOnUnfriendComplete()
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(""));
}Define the
OnUnfriendComplete()
function that will be called when the unfriend process is completes. 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);
}
}
Listen for On Blocked Player List Updated
When a player is blocked or unblocked, the cached blocked player list will be updated automatically by the AGS 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.
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);Open the
ManagingFriendsSubsystem_Starter
class CPP file and create the definitions for the functions above. Start with theBindOnCachedBlockedPlayersDataUpdated()
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(); })));
}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);
}
}Essentially, 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 theUnbindOnCachedBlockedPlayersDataUpdated()
function. You will use these functions to update displayed entries widgets in the next tutorial.
Resources
- The files used in this tutorial section are available in the Unreal Byte Wars GitHub repository.