Unreal: Friends #

This tutorial uses Unreal Engine 5.3 with Pragma Engine 0.1.0 to demonstrate integrating Pragma Engine party functionality with a third-party game engine. This guide assumes you are proficient with Unreal Editor.

In this tutorial, we’ll expand the MyPlayerController.h header file and MyPlayerController.cpp source file to implement functionality related to the Pragma Engine Friend service.

By the end of the tutorial, our game client will be able to send and receive friend invites, fetch friend data, and make basic and rich presence selections.

Get started #

To get started, re-run the update script command:

update-pragma-sdk.sh

Ensure you have a locally running Pragma Engine to test examples as you build each function.

How to use this tutorial #

The code presented in this tutorial is simplified to give you an introduction to the game flow. An actual game would have a more sophisticated design, and the player experience may differ significantly.

We’ve built this barebones social screen to help you visualize the functionality presented in this tutorial:

The functions in this tutorial are built as UFunctions with the Exec and BlueprintCallable specifiers, meaning they can be executed by the in-game console and in a Blueprint or Level Blueprint graph. The Exec specifier is useful for quickly testing your functions.

The example tests in each section are meant to ensure your C++ code is working properly and are unlikely to represent a completed game design. Adapt the organization and naming to suit your project’s needs.

For convenience, we’ve included sample C++ files that contain all the code from this tutorial as well as the login/logout functionality, party functionality, and matchmaking functionality.

Note that you may need to update your #include statements as you progress through the tutorial.

Implement the Initialize function #

If you haven’t already, implement the InitializeSocial() function that initializes the Friends and Presence services and sets the player’s basic presence to Online. See Implement the Initialize function in the Unreal: Presence topic.

Send friend invite by username #

Goal #

Implement a SendFriendInviteByUsername() function that allows players to send friend invites to other players using the player’s full username (display name and discriminator). In a real game, display names will likely be available to a player within a game instances, on leaderboards, or communicated through methods outside of Pragma, such as via voice chat or direct message.

You can fetch your full username using the GetFullUsername() function we defined in the Get party and player information step of the Unreal Party tutorial.

Steps #

  1. Declare the SendFriendInviteByUsername() function in the public section of your MyPlayerController.h file. Have the function accept a username in the form of a string. This string will include the invitee’s display name (e.g. test01) and discriminator (e.g. #6902)

    UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
    void SendFriendInviteByUsername(const FString& FullUsername);
    
  2. Define SendFriendInviteByUsername() in your MyPlayerController.cpp file:

    void AMyPlayerController::SendFriendInviteByUsername(const FString& FullUsername)
    {
    	FString DisplayName, Discriminator;
    	FullUsername.Split("#", &DisplayName, &Discriminator);
    
    	UPragmaFriendApi::FOnCompleteDelegate SendInviteByDisplayDelegate;
    	SendInviteByDisplayDelegate.BindWeakLambda(this, [DisplayName, Discriminator](TPragmaResult<> Result)
    	{
    		if (Result.IsSuccessful())
    		{
    			UE_LOG(LogTemp, Display, TEXT("Sent friend invite to display name: %s"), *DisplayName);
    		}
    		else
    		{
    			UE_LOG(LogTemp, Warning, TEXT("Pragma unable to send friend invite by display name. %s %s %s"), *DisplayName, *Discriminator, *Result.Error().ToString());
    		}
    	});
    
    	Player->FriendApi().SendFriendInviteByDisplayName(
    	FPragma_Account_DisplayName{DisplayName, Discriminator},
    	SendInviteByDisplayDelegate);
    }
    

Our SendFriendInviteByUsername() function accepts a string value representing the invitee’s full username (display name plus discriminator). The function splits the provided username into a DisplayName and a Discriminator so it can send the values as a FPragma_Account_DisplayName object to the GameLoopApi’s SendFriendInviteByDisplayName() function. The GameLoopApi function facilitates sending the friend invite to the correct player.

Test #

To test this functionality using the Unreal in-game console, as user test01, issue SendFriendInviteByUsername using the player’s full display name (e.g., test02#7136). You’ll see “You received a friend invite from test01.” in the Unreal output log for user test02.

To test this functionality using Unreal Blueprints:

  1. Create a “Send friend invite by username” button with a editable textbox that accepts a username.
  2. Have the “Send friend invite by username” button call your SendFriendInviteByUsername() method with the entered username.
  3. As user test01, enter another user’s full display name (e.g. test03#1942) in the text box and send the invite.

When successfully executed, you’ll see “You received a friend invite from test01.” in your Unreal output log for user test03, along with any on-screen functionality you defined in the Blueprints.

Send friend invite by social ID #

Goal #

Implement a SendFriendInviteBySocialId() function that allows players to send friend invites to other players using the player’s social ID. In a real game, this function would likely be called behind the scenes, for example if a player clicks on another player’s avatar in a party to send them a friend invite. In these situations using a social ID is preferable to using a display name because display names can easily change.

You can fetch your social ID using the GetSocialId() function we defined in the Get party and player information step of the Unreal Party tutorial.

Steps #

  1. Declare the SendFriendInviteBySocialId() function in the public section of your MyPlayerController.h file. Have the function accept a social ID in the form of a string:

    UFUNCTION(Exec, BlueprintCallable, meta=(Category="Pragma"))
    void SendFriendInviteBySocialId(const FString& SocialId);
    
  2. Define SendFriendInviteBySocialId() in your MyPlayerController.cpp file:

    void AMyPlayerController::SendFriendInviteBySocialId(const FString& SocialId)
    {
        UPragmaFriendApi::FOnCompleteDelegate SendInviteBySocialDelegate;
        SendInviteBySocialDelegate.BindWeakLambda(this, [=, this](TPragmaResult<> Result)
        {
            if (Result.IsSuccessful())
            {
                UE_LOG(LogTemp, Display, TEXT("Sent friend invite to id: %s"), *SocialId);
            }
            else
            {
                UE_LOG(LogTemp, Warning, TEXT("Pragma unable to send friend invite by ID. %s"), *Result.Error().ToString());
            }
        });
    
        Player->FriendApi().SendFriendInviteBySocialId(
        SocialId,
        SendInviteBySocialDelegate);
    }
    

Our SendInviteBySocialId() function accepts a string value representing the invitee’s social ID. Using this ID, the function calls the GameLoopApi’s SendInviteBySocialID() function, which facilitates sending a friend invite to the identified player.

Test #

To test this functionality using the Unreal in-game console, as user test01, issue SendInviteBySocialId using the player’s social ID (e.g., test02#7136). You’ll see “You received a friend invite from test01.” in the Unreal output log for user test02.

To test this functionality using Unreal Blueprints:

  1. Using the GetPartyPlayers() function you defined in the Get party and player information step of the Unreal Parties tutorial, display a list of the party members (including their social IDs) in the player’s current party.
  2. Allow a user to click a party player from the list, or create an associated button, to send an invite.
  3. Have your UI action call your SendInviteBySocialId() method with the social ID of the selected player.
  4. As user test01, click a party player’s display name or associated invite button.

When successfully executed, you’ll see “You received a friend invite from test01.” in your Unreal output log for user test03, along with any on-screen functionality you defined in the Blueprints.

View pending friend invites #

Goal #

Implement a GetFriendInvites() function that allows users to retrieve a list of pending received friend invitations.

Steps #

  1. Declare the GetFriendInvites() function in the public section of your MyPlayerController.h file:

    UFUNCTION(Exec, BlueprintCallable, meta=(Category="Pragma"))
    TArray<FPragmaFriendOverview> GetFriendInvites();
    
  2. Define GetFriendInvites() in your MyPlayerController.cpp file:

    TArray<FPragmaFriendOverview> AMyPlayerController::GetFriendInvites()
    {
    	using FReceivedInvites = TMap<FString, FPragmaFriendOverview>;
    
    	FReceivedInvites PendingReceivedInvites = *Player->FriendApi().GetReceivedInvites();
    
    	TArray<FPragmaFriendOverview> InviteList;
    
    	for (auto&PendingFriendInvite : PendingReceivedInvites)
    	{
    		FString PendingFriendSocialId = PendingFriendInvite.Value.SocialId();
    
    		InviteList.Add(PendingFriendInvite.Value);
    
    		UE_LOG(LogTemp, Display, TEXT("You have a pending invite from Social ID: %s"), *PendingFriendSocialId);
    	}
    
    	return InviteList;
    }
    

Test #

To test this functionality using the Unreal in-game console, as player 1, send a friend invite to player 2. As player 2, call GetFriendInvites. You’ll see “You have a pending invite from Social ID: " followed by the social ID of player 1.

To test this functionality using Unreal Blueprints, add a “Get friend invites” button and use the returned array to display various friend data, such as display name or social ID.

Accept or decline a friend invite #

Goal #

Implement a AcceptFriendInvite() function and a DeclineInvite() function to allow players to respond to pending friend invites. Players can respond to friend invites using the inviter’s social ID.

Steps #

  1. Declare the AcceptFriendInvite() and DeclineInvite() functions in the private section of your MyPlayerController.h file:

    UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
    void AcceptFriendInvite(const FString& InviterSocialId);
    
    UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
    void DeclineFriendInvite(const FString& InviterSocialId);
    
  2. Define AcceptFriendInvite() and DeclineInvite() in your MyPlayerController.cpp file:

    void AMyPlayerController::AcceptFriendInvite(const FString& InviterSocialId)
    {
        UPragmaFriendApi::FOnCompleteDelegate RespondFriendInviteDelegate;
        RespondFriendInviteDelegate.BindWeakLambda(this, [=, this](TPragmaResult<> Result)
        {
            if (Result.IsSuccessful())
            {
                UE_LOG(LogTemp, Display, TEXT("Accepted friend invite from %s."), *InviterSocialId)
            }
            else
            {
                UE_LOG(LogTemp, Warning,
                    TEXT("Unable to respond: %s"), *Result.Error().ToString());
            }
        });
    
        Player->FriendApi().AcceptFriendInvite(
            InviterSocialId,
            RespondFriendInviteDelegate
        );
    }
    
    
    void AMyPlayerController::DeclineFriendInvite(const FString& InviterSocialId)
    {
        UPragmaFriendApi::FOnCompleteDelegate RespondFriendInviteDelegate;
        RespondFriendInviteDelegate.BindWeakLambda(this, [=, this](TPragmaResult<> Result)
        {
            if (Result.IsSuccessful())
            {
                UE_LOG(LogTemp, Display, TEXT("Declined friend invite from %s."), *InviterSocialId)
            }
            else
            {
                UE_LOG(LogTemp, Warning,
                    TEXT("Unable to respond: %s"), *Result.Error().ToString());
            }
        });
    
        Player->FriendApi().DeclineFriendInvite(
            InviterSocialId,
            RespondFriendInviteDelegate
        );
    }
    

Our AcceptFriendInvite() and DeclineFriendInvite() functions accepts a string value representing the invite’s social ID, which it uses to call the FriendApi’s AcceptFriendInvite() or DeclineFriendInvite() function. Alternately, you could design one respond function that accepts a boolean value representing whether to accept or decline the friend invite, similar to the accept/decline party invite function (see Accept or decline a party invite).

Test #

To test this functionality using the Unreal in-game console:

  1. If you haven’t already, issue a friend request from one player to another (e.g., have test01 send a friend invite to test03#1943).
  2. As test03, view your pending friend invites to see the inviter’s social ID.
  3. Issue AcceptFriendInvite or DeclineFriendInvite with the inviter’s social ID.

If successful, you should see “Accepted friend invite from [test01’s social ID]” or “Declined friend invite from [test01’s social ID]” in the Unreal output log.

To test this functionality using Unreal Blueprints:

  1. Create an editable text box where the player can enter the social ID of the friend who sent the friend invite.
  2. Create an “Accept friend invite” button that calls our AcceptFriendInvite() function with the social ID value entered in the social ID text box.
  3. Create a “Decline friend invite” button that calls our DeclineFriendInvite() function with the social ID value entered in the social ID text box.

When successfully executed, you’ll see “Accepted friend invite from [test01’s social ID]” or “Declined friend invite from [test01’s social ID]” in the Unreal output log along with any on-screen functionality you defined in the Blueprints.

Get Friend list #

Goal #

Implement a GetFriends() function that allows users to retrieve a list of players on their friend list, identified by social ID.

Steps #

  1. Declare the GetFriends() function in the public section of your MyPlayerController.h file:

    UFUNCTION(Exec, BlueprintCallable, meta=(Category="Pragma"))
    TMap<FString, FPragmaFriend> GetFriends();
    
  2. Define GetFriends() in your MyPlayerController.cpp file:

    TMap<FString, FPragmaFriend> AMyPlayerController::GetFriends()
    {
    	TMap<FString, FPragmaFriend> MyFriendList = *Player->FriendApi().GetFriends();
    
    	UE_LOG(LogTemp, Display, TEXT("Friends: "));
    
    	for (auto&MyFriend : MyFriendList)
    	{
    		UE_LOG(LogTemp, Display, TEXT("%s"), *MyFriend.Key);
    	}
    	return MyFriendList;
    }
    

Test #

To test this functionality using the Unreal in-game console, as a user with one or more friends, call GetFriends. You’ll see “Friends:” followed by a list of social IDs in the Unreal output log.

To test this functionality using Unreal Blueprints, add a “Get friends” button and use the returned map to display various friend data, such as a list of display names.

Remove a friend #

Goal #

Implement a RemoveFriend() function that removes a player from your friends list. Players remove friends using the friend’s social ID.

Steps #

  1. Declare the RemoveFriend() function in the public section of your MyPlayerController.h file:

    UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
    void RemoveFriend(const FString FriendSocialID);
    
  2. Define RemoveFriend() in your MyPlayerController.cpp file:

    void AMyPlayerController::RemoveFriend(const FString FriendSocialID)
    {
        UPragmaFriendApi::FOnCompleteDelegate RespondRemoveFriendDelegate;
        RespondRemoveFriendDelegate.BindWeakLambda(this, [=, this](TPragmaResult<> Result)
        {
            if (Result.IsSuccessful())
            {
                UE_LOG(LogTemp, Display, TEXT("Removed friend: %s."), *FriendSocialID)
            }
            else
            {
                UE_LOG(LogTemp, Warning,
                    TEXT("Unable to remove friend: %s"), *Result.Error().ToString());
            }
        });
    
        Player->FriendApi().RemoveFriend(
        FriendSocialID,
        RespondRemoveFriendDelegate
    );
    }
    

Test #

To test this functionality using the Unreal in-game console, as a user with one or more friends, call RemoveFriend with a friend’s social ID. You’ll see “Friend removed.” in the Unreal output log.

To test this functionality using Unreal Blueprints, add a “Remove as friend” button next to each friend displayed in the UI. Have the button call your RemoveFriend() function with the social ID of the selected player (Player->SocialID). When successfully executed, you’ll see “Friend removed.” in your Unreal output log, along with any on-screen functionality you defined in the Blueprints, such as updating a list of current friends.

Sample header and source files #

The following sample files combine the code blocks from this tutorial, along with the functions from the Handle Login and Logout tutorial, the Unreal: Parties tutorial, the Unreal: Matchmaking tutorial, and the Unreal: Presence tutorial.

Sample MyPlayerController.h header file
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "PragmaPtr.h"
#include "PragmaGameInstanceSubsystem.h"
#include "PragmaResult.h"
#include "Dto/PragmaGameInstanceExtDto.h"
#include "Dto/PragmaPartyRpcExtDto.h"
#include "Pragma/Api/Player/PragmaFriendOverview.h"
#include "Pragma/Api/Player/PragmaFriendApi.h"
#include "Services/Party/PragmaParty.h"
#include "PragmaLocalPlayerSubsystem.h"
#include "Pragma/Api/Player/PragmaFriend.h"
#include "MyPlayerController.generated.h"

// Forward declares Pragma pointer types for Pragma::FPlayer.
PRAGMA_FWD(FPlayer);

UCLASS()
class UNREALTUTORIAL_API AMyPlayerController : public APlayerController
{
	GENERATED_BODY()

public:
    
	virtual void BeginPlay() override;

	// Weak pointer to our Pragma Player owned by the PragmaLocalPlayerSubsystem.
	Pragma::FRuntimePtr Runtime;
	Pragma::FPlayerPtr Player;

	//Login and Logout functions
	
	UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
	void LogIn(const FString& Username);

	UFUNCTION(Exec, BlueprintCallable, meta=(Category="Pragma"))
	void LogOut();

	//Initialization functions

	UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
	void InitializeGameLoop();
		
	UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
	void InitializeSocial();
	
	//Party functions
	
	UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
	void CreateParty();

	UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
	void JoinPartyWithCode(const FString& InviteCode);
	
	UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
	void SendPartyInviteByPlayerId(const FString& InviteeId);

	UFUNCTION(Exec, BlueprintCallable, meta=(Category="Pragma"))
	void RespondToPartyInvite(const FString& PartyInviteId, const bool response);

	UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
	void SetGameMode(const EPragma_Party_GameMode& GameModeSelection);

	UFUNCTION(Exec, BlueprintCallable, meta=(Category="Pragma"))
	void SetCharacter(const EPragma_Party_Character& CharacterSelection);

	UFUNCTION(Exec, BlueprintCallable, meta=(Category="Pragma"))
	void LeaveParty();
	
	//Matchmaking functions
	UFUNCTION(Exec, BlueprintCallable, meta=(Category="Pragma"))
	void EnterMatchmaking();

	//Presence functions

	UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
	void SetPresenceToOnline();

	UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
	void SetPresenceToAway();

	UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
	void SetInShop(const bool IsInShop);


	//Friend functions
	UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
	void SendFriendInviteByUsername(const FString& FullUsername);
	
	UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
	void SendFriendInviteBySocialId(const FString& SocialId);

	UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
	void AcceptFriendInvite(const FString& InviterSocialId);

	UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
	void DeclineFriendInvite(const FString& InviterSocialId);

	UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
	void RemoveFriend(const FString FriendSocialID);

	
	//Get functions

	UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
	FString GetFullUsername();

	UFUNCTION(Exec, BlueprintCallable, meta=(Category="Pragma"))
	FString GetPlayerId();

	UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
	FString GetSocialId();

	UFUNCTION(Exec, BlueprintCallable, Category="Pragma")
	TArray<FString> GetPartyPlayers();

	UFUNCTION(Exec, BlueprintCallable, meta=(Category="Pragma"))
	TArray<FString> GetPartyInvites();
	
	UFUNCTION(Exec, BlueprintCallable, meta=(Category="Pragma"))
	TArray<FPragmaFriendOverview> GetFriendInvites();

	UFUNCTION(Exec, BlueprintCallable, meta=(Category="Pragma"))
	TMap<FString, FPragmaFriend> GetFriends();
	
	
	
private:
	void HandleLoggedIn(const TPragmaResult<>& Result);
	void HandleLoggedOut();
	void HandleOnPresenceChanged(const FPragmaPresence& MyPresence);
	void HandleOnAddedToGame(const FString GameInstanceId, const FPragma_GameInstance_ExtAddedToGame Ext);
	
};
Sample MyPlayerController.cpp source file
// Fill out your copyright notice in the Description page of Project Settings.

#include "MyPlayerController.h"

#include "PragmaPlayer.h"
#include "Services/Party/PragmaParty.h"
#include "Dto/PragmaAccountRpcDto.h"
#include "Dto/PragmaPartyRpcExtDto.h"
#include "Dto/PragmaPresenceExtDto.h"
#include "Pragma/Api/Player/PragmaFriendOverview.h"
#include "PragmaLocalPlayerSubsystem.h"

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

	UE_LOG(LogTemp, Display, TEXT("Initializing") );
	
	const auto* Subsystem = GetLocalPlayer()->GetSubsystem<UPragmaLocalPlayerSubsystem>();

	// The Pragma Runtime is automatically initialized in the PragmaGameInstanceSubsystem.
	auto RuntimeA = Subsystem->Runtime();

	// Set configuration for the SDK before logging in.
	RuntimeA->Config().BackendAddress = "http://127.0.0.1:10000";
	RuntimeA->Config().ProtocolType = EPragmaProtocolType::WebSocket;
	RuntimeA->Config().GameClientVersion = "GameServerVersion1";

	// The UPragmaLocalPlayerSubsystem is automatically initialized
	// with a Pragma Player object for every LocalPlayer.
	Player = Subsystem->Player();
	
	//Event handlers for party actions
	Player->PresenceApi().OnPresenceChanged.AddUObject(
	this, &AMyPlayerController::HandleOnPresenceChanged);

	Player->GameLoopApi().OnAddedToGame.AddUObject(
	this, &AMyPlayerController::HandleOnAddedToGame);
	
}

//Login and Logout functions

void AMyPlayerController::LogIn(const FString& Username)
{
    Player->LogIn(
        EPragma_Account_IdProvider::UNSAFE,
        Username,
        Pragma::FPlayer::FLoggedInDelegate::CreateUObject(
            this, &AMyPlayerController::HandleLoggedIn));
}

void AMyPlayerController::LogOut()
{
    Player->LogOut(Pragma::FPlayer::FLoggedOutDelegate::CreateUObject(
        this, &AMyPlayerController::HandleLoggedOut));
}

//Initialization functions

void AMyPlayerController::InitializeGameLoop()
{
	UPragmaGameLoopApi::FOnCompleteDelegate GameLoopInitializeDelegate;
	GameLoopInitializeDelegate.BindWeakLambda(this, [this](const TPragmaResult<>& Result)
		{
			if (Result.IsSuccessful())
			{
				UE_LOG(LogTemp, Display, TEXT("Game loop services initialized."));
			}
			else
			{
				UE_LOG(LogTemp, Warning, TEXT("Game loop services failed to initialize: %s"), *Result.Error().ToString());
			}
		});
	Player->GameLoopApi().Initialize(GameLoopInitializeDelegate);
}

void AMyPlayerController::InitializeSocial()
{
	UPragmaGameLoopApi::FOnCompleteDelegate FriendInitializeDelegate;
	FriendInitializeDelegate.BindWeakLambda(this, [this](const TPragmaResult<>& Result)
		{
			if (Result.IsSuccessful())
			{
				UE_LOG(LogTemp, Display, TEXT("Friends and Presence services initialized."));
			}
			else
			{
				UE_LOG(LogTemp, Warning, TEXT("Friends and Presence services failed to initialize: %s"), *Result.Error().ToString());
			}
		});
	Player->FriendApi().Initialize(FriendInitializeDelegate);
}


//Party functions

void AMyPlayerController::CreateParty()
{
	UPragmaGameLoopApi::FOnCompleteDelegate OnPartyCreatedDelegate;
	OnPartyCreatedDelegate.BindWeakLambda(this, [this](TPragmaResult<> Result)
	{
		if (Result.IsSuccessful())
		{
			UE_LOG(LogTemp, Display, TEXT("Pragma party created with code: %s"), *Player->GameLoopApi().GetPartyState()->Party()->GetInviteCode());
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("Pragma unable to create party: %s"), *Result.Error().ToString());
		}
	});

	Player->GameLoopApi().CreateParty(
		FPragma_Party_ExtCreateRequest{},
		FPragma_Party_ExtPlayerJoinRequest{},
		TArray<FString>(),
		TMap<FString, int32>(),
		OnPartyCreatedDelegate);
}

void AMyPlayerController::JoinPartyWithCode(const FString& InviteCode)
{
	UPragmaGameLoopApi::FOnCompleteDelegate JoinWithInviteCodeDelegate;
	JoinWithInviteCodeDelegate.BindWeakLambda(this, [=, this](TPragmaResult<> Result)
	{
		if (Result.IsSuccessful())
		{
			UE_LOG(LogTemp, Display, TEXT("Joined party using invite code %s"), *InviteCode);
		}
		else
		{
			UE_LOG(LogTemp, Warning,
				TEXT("Unable to join party: %s"), *Result.Error().ToString());
		}
	});

	Player->GameLoopApi().JoinPartyWithInviteCode(
		FPragma_Party_ExtPlayerJoinRequest{},
		InviteCode,
		JoinWithInviteCodeDelegate
	);
}

void AMyPlayerController::SendPartyInviteByPlayerId(const FString& InviteeId)
{
	Player->GameLoopApi().SendPartyInvite(
		InviteeId,
		UPragmaGameLoopApi::FOnInviteSentDelegate::CreateWeakLambda(
	this, [this, InviteeId](const TPragmaResult<FString>& SendPartyInviteResult)
	{
		if (SendPartyInviteResult.IsSuccessful())
		{
			const FString InviteId = SendPartyInviteResult.Payload();
			UE_LOG(LogTemp, Display, TEXT("Send party invite by player id succeeded. Party invite ID: %s"), *InviteId);
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("Pragma unable to send invite: %s"), *SendPartyInviteResult.Error().ToString());
		}
	})
	);
}

TArray<FString> AMyPlayerController::GetPartyInvites()
{
	const TArray<FPragmaPartyInvite> PartyInvites = Player->GameLoopApi().GetPendingPartyInvites();

	TArray<FString> PartyInviteCodes;

	for(const FPragmaPartyInvite PartyInvite : PartyInvites)
	{
		FString PartyInviteCode = PartyInvite.GetInviteId();
		UE_LOG(LogTemp, Display, TEXT("Party invite id: %s"), *PartyInviteCode);
		PartyInviteCodes.Add(PartyInviteCode);
	}

	return PartyInviteCodes;
}

void AMyPlayerController::RespondToPartyInvite(const FString& PartyInviteCode, const bool Response)
{
	UPragmaGameLoopApi::FOnCompleteDelegate RespondInviteDelegate;
	RespondInviteDelegate.BindWeakLambda(this, [=, this](TPragmaResult<> Result)
	{
		if (Result.IsSuccessful())
		{
			if (Response==true)
			{
				UE_LOG(LogTemp, Display, TEXT("Accepted party invite id %s. Party successfully joined."), *PartyInviteCode);
			}
			else
			{
				UE_LOG(LogTemp, Display, TEXT("Declined party invite id %s"), *PartyInviteCode);
			}
		}
		else
		{
			UE_LOG(LogTemp, Warning,
				TEXT("Unable to respond: %s"), *Result.Error().ToString());
		}
	});

	Player->GameLoopApi().RespondToPartyInvite(
		FPragma_Party_ExtPlayerJoinRequest{},
		PartyInviteCode,
		Response,
		RespondInviteDelegate
	);
}

void AMyPlayerController::SetGameMode(const EPragma_Party_GameMode& GameModeSelection)
{
	if(Player->GameLoopApi().IsLeaderOfParty(Player->Id())==true)
	{
		FPragma_Party_ExtUpdatePartyRequest Request;
		Request.Update.SetNewGameMode(GameModeSelection);

		UPragmaGameLoopApi::FOnCompleteDelegate UpdatePartyDelegate;
	
		UpdatePartyDelegate.BindWeakLambda(this, [=, this](TPragmaResult<> Result)
		{
			if (Result.IsSuccessful())
			{
				UE_LOG(LogTemp, Display,
					TEXT("Changed game mode selection to %s"), *UEnum::GetValueAsString<EPragma_Party_GameMode>(GameModeSelection));
			}
			else
			{
				UE_LOG(LogTemp, Warning,
					TEXT("Unable to change game mode: %s"), *Result.Error().ToString());
			}
		});

		Player->GameLoopApi().UpdateParty(Request, UpdatePartyDelegate);
	}
	else
	{
		UE_LOG(LogTemp, Display,TEXT("Only party leaders can select game mode"));
	}
}

void AMyPlayerController::SetCharacter(const EPragma_Party_Character& CharacterSelection)
{
	UPragmaGameLoopApi::FOnCompleteDelegate UpdatePartyPlayerDelegate;
	UpdatePartyPlayerDelegate.BindWeakLambda(this, [=, this](TPragmaResult<> Result)
	{
		if (Result.IsSuccessful())
		{
			UE_LOG(LogTemp, Display,
				TEXT("Changed character selection to %s"), *UEnum::GetValueAsString<EPragma_Party_Character>(CharacterSelection));
		}
		else
		{
			UE_LOG(LogTemp, Warning,
				TEXT("Unable to change character: %s"), *Result.Error().ToString());
		}
	});

	FPragma_Party_ExtUpdatePartyPlayerRequest Request;
	Request.Update.SetNewCharacter(CharacterSelection);

	Player->GameLoopApi().UpdatePartyPlayer(Request, UpdatePartyPlayerDelegate);
}

void AMyPlayerController::LeaveParty()
{
	UPragmaGameLoopApi::FOnCompleteDelegate LeavePartyDelegate;
	LeavePartyDelegate.BindWeakLambda(this, [this](TPragmaResult<> Result)
	{
		if (Result.IsSuccessful())
		{
			UE_LOG(LogTemp, Display, TEXT("Successfully left party."));
		}
		else
		{
			UE_LOG(LogTemp, Warning,
				TEXT("Unable to leave party: %s"), *Result.Error().ToString());
		}
	});
	Player->GameLoopApi().LeaveParty(LeavePartyDelegate);
}

//Matchmaking functions

void AMyPlayerController::EnterMatchmaking()
{
	UPragmaGameLoopApi::FOnCompleteDelegate OnEnterMatchmakingDelegate;
	OnEnterMatchmakingDelegate.BindWeakLambda(this, [this](TPragmaResult<> Result)
	{
		if (Result.IsSuccessful())
		{
			UE_LOG(LogTemp, Display, TEXT("Enter matchmaking success."));
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("Pragma unable to enter matchmaking: %s"), *Result.Error().ToString());
		}
	});

	Player->GameLoopApi().EnterMatchmaking(OnEnterMatchmakingDelegate);
}

//Presence functions

void AMyPlayerController::SetPresenceToOnline()
{
	Player->PresenceApi().SetAsOnline(
		UPragmaPresenceApi::FOnCompleteDelegate::CreateWeakLambda(this, [](const TPragmaResult<>& Result)
		{
			if (Result.IsSuccessful())
			{
				UE_LOG(LogTemp, Display, TEXT("Set presence to online succeeded."));
			}
			else
			{
				UE_LOG(LogTemp, Display, TEXT("Set presence to online failed."));
			}
		}));
}

void AMyPlayerController::SetPresenceToAway()
{
	Player->PresenceApi().SetAsAway(
		UPragmaPresenceApi::FOnCompleteDelegate::CreateWeakLambda(this, [](const TPragmaResult<>& Result)
		{
			if (Result.IsSuccessful())
			{
				UE_LOG(LogTemp, Display, TEXT("Set presence to away succeeded."));
			}
			else
			{
				UE_LOG(LogTemp, Display, TEXT("Set presence to away failed."));
			}
		}));
}


void AMyPlayerController::SetInShop(const bool IsInShop)
{

	FPragma_Presence_ExtRichPresencePlayerRequest Request;
	Request.Update.SetInShop(IsInShop);

	UPragmaPresenceApi::FOnCompleteDelegate SetRichPresenceDelegate;

	SetRichPresenceDelegate.BindWeakLambda(this, [IsInShop](TPragmaResult<> Result)
	{
		if (Result.IsSuccessful())
		{
			UE_LOG(LogTemp, Display,
				TEXT("Set in shop to %s"), IsInShop ? TEXT("true") : TEXT("false"));
		}
		else
		{
			UE_LOG(LogTemp, Warning,
				TEXT("Unable to set rich presence: %s"), *Result.Error().ToString());
		}
	});

	Player->PresenceApi().SetRichPresence(Request, SetRichPresenceDelegate);
}


// Friend functions

void AMyPlayerController::SendFriendInviteByUsername(const FString& FullUsername)
{
	FString DisplayName, Discriminator;
	FullUsername.Split("#", &DisplayName, &Discriminator);

	UPragmaFriendApi::FOnCompleteDelegate SendInviteByDisplayDelegate;
	SendInviteByDisplayDelegate.BindWeakLambda(this, [DisplayName, Discriminator](TPragmaResult<> Result)
	{
		if (Result.IsSuccessful())
		{
			UE_LOG(LogTemp, Display, TEXT("Sent friend invite to display name: %s"), *DisplayName);
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("Pragma unable to send friend invite by display name. %s %s %s"), *DisplayName, *Discriminator, *Result.Error().ToString());
		}
	});

	Player->FriendApi().SendFriendInviteByDisplayName(
	FPragma_Account_DisplayName{DisplayName, Discriminator},
	SendInviteByDisplayDelegate);
}

void AMyPlayerController::SendFriendInviteBySocialId(const FString& SocialId)
{
	UPragmaFriendApi::FOnCompleteDelegate SendInviteBySocialDelegate;
	SendInviteBySocialDelegate.BindWeakLambda(this, [=, this](TPragmaResult<> Result)
	{
		if (Result.IsSuccessful())
		{
			UE_LOG(LogTemp, Display, TEXT("Sent friend invite to id: %s"), *SocialId);
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("Pragma unable to send friend invite by ID. %s"), *Result.Error().ToString());
		}
	});

	Player->FriendApi().SendFriendInviteBySocialId(
	SocialId,
	SendInviteBySocialDelegate);
}

void AMyPlayerController::AcceptFriendInvite(const FString& InviterSocialId)
{
	UPragmaFriendApi::FOnCompleteDelegate RespondFriendInviteDelegate;
	RespondFriendInviteDelegate.BindWeakLambda(this, [=, this](TPragmaResult<> Result)
	{
		if (Result.IsSuccessful())
		{
			UE_LOG(LogTemp, Display, TEXT("Accepted friend invite from %s."), *InviterSocialId)
		}
		else
		{
			UE_LOG(LogTemp, Warning,
				TEXT("Unable to respond: %s"), *Result.Error().ToString());
		}
	});

	Player->FriendApi().AcceptFriendInvite(
		InviterSocialId,
		RespondFriendInviteDelegate
	);
}


void AMyPlayerController::DeclineFriendInvite(const FString& InviterSocialId)
{
	UPragmaFriendApi::FOnCompleteDelegate RespondFriendInviteDelegate;
	RespondFriendInviteDelegate.BindWeakLambda(this, [=, this](TPragmaResult<> Result)
	{
		if (Result.IsSuccessful())
		{
			UE_LOG(LogTemp, Display, TEXT("Declined friend invite from %s."), *InviterSocialId)
		}
		else
		{
			UE_LOG(LogTemp, Warning,
				TEXT("Unable to respond: %s"), *Result.Error().ToString());
		}
	});

	Player->FriendApi().DeclineFriendInvite(
		InviterSocialId,
		RespondFriendInviteDelegate
	);
}

void AMyPlayerController::RemoveFriend(const FString FriendSocialID)
{
	UPragmaFriendApi::FOnCompleteDelegate RespondRemoveFriendDelegate;
	RespondRemoveFriendDelegate.BindWeakLambda(this, [=, this](TPragmaResult<> Result)
	{
		if (Result.IsSuccessful())
		{
			UE_LOG(LogTemp, Display, TEXT("Removed friend: %s."), *FriendSocialID)
		}
		else
		{
			UE_LOG(LogTemp, Warning,
				TEXT("Unable to remove friend: %s"), *Result.Error().ToString());
		}
	});

	Player->FriendApi().RemoveFriend(
	FriendSocialID,
	RespondRemoveFriendDelegate
);
}


//Handler functions

void AMyPlayerController::HandleLoggedIn(const TPragmaResult<>& Result)
{
    if (Result.IsFailure())
    {
        UE_LOG(LogTemp, Display, TEXT("Pragma -- Login failed: %s"), *Result.ErrorCode());
        return;
    }
    UE_LOG(LogTemp, Display, TEXT("Pragma -- Logged in."));
}

void AMyPlayerController::HandleLoggedOut()
{
    UE_LOG(LogTemp, Display, TEXT("Pragma -- Logged out."));
}

void AMyPlayerController::HandleOnAddedToGame(const FString GameInstanceId, const FPragma_GameInstance_ExtAddedToGame Ext)
{
	UE_LOG(LogTemp, Display, TEXT("Game instance ID: %s"), *GameInstanceId );
}

void AMyPlayerController::HandleOnPresenceChanged(const FPragmaPresence& MyPresence)
{
	UE_LOG(LogTemp, Display, TEXT("Presence status changed: "
			"\n Basic: %s, \n Rich: %s, %s"), *UEnum::GetValueAsString<EBasicPresence>(MyPresence.BasicPresence),
		MyPresence.Ext.InShop ? TEXT("In shop") : TEXT("Not in shop"),
		MyPresence.Ext.InParty ? TEXT("In party") : TEXT("Not in party"));
}


//Get functions

//Get player's username (display name and discriminator)
FString AMyPlayerController::GetFullUsername()
{
	UE_LOG(LogTemp, Display, TEXT("Your full username: %s"), *Player->FullDisplayName());
	return Player->FullDisplayName();
}

//Get player's UUID
FString AMyPlayerController::GetPlayerId()
{
	UE_LOG(LogTemp, Display, TEXT("Your player ID: %s"), *Player->Id());
	return Player->Id();
}

FString AMyPlayerController::GetSocialId()
{
	UE_LOG(LogTemp, Display, TEXT("Your social ID: %s"), *Player->SocialId());
	return Player->SocialId();
}


//Get list of player objects for players in the party
TArray<FString> AMyPlayerController::GetPartyPlayers()
{
	
	const TArray<const UPragmaPartyPlayer*> PartyPlayers = Player->GameLoopApi().GetPartyState()->Party()->GetPlayers();
	
	TArray<FString> DisplayNames;

	const FString& YourPlayerId = Player->Id();

	for (const UPragmaPartyPlayer* PartyPlayer : PartyPlayers)
	{
		FString DisplayName = PartyPlayer->GetDisplayName().DisplayName;

		FString PlayerId = PartyPlayer->GetPlayerId();

		FString SocialId = PartyPlayer->GetSocialId();

		if (PlayerId == YourPlayerId)
		{
			DisplayName += " (You)";
		}

		if (PartyPlayer->IsLeader())
		{
			DisplayName += " (Leader)";
		}

		if (PartyPlayer->IsReady())
		{
			DisplayName += " (Ready)";
		}

		DisplayNames.Add(DisplayName);

		UE_LOG(LogTemp, Display, TEXT("Display name: %s, Player ID: %s, Social ID: %s"), *DisplayName, *PlayerId, *SocialId);
	}

	return DisplayNames;
}

//Get list of pending friend invites
TArray<FPragmaFriendOverview> AMyPlayerController::GetFriendInvites()
{
	using FReceivedInvites = TMap<FString, FPragmaFriendOverview>;
	
	FReceivedInvites PendingReceivedInvites = *Player->FriendApi().GetReceivedInvites();

	TArray<FPragmaFriendOverview> InviteList;

	for (auto&PendingFriendInvite : PendingReceivedInvites)
	{
		FString PendingFriendSocialId = PendingFriendInvite.Value.SocialId();

		InviteList.Add(PendingFriendInvite.Value);

		UE_LOG(LogTemp, Display, TEXT("You have a pending invite from Social ID: %s"), *PendingFriendSocialId);
	}

	return InviteList;
}

//Get list of friends
TMap<FString, FPragmaFriend> AMyPlayerController::GetFriends()
{
	TMap<FString, FPragmaFriend> MyFriendList = *Player->FriendApi().GetFriends();
	
	UE_LOG(LogTemp, Display, TEXT("Friends: "));

	for (auto&MyFriend : MyFriendList)
	{
		UE_LOG(LogTemp, Display, TEXT("%s"), *MyFriend.Key);
	}
	return MyFriendList;
}

Next steps #

Congratulations! You now have a custom Unreal implementation of the Pragma Engine Friend service. Read our Friend service docs to learn about more features you can apply to your game.