Files
UnrealEngine/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/Chatroom.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

299 lines
9.3 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Chatroom.h"
#include "Engine/GameInstance.h"
#include "TimerManager.h"
#include "Engine/LocalPlayer.h"
#include "OnlineSubsystemUtils.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(Chatroom)
inline FString GetLocalUserNickName(UWorld* World, const FUniqueNetId& LocalUserId)
{
check(World);
UGameInstance* GameInstance = World->GetGameInstance();
check(GameInstance);
ULocalPlayer* LP = GameInstance->FindLocalPlayerFromUniqueNetId(LocalUserId);
if (LP)
{
return LP->GetNickname();
}
return TEXT("INVALID");
}
UChatroom::UChatroom()
: MaxChatRoomRetries(5)
, NumChatRoomRetries(0)
{
}
void UChatroom::UnregisterDelegates()
{
IOnlineChatPtr ChatInt = Online::GetChatInterface(GetWorld());
if (ChatInt.IsValid())
{
if (ChatRoomCreatedDelegateHandle.IsValid())
{
// Failsafe if chat CreateRoom never completes (xmpp is misbehaving)
ChatInt->ClearOnChatRoomCreatedDelegate_Handle(ChatRoomCreatedDelegateHandle);
}
}
}
void UChatroom::CreateOrJoinChatRoom(FUniqueNetIdRepl LocalUserId, FChatRoomId ChatRoomId, FOnChatRoomCreatedOrJoined CompletionDelegate, FChatRoomConfig RoomConfig)
{
bool bShouldFireDelegate = false;
if (!ChatRoomId.IsEmpty())
{
if (LocalUserId.IsValid())
{
UWorld* World = GetWorld();
IOnlineChatPtr ChatInt = Online::GetChatInterface(World);
if (ChatInt.IsValid())
{
if (!IsAlreadyInChatRoom(LocalUserId, ChatRoomId))
{
if (CurrentChatRoomId.IsEmpty())
{
UE_LOG(LogOnlineChat, Verbose, TEXT("[UChatroom::CreateOrJoinChatRoom] attempting to join %s"), *ChatRoomId);
CurrentChatRoomId = ChatRoomId;
FTimerManager& TM = GetTimerManager();
if (TM.GetTimerRemaining(ChatRoomRetryTimerHandle) > 0)
{
TM.ClearTimer(ChatRoomRetryTimerHandle);
}
FOnChatRoomJoinPublicDelegate ChatRoomDelegate;
ChatRoomDelegate.BindUObject(this, &ThisClass::OnChatRoomCreatedOrJoined, CompletionDelegate, RoomConfig);
// Try to create the room first (it will create if it doesn't exist, or just join if it does)
ChatRoomCreatedDelegateHandle = ChatInt->AddOnChatRoomCreatedDelegate_Handle(ChatRoomDelegate);
ChatInt->CreateRoom(*LocalUserId, ChatRoomId, GetLocalUserNickName(World, *LocalUserId), RoomConfig);
}
else
{
if (ChatRoomId == CurrentChatRoomId)
{
UE_LOG(LogOnlineChat, Verbose, TEXT("[UChatroom::CreateOrJoinChatRoom] already joining %s"), *ChatRoomId);
}
else
{
UE_LOG(LogOnlineChat, Verbose, TEXT("[UChatroom::CreateOrJoinChatRoom] can't join %s already joining %s"), *ChatRoomId, *CurrentChatRoomId);
bShouldFireDelegate = true;
}
}
}
else
{
UE_LOG(LogOnlineChat, Verbose, TEXT("[UChatroom::CreateOrJoinChatRoom] already joined %s"), *ChatRoomId);
}
}
}
else
{
UE_LOG(LogOnlineChat, Verbose, TEXT("[UChatroom::CreateOrJoinChatRoom] invalid user id"));
bShouldFireDelegate = true;
}
}
else
{
UE_LOG(LogOnlineChat, Verbose, TEXT("[UChatroom::CreateOrJoinChatRoom] invalid chat room id"));
bShouldFireDelegate = true;
}
if (bShouldFireDelegate)
{
CompletionDelegate.ExecuteIfBound(ChatRoomId, false);
}
}
void UChatroom::OnChatRoomCreatedOrJoined(const FUniqueNetId& LocalUserId, const FChatRoomId& RoomId, bool bWasSuccessful, const FString& Error, FOnChatRoomCreatedOrJoined CompletionDelegate, FChatRoomConfig RoomConfig)
{
if (CurrentChatRoomId == RoomId)
{
UE_LOG(LogOnlineChat, Verbose, TEXT("[UChatroom::OnChatRoomCreatedOrJoined] %s joined room %s Success: %d%s"), *LocalUserId.ToString(), *RoomId, bWasSuccessful, !Error.IsEmpty() ? *FString::Printf(TEXT(" %s"), *Error) : TEXT(""));
// Don't clear if this delegate failed because of existing pending join
const bool bAlreadyJoining = !Error.IsEmpty() && Error.Contains(TEXT("operation pending"));
if (!bAlreadyJoining)
{
IOnlineChatPtr ChatInt = Online::GetChatInterface(GetWorld());
if (ChatInt.IsValid())
{
ChatInt->ClearOnChatRoomCreatedDelegate_Handle(ChatRoomCreatedDelegateHandle);
}
if (bWasSuccessful)
{
FTimerDelegate Delegate = FTimerDelegate::CreateLambda([RoomId, CompletionDelegate, bWasSuccessful]()
{
// Announce that chat is available
CompletionDelegate.ExecuteIfBound(RoomId, bWasSuccessful);
});
GetTimerManager().SetTimerForNextTick(Delegate);
}
else
{
if (NumChatRoomRetries < MaxChatRoomRetries)
{
NumChatRoomRetries++;
UE_LOG(LogOnlineChat, Verbose, TEXT("[UChatroom::OnChatRoomCreatedOrJoined] retry %d/%d"), NumChatRoomRetries, MaxChatRoomRetries);
FUniqueNetIdRepl StrongUserId(LocalUserId.AsShared());
auto RetryDelegate = FTimerDelegate::CreateWeakLambda(this, [this, StrongUserId, RoomId, CompletionDelegate, RoomConfig] ()
{
// Attempt to rejoin room shortly
CreateOrJoinChatRoom(StrongUserId, RoomId, CompletionDelegate, RoomConfig);
});
GetTimerManager().SetTimer(ChatRoomRetryTimerHandle, RetryDelegate, .3f, false);
}
else
{
UE_LOG(LogOnlineChat, Verbose, TEXT("[UChatroom::OnChatRoomCreatedOrJoined] exceeded %d retries"), MaxChatRoomRetries);
}
// Clear out the chat id since we failed to join
CurrentChatRoomId.Empty();
}
}
else
{
UE_LOG(LogOnlineChat, Verbose, TEXT("[UChatroom::OnChatRoomCreatedForRejoin] already attempting to join %s"), *CurrentChatRoomId);
}
}
else
{
UE_LOG(LogOnlineChat, Verbose, TEXT("[UChatroom::OnChatRoomCreatedForRejoin] other chat room detected %s, waiting for %s"), *RoomId, *CurrentChatRoomId);
}
}
void UChatroom::LeaveChatRoom(const FUniqueNetIdRepl& LocalUserId, const FOnChatRoomLeft& CompletionDelegate)
{
if (!CurrentChatRoomId.IsEmpty())
{
UE_LOG(LogOnlineChat, Verbose, TEXT("[UChatroom::LeaveChatRoom] %s leaving chat room %s"),
*LocalUserId.ToString(),
*CurrentChatRoomId);
// Always reset these regardless of chat room
GetTimerManager().ClearTimer(ChatRoomRetryTimerHandle);
ChatRoomRetryTimerHandle.Invalidate();
NumChatRoomRetries = 0;
// ExitRoom callback could fire inline if it fails, make a copy so the ensure in ChatRoomLeftInternal does not hit
FChatRoomId ChatRoomIdCpy = CurrentChatRoomId;
CurrentChatRoomId.Empty();
if (IsOnline())
{
// Leave chat
IOnlineChatPtr ChatInt = Online::GetChatInterface(GetWorld());
if (ensure(ChatInt.IsValid()))
{
FOnChatRoomCreatedDelegate ChatRoomDelegate;
ChatRoomDelegate.BindUObject(this, &ThisClass::OnChatRoomLeft, ChatRoomIdCpy, CompletionDelegate);
ChatRoomLeftDelegateHandle = ChatInt->AddOnChatRoomExitDelegate_Handle(ChatRoomDelegate);
ChatInt->ExitRoom(*LocalUserId, ChatRoomIdCpy);
}
}
else
{
UE_LOG(LogOnlineChat, Display, TEXT("[UChatroom::LeaveChatRoom] Left chat while not logged in"));
// Clear out the chat room to avoid ensures (logged out, not in this room anymore)
ChatRoomLeftInternal(ChatRoomIdCpy, CompletionDelegate);
}
}
else
{
UE_LOG(LogOnlineChat, Verbose, TEXT("[UChatroom::LeaveChatRoom] %s no chat room to leave."), *LocalUserId.ToDebugString());
}
}
void UChatroom::OnChatRoomLeft(const FUniqueNetId& LocalUserId, const FChatRoomId& RoomId, bool bWasSuccessful, const FString& Error, FChatRoomId ChatRoomIdCopy, FOnChatRoomLeft CompletionDelegate)
{
if (ChatRoomIdCopy == RoomId)
{
// Not much to do failure or otherwise, just print logs here
UE_LOG(LogOnlineChat, Verbose, TEXT("[UChatroom::OnChatRoomLeft] %s left chat room %s Success: %d%s"), *LocalUserId.ToDebugString(), *RoomId, bWasSuccessful, !Error.IsEmpty() ? *FString::Printf(TEXT(" %s"), *Error) : TEXT(""));
IOnlineChatPtr ChatInt = Online::GetChatInterface(GetWorld());
if (ensure(ChatInt.IsValid()))
{
ChatInt->ClearOnChatRoomExitDelegate_Handle(ChatRoomLeftDelegateHandle);
}
ChatRoomLeftInternal(RoomId, CompletionDelegate);
}
else
{
UE_LOG(LogOnlineChat, Verbose, TEXT("[UChatroom::OnChatRoomLeft] other chat room detected %s, waiting for %s"), *RoomId, *ChatRoomIdCopy);
}
}
void UChatroom::ChatRoomLeftInternal(const FChatRoomId& RoomId, const FOnChatRoomLeft& CompletionDelegate)
{
GetTimerManager().SetTimerForNextTick(FTimerDelegate::CreateLambda([CompletionDelegate, RoomId]()
{
// Announce that chat is available
CompletionDelegate.ExecuteIfBound(RoomId);
}));
}
bool UChatroom::IsOnline() const
{
return true;
}
bool UChatroom::IsAlreadyInChatRoom(const FUniqueNetIdRepl& LocalUserId, const FChatRoomId& ChatRoomId) const
{
if (ensure(LocalUserId.IsValid()))
{
UWorld* World = GetWorld();
IOnlineChatPtr ChatInt = Online::GetChatInterface(World);
if (ensure(ChatInt.IsValid()))
{
TArray<FChatRoomId> JoinedRooms;
ChatInt->GetJoinedRooms(*LocalUserId, JoinedRooms);
auto Pred = [&ChatRoomId](const FChatRoomId& OtherChatRoomId)
{
return OtherChatRoomId == ChatRoomId;
};
return JoinedRooms.ContainsByPredicate(Pred);
}
}
return false;
}
UWorld* UChatroom::GetWorld() const
{
if (HasAnyFlags(RF_ClassDefaultObject))
{
UE_LOG(LogOnlineChat, Warning, TEXT("[UChatroom::GetWorld] Called GetWorld on the CDO"));
return nullptr;
}
else
{
UWorld* World = GetOuter() ? GetOuter()->GetWorld() : nullptr;
checkf(World, TEXT("[UChatroom::GetWorld] Should have an outer that can access a world"));
return World;
}
}
FTimerManager& UChatroom::GetTimerManager() const
{
UWorld* World = GetWorld();
check(World);
return World->GetTimerManager();
}