Skip to main content

Implement Subsystem - Private Chat - (Unreal Engine module)

Last updated on November 26, 2024
info

While you don't have to set anything up in the AccelByte Gaming Services (AGS) Admin Portal to use the private chat feature, there are some parameters you can change there if you want, such as character limit, spam protection, etc. Refer to Introduction to Chat for a more information.

Unwrap the Subsystem

In this tutorial, you will learn how to implement private chat using the AGS Online Subsystem (OSS). In the Byte Wars project, there is already a game instance subsystem created named PrivateChatSubsystem. This subsystem contains private chat-related functions. In this tutorial, you will use a starter version of that subsystem so you can implement private chat from scratch.

What's in the Starter Pack

To follow this tutorial, a starter subsystem class named PrivateChatSubsystem_Starter has been prepared for you. This class consists of the following files:

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

The PrivateChatSubsystem_Starter class has several functions provided.

  • An AGS OSS chat interface you can use to implement private chat-related functions:
protected:
// ...
FOnlineChatAccelBytePtr GetChatInterface() const;
FOnlineChatAccelBytePtr UPrivateChatSubsystem_Starter::GetChatInterface() const
{
const IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld());
if (!ensure(Subsystem))
{
UE_LOG_PRIVATECHAT(Warning, TEXT("The online subsystem is invalid. Please make sure OnlineSubsystemAccelByte is enabled and DefaultPlatformService under [OnlineSubsystem] in the Engine.ini set to AccelByte."));
return nullptr;
}

return StaticCastSharedPtr<FOnlineChatAccelByte>(Subsystem->GetChatInterface());
}
  • A utility to prompt a notification to the game UI. You will need this to show notifications when the player receives new private chat messages.
protected:
// ...
UPromptSubsystem* GetPromptSubsystem() const;
UPromptSubsystem* UPrivateChatSubsystem_Starter::GetPromptSubsystem() const
{
const UAccelByteWarsGameInstance* GameInstance = Cast<UAccelByteWarsGameInstance>(GetGameInstance());
if (!GameInstance)
{
return nullptr;
}

return GameInstance->GetSubsystem<UPromptSubsystem>();
}
  • A function to show how to push a notification to the Byte Wars notification system. It takes the player name and shows a notifacation stating that a message from said player was receieved.
protected:
// ...
void PushPrivateChatMessageReceivedNotification(const FUniqueNetId& UserId, const TSharedRef<FChatMessage>& Message);
void UPrivateChatSubsystem_Starter::PushPrivateChatMessageReceivedNotification(const FUniqueNetId& UserId, const TSharedRef<FChatMessage>& Message)
{
if (!GetPromptSubsystem())
{
UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot push private chat message received notification. Prompt Subsystem is not valid."));
return;
}

const FUniqueNetIdAccelByteUserRef SenderABId = StaticCastSharedRef<const FUniqueNetIdAccelByteUser>(Message->GetUserId());
if (!SenderABId.Get().IsValid())
{
UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot push private chat message received notification. Sender User Id is not valid."));
return;
}

// Only push a notification only if the player is not in the chat menu of the same recipient.
const UCommonActivatableWidget* ActiveWidget = UAccelByteWarsBaseUI::GetActiveWidgetOfStack(EBaseUIStackType::Menu, this);
if (const UPrivateChatWidget_Starter* PrivateChatWidget = StaticCast<const UPrivateChatWidget_Starter*>(ActiveWidget))
{
const FUniqueNetIdAccelByteUserPtr CurrentRecipientABId = StaticCastSharedPtr<const FUniqueNetIdAccelByteUser>(PrivateChatWidget->GetPrivateChatRecipient());

if (!CurrentRecipientABId || CurrentRecipientABId->GetAccelByteId().Equals(SenderABId->GetAccelByteId()))
{
return;
}
}

// If the sender doesn't have display name, then use the default display name.
FString SenderStr = Message.Get().GetNickname();
if (SenderStr.IsEmpty() || SenderABId.Get().GetAccelByteId().Equals(SenderStr))
{
SenderStr = UTutorialModuleOnlineUtility::GetUserDefaultDisplayName(Message->GetUserId().Get());
}

GetPromptSubsystem()->PushNotification(FText::Format(PRIVATE_CHAT_RECEIVED_MESSAGE, FText::FromString(SenderStr)));
}

Implement sending private chat messages

In this section, you will implement functions to send a private chat message.

  1. Open the PrivateChatSubsystem_Starter class header file and declare the following function:

    public:
    // ...
    void SendPrivateChatMessage(const FUniqueNetIdPtr UserId, const FUniqueNetIdPtr RecipientUserId, const FString& Message);
  2. Next, declare a delegate and a callback function to handle when a private chat message is sent. You will use this delegate to connect with the private chat UI later.

    public:
    // ...
    FOnSendChatComplete* GetOnSendPrivateChatCompleteDelegates()
    {
    return &OnSendPrivateChatCompleteDelegates;
    }
    protected:
    void OnSendPrivateChatComplete(FString UserId, FString MsgBody, FString RoomId, bool bWasSuccessful);
    private:
    FOnSendChatComplete OnSendPrivateChatCompleteDelegates;
  3. Now, define the functions above. Open the PrivateChatSubsystem_Starter class CPP file and define the SendPrivateChatMessage() function first to send a private chat message.

    void UPrivateChatSubsystem_Starter::SendPrivateChatMessage(const FUniqueNetIdPtr UserId, const FUniqueNetIdPtr RecipientUserId, const FString& Message)
    {
    if (!GetChatInterface())
    {
    UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot send private chat message. Chat Interface is not valid."));
    OnSendPrivateChatComplete(FString(), Message, FString(), false);
    return;
    }

    if (!UserId)
    {
    UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot send private chat message. User NetId is not valid."));
    OnSendPrivateChatComplete(FString(), Message, FString(), false);
    return;
    }

    if (!RecipientUserId)
    {
    UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot send private chat message. Recipient NetId is not valid."));
    OnSendPrivateChatComplete(FString(), Message, FString(), false);
    return;
    }

    GetChatInterface()->SendPrivateChat(UserId.ToSharedRef().Get(), RecipientUserId.ToSharedRef().Get(), Message);
    }
  4. Next, define the OnSendPrivateChatComplete() function. When the send private chat request is complete, it will broadcast the delegate you created earlier to inform you that the process has completed.

    void UPrivateChatSubsystem_Starter::OnSendPrivateChatComplete(FString UserId, FString MsgBody, FString RoomId, bool bWasSuccessful)
    {
    // Abort if the room id is not a private chat room.
    if (!GetChatInterface() || GetChatInterface()->GetChatRoomType(RoomId) != EAccelByteChatRoomType::PERSONAL)
    {
    return;
    }

    if (bWasSuccessful)
    {
    UE_LOG_PRIVATECHAT(Log, TEXT("Success to send chat message on Room %s"), *RoomId);
    }
    else
    {
    UE_LOG_PRIVATECHAT(Warning, TEXT("Failed to send chat message on Room %s"), *RoomId);
    }

    OnSendPrivateChatCompleteDelegates.Broadcast(UserId, MsgBody, RoomId, bWasSuccessful);
    }
  5. Then, you need to bind the callback function above so it will be called when the private chat message is sent. You can do this by adding the following code to the Initialize() function, which is the first function to be called when the subsystem is initialized.

    void UPrivateChatSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
    {
    // ...
    if (GetChatInterface())
    {
    GetChatInterface()->OnSendChatCompleteDelegates.AddUObject(this, &ThisClass::OnSendPrivateChatComplete);
    // ...
    }
    }
  6. Finally, you need to unbind the callback function when the subsystem is deinitialized. You can do this by adding the following code:

    void UPrivateChatSubsystem_Starter::Deinitialize()
    {
    // ...
    if (GetChatInterface())
    {
    GetChatInterface()->OnSendChatCompleteDelegates.RemoveAll(this);
    // ...
    }
    }

Congratulations! You have implemented sending private chat messages.

Implement receiving private chat messages

In this section, you will implement functions to handle when a private chat is received.

  1. Open the PrivateChatSubsystem_Starter class header file and declare a delegate and a callback function to handle when a private chat message is received. You will use this to show a notification when a new chat is received. You will also use this delegate to connect with the private chat UI later to show the received message.

    public:
    // ...
    FOnChatPrivateMessageReceived* GetOnPrivateChatMessageReceivedDelegates()
    {
    return &OnPrivateChatMessageReceivedDelegates;
    }
    protected:
    // ...
    void OnPrivateChatMessageReceived(const FUniqueNetId& UserId, const TSharedRef<FChatMessage>& Message);
    private:
    // ...
    FOnChatPrivateMessageReceived OnPrivateChatMessageReceivedDelegates;
  2. Next, open the PrivateChatSubsystem_Starter class CPP file and define the OnPrivateChatMessageReceived() function. When the private chat is received, it will broadcast the delegate you created earlier.

    void UPrivateChatSubsystem_Starter::OnPrivateChatMessageReceived(const FUniqueNetId& UserId, const TSharedRef<FChatMessage>& Message)
    {
    UE_LOG_PRIVATECHAT(Log,
    TEXT("Received private chat message from %s: %s"),
    !Message.Get().GetNickname().IsEmpty() ? *Message.Get().GetNickname() : *UTutorialModuleOnlineUtility::GetUserDefaultDisplayName(Message->GetUserId().Get()),
    *Message.Get().GetBody());

    OnPrivateChatMessageReceivedDelegates.Broadcast(UserId, Message);
    }
  3. Then, you need to bind the callback function above so it will be called when the private chat message is received. You can do this by adding the following code to the Initialize() function, which is the first function to be called when the subsystem is initialized. Here, you also bind a predefined function named PushPrivateChatMessageReceivedNotification() to show a notification when a new chat is received.

    void UPrivateChatSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
    {
    // ...
    if (GetChatInterface())
    {
    // ...
    GetChatInterface()->OnChatPrivateMessageReceivedDelegates.AddUObject(this, &ThisClass::OnPrivateChatMessageReceived);

    // Push a notification when received chat messages.
    GetChatInterface()->OnChatPrivateMessageReceivedDelegates.AddUObject(this, &ThisClass::PushPrivateChatMessageReceivedNotification);
    }
    }
  4. Finally, you need to unbind the callback function when the subsystem is deinitialized. You can do this by adding the following code:

    void UPrivateChatSubsystem_Starter::Deinitialize()
    {
    // ...
    if (GetChatInterface())
    {
    // ...
    GetChatInterface()->OnChatPrivateMessageReceivedDelegates.RemoveAll(this);
    }
    }

Congratulations! You have implemented receiving private chat messages.

Implement getting last private chat messages

In this section, you will implement functions to get the last private chat messages.

  1. Open the PrivateChatSubsystem_Starter class header file and declare the following function:

    public:
    // ...
    bool GetLastPrivateChatMessages(const FUniqueNetIdPtr UserId, const FChatRoomId& RoomId, const int32 NumMessages, TArray<TSharedRef<FChatMessage>>& OutMessages);
  2. Next, open the PrivateChatSubsystem_Starter class CPP file and define the function above. This function will return the last chat messages saved in the game cache.

    note

    All of the chat messages are saved in the game cache at runtime. When you close the game, the chat messages will be deleted.

    bool UPrivateChatSubsystem_Starter::GetLastPrivateChatMessages(const FUniqueNetIdPtr UserId, const FChatRoomId& RoomId, const int32 NumMessages, TArray<TSharedRef<FChatMessage>>& OutMessages)
    {
    if (!GetChatInterface())
    {
    UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot get last chat messages. Chat Interface is not valid."));
    return false;
    }

    if (!UserId)
    {
    UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot get last chat messages. User NetId is not valid."));
    return false;
    }

    // Abort if not a private chat room.
    if (RoomId.IsEmpty() || GetChatInterface()->GetChatRoomType(RoomId) != EAccelByteChatRoomType::PERSONAL)
    {
    UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot get last chat messages. Room Id is empty or not a private chat room."));
    return false;
    }

    GetChatInterface()->GetLastMessages(UserId.ToSharedRef().Get(), RoomId, NumMessages, OutMessages);
    UE_LOG_PRIVATECHAT(Log, TEXT("Success to get last chat messages. Number of returned messages: %d"), OutMessages.Num());

    return true;
    }

Congratulations! You have implemented getting the last private chat messages.

Implement private chat utilities

In this section, you will implement some utilities of the private chat.

  1. Open the PrivateChatSubsystem_Starter class header file and declare the following functions:

    public:
    // ...
    FString GetPrivateChatRoomId(const FUniqueNetIdPtr SenderUserId, const FUniqueNetIdPtr RecipientUserId);
    bool IsMessageFromLocalUser(const FUniqueNetIdPtr UserId, const FChatMessage& Message);
  2. Next, open the PrivateChatSubsystem_Starter class CPP file and define GetPrivateChatRoomId(). This function will return the private chat room ID. It can be used to differentiate between received chat types (e.g., session chat vs. private chat).

    FString UPrivateChatSubsystem_Starter::GetPrivateChatRoomId(const FUniqueNetIdPtr SenderUserId, const FUniqueNetIdPtr RecipientUserId)
    {
    if (!GetChatInterface())
    {
    return FString();
    }

    if (!SenderUserId || !RecipientUserId)
    {
    return FString();
    }

    const FUniqueNetIdAccelByteUserPtr SenderABId = StaticCastSharedPtr<const FUniqueNetIdAccelByteUser>(SenderUserId);
    const FUniqueNetIdAccelByteUserPtr RecipientABId = StaticCastSharedPtr<const FUniqueNetIdAccelByteUser>(RecipientUserId);
    if (!SenderABId || !RecipientABId)
    {
    return FString();
    }

    return GetChatInterface()->PersonalChatTopicId(SenderABId->GetAccelByteId(), RecipientABId->GetAccelByteId());
    }
  3. Still in the same file, define the IsMessageFromLocalUser() function. This function checks whether the chat is from the local player or other players. It can be used for displaying the chat message later by differentiating how you display received messages and sent messages (e.g., colors and alignment).

    bool UPrivateChatSubsystem_Starter::IsMessageFromLocalUser(const FUniqueNetIdPtr UserId, const FChatMessage& Message)
    {
    if (!GetChatInterface())
    {
    UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot check whether chat message is from local user or not. Chat Interface is not valid."));
    return false;
    }

    if (!UserId)
    {
    UE_LOG_PRIVATECHAT(Warning, TEXT("Cannot check whether chat message is from local user or not. User NetId is not valid."));
    return false;
    }

    return GetChatInterface()->IsMessageFromLocalUser(UserId.ToSharedRef().Get(), Message, true);
    }

Congratulations! You have implemented private chat utilities.

Resources