Files
UnrealEngine/Engine/Plugins/Online/OnlineSubsystemTencent/Source/Private/OnlinePresenceTencent.cpp
2025-05-18 13:04:45 +08:00

347 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "OnlinePresenceTencent.h"
#if WITH_TENCENTSDK
#if WITH_TENCENT_RAIL_SDK
#include "OnlineSubsystemTencent.h"
#include "OnlineIdentityTencent.h"
#include "OnlineSessionTencent.h"
#include "MetadataKeysRail.h"
FOnlinePresenceTencent::FOnlinePresenceTencent(FOnlineSubsystemTencent* InSubsystem)
: TencentSubsystem(InSubsystem)
{
}
FOnlinePresenceTencent::~FOnlinePresenceTencent()
{
IOnlineIdentityPtr IdentityInt = TencentSubsystem->GetIdentityInterface();
if (IdentityInt.IsValid())
{
IdentityInt->ClearOnLoginChangedDelegate_Handle(OnLoginChangedHandle);
}
}
bool FOnlinePresenceTencent::Init()
{
IOnlineIdentityPtr IdentityInt = TencentSubsystem->GetIdentityInterface();
if (IdentityInt.IsValid())
{
OnLoginChangedHandle = IdentityInt->AddOnLoginChangedDelegate_Handle(FOnLoginChangedDelegate::CreateThreadSafeSP(this, &FOnlinePresenceTencent::OnLoginChanged));
FOnFriendMetadataChangedDelegate Delegate = FOnFriendMetadataChangedDelegate::CreateThreadSafeSP(this, &FOnlinePresenceTencent::OnFriendMetadataChangedEvent);
OnFriendMetadataChangedDelegateHandle = TencentSubsystem->AddOnFriendMetadataChangedDelegate_Handle(Delegate);
if (rail::IRailFriends* RailFriends = RailSdkWrapper::Get().RailFriends())
{
// Cache the initial online state of all friends
rail::RailArray<rail::RailFriendInfo> Friends;
rail::RailResult Result = RailFriends->GetFriendsList(&Friends);
if (Result == rail::kSuccess)
{
TArray<FUniqueNetIdRef> FriendIds;
for (uint32 RailIdx = 0; RailIdx < Friends.size(); ++RailIdx)
{
const rail::RailFriendInfo& RailFriendInfo(Friends[RailIdx]);
if (RailFriendInfo.friend_rail_id != rail::kInvalidRailId)
{
FUniqueNetIdRef FriendId(FUniqueNetIdRail::Create(RailFriendInfo.friend_rail_id));
UE_LOG_ONLINE_PRESENCE(VeryVerbose, TEXT("Friend Id: %s State: %s"), *FriendId->ToDebugString(), *LexToString(RailFriendInfo.online_state.friend_online_state));
SetUserOnlineState(*FriendId, RailOnlineStateToOnlinePresence(RailFriendInfo.online_state.friend_online_state));
}
else
{
UE_LOG_ONLINE_PRESENCE(Warning, TEXT("Invalid friend in friends list"));
}
}
}
else
{
UE_LOG_ONLINE_PRESENCE(Warning, TEXT("Unable to read friends friends list"));
}
}
else
{
UE_LOG_ONLINE_PRESENCE(Warning, TEXT("Rail friends interface is null"));
}
return true;
}
return false;
}
void FOnlinePresenceTencent::Shutdown()
{
TencentSubsystem->ClearOnFriendMetadataChangedDelegate_Handle(OnFriendMetadataChangedDelegateHandle);
}
void FOnlinePresenceTencent::SetUserOnlineState(const FUniqueNetId& UserId, EOnlinePresenceState::Type NewState)
{
const TSharedRef<FOnlineUserPresenceTencent>* Found = CachedPresence.Find(UserId.AsShared());
if (Found != nullptr)
{
(*Found)->bIsOnline = (NewState != EOnlinePresenceState::Type::Offline);
(*Found)->Status.State = NewState;
}
EOnlinePresenceState::Type& OnlineState = UserOnlineStatus.FindOrAdd(UserId.AsShared());
OnlineState = NewState;
}
void FOnlinePresenceTencent::OnLoginChanged(int32 LocalUserNum)
{
IOnlineIdentityPtr IdentityInt = TencentSubsystem->GetIdentityInterface();
if (IdentityInt.IsValid())
{
FUniqueNetIdRailPtr UserId = StaticCastSharedPtr<const FUniqueNetIdRail>(IdentityInt->GetUniquePlayerId(LocalUserNum));
if (UserId.IsValid())
{
ELoginStatus::Type LoginStatus = IdentityInt->GetLoginStatus(LocalUserNum);
if (LoginStatus == ELoginStatus::NotLoggedIn)
{
FOnlineAsyncTaskRailClearAllMetadata* NewTask = new FOnlineAsyncTaskRailClearAllMetadata(TencentSubsystem, *UserId);
TencentSubsystem->QueueAsyncTask(NewTask);
int32 NumRemoved = CachedPresence.Remove(UserId.ToSharedRef());
}
}
}
}
void FOnlinePresenceTencent::OnFriendMetadataChangedEvent(const FUniqueNetId& UserId, const FMetadataPropertiesRail& Metadata)
{
UE_LOG_ONLINE_PRESENCE(Verbose, TEXT("FOnlinePresenceTencent::OnFriendMetadataChangedEvent"));
}
bool IsPresenceEqual(const FOnlineUserPresenceStatus& A, const FOnlineUserPresenceStatus& B)
{
// Session and joinability details are updated internally and don't need comparison
const bool bEqual = (A.StatusStr == B.StatusStr) &&
(A.State == B.State) &&
(A.Properties.OrderIndependentCompareEqual(B.Properties));
return bEqual;
}
void FOnlinePresenceTencent::SetPresence(const FUniqueNetId& User, const FOnlineUserPresenceStatus& Status, const FOnPresenceTaskCompleteDelegate& Delegate)
{
bool bNeedsUpdate = true;
FUniqueNetIdRef UserPtr = User.AsShared();
TSharedRef<FOnlineUserPresenceTencent>* Cached = CachedPresence.Find(User.AsShared());
if (Cached != nullptr)
{
bNeedsUpdate = !IsPresenceEqual((*Cached)->Status, Status);
}
else
{
Cached = &CachedPresence.Add(User.AsShared(), MakeShared<FOnlineUserPresenceTencent>());
}
(*Cached)->Status = Status;
if (bNeedsUpdate)
{
FMetadataPropertiesRail Metadata;
Metadata.Add(RAIL_PRESENCE_STATUS_KEY, Status.StatusStr);
Metadata.Add(RAIL_PRESENCE_APPID_KEY, TencentSubsystem->GetAppId());
Metadata.Append(Status.Properties);
GetGameSessionPresenceData(Metadata);
FOnlineAsyncTaskRailSetUserMetadata* NewTask = new FOnlineAsyncTaskRailSetUserPresence(TencentSubsystem, Metadata, FOnOnlineAsyncTaskRailSetUserMetadataComplete::CreateLambda([Delegate, UserPtr](const FSetUserMetadataTaskResult& Result)
{
Delegate.ExecuteIfBound(*UserPtr, Result.Error.WasSuccessful());
}));
TencentSubsystem->QueueAsyncTask(NewTask);
}
else
{
UE_LOG_ONLINE_PRESENCE(Verbose, TEXT("SetPresence data already set."));
TencentSubsystem->ExecuteNextTick([UserPtr, Delegate]()
{
Delegate.ExecuteIfBound(*UserPtr, true);
});
}
}
void FOnlinePresenceTencent::GetGameSessionPresenceData(FMetadataPropertiesRail& InMetadata)
{
// Get the user's session, if any
FString UserSessionId;
bool bIsOnline = true;
bool bIsPlaying = false;
bool bIsJoinable = false;
bool bHasVoiceSupport = false;
FOnlineSessionTencentPtr SessionInt = StaticCastSharedPtr<FOnlineSessionTencent>(TencentSubsystem->GetSessionInterface());
if (SessionInt.IsValid())
{
bool bPublicJoinable = false;
bool bFriendJoinable = false;
bool bInviteOnly = false;
bool bAllowInvites = false;
FNamedOnlineSession* PresenceSession = SessionInt->GetPresenceSession();
if (PresenceSession != nullptr &&
PresenceSession->GetJoinability(bPublicJoinable, bFriendJoinable, bInviteOnly, bAllowInvites))
{
TSharedPtr<FOnlineSessionInfo> UserSessionInfo = PresenceSession->SessionInfo;
if (UserSessionInfo.IsValid())
{
bIsPlaying = true;
UserSessionId = UserSessionInfo->GetSessionId().ToString();
bIsJoinable = (bPublicJoinable || bFriendJoinable) && (!bInviteOnly && bAllowInvites);
}
}
}
int32 NumBits = 0;
uint32 PresenceBits = 0;
PresenceBits |= bIsOnline ? (1 << NumBits) : 0; NumBits++;
PresenceBits |= bIsPlaying ? (1 << NumBits) : 0; NumBits++;
PresenceBits |= bIsJoinable ? (1 << NumBits) : 0; NumBits++;
PresenceBits |= bHasVoiceSupport ? (1 << NumBits) : 0; NumBits++;
InMetadata.Add(RAIL_PRESENCE_PRESENCEBITS_KEY, FVariantData(PresenceBits));
InMetadata.Add(RAIL_PRESENCE_SESSION_ID_KEY, FVariantData(UserSessionId));
}
void FOnlinePresenceTencent::UpdatePresenceFromSessionData()
{
FMetadataPropertiesRail Metadata;
GetGameSessionPresenceData(Metadata);
FOnlineAsyncTaskRailSetUserMetadata* NewTask = new FOnlineAsyncTaskRailSetUserMetadata(TencentSubsystem, Metadata, FOnOnlineAsyncTaskRailSetUserMetadataComplete::CreateLambda([](const FSetUserMetadataTaskResult& Result)
{
UE_LOG_ONLINE_PRESENCE(Verbose, TEXT("UpdatePresenceFromSessionData %s"), *Result.Error.ToLogString());
}));
TencentSubsystem->QueueAsyncTask(NewTask);
}
void FOnlinePresenceTencent::QueryPresence(const FUniqueNetId& User, const FOnPresenceTaskCompleteDelegate& Delegate)
{
FUniqueNetIdRef UserPtr = User.AsShared();
TSharedRef<FOnlinePresenceTencent, ESPMode::ThreadSafe> StrongThis = AsShared();
FOnlineAsyncTaskRailGetUserPresence* NewTask = new FOnlineAsyncTaskRailGetUserPresence(TencentSubsystem, (const FUniqueNetIdRail&)User, FOnOnlineAsyncTaskRailGetUserMetadataComplete::CreateLambda([StrongThis, Delegate, UserPtr](const FGetUserMetadataTaskResult& InResult)
{
if (InResult.Error.WasSuccessful() && InResult.UserId.IsValid())
{
const TSharedRef<FOnlineUserPresenceTencent>* Cached = StrongThis->CachedPresence.Find(InResult.UserId.ToSharedRef());
if (Cached == nullptr)
{
Cached = &StrongThis->CachedPresence.Add(InResult.UserId.ToSharedRef(), MakeShared<FOnlineUserPresenceTencent>());
}
const FVariantData* PresenceBitsData = InResult.Metadata.Find(RAIL_PRESENCE_PRESENCEBITS_KEY);
if (PresenceBitsData && PresenceBitsData->GetType() == EOnlineKeyValuePairDataType::UInt32)
{
uint32 PresenceBits;
PresenceBitsData->GetValue(PresenceBits);
int32 NumBits = 0;
(*Cached)->bIsOnline = !!(PresenceBits & (1 << NumBits)); NumBits++;
(*Cached)->bIsPlaying = !!(PresenceBits & (1 << NumBits)); NumBits++;
(*Cached)->bIsJoinable = !!(PresenceBits & (1 << NumBits)); NumBits++;
(*Cached)->bHasVoiceSupport = !!(PresenceBits & (1 << NumBits)); NumBits++;
}
else
{
(*Cached)->bIsOnline = false;
(*Cached)->bIsPlaying = false;
(*Cached)->bIsJoinable = false;
(*Cached)->bHasVoiceSupport = false;
}
const FVariantData* PresenceSessionIdData = InResult.Metadata.Find(RAIL_PRESENCE_SESSION_ID_KEY);
if (PresenceSessionIdData && PresenceSessionIdData->GetType() == EOnlineKeyValuePairDataType::String)
{
FString SessionIdStr;
PresenceSessionIdData->GetValue(SessionIdStr);
if (!SessionIdStr.IsEmpty())
{
FOnlineSessionTencentPtr SessionInt = StaticCastSharedPtr<FOnlineSessionTencent>(StrongThis->TencentSubsystem->GetSessionInterface());
if (SessionInt.IsValid())
{
(*Cached)->SessionId = SessionInt->CreateSessionIdFromString(SessionIdStr);
}
}
}
else
{
(*Cached)->SessionId = FUniqueNetIdRail::EmptyId();
}
const FVariantData* AppIdData = InResult.Metadata.Find(RAIL_PRESENCE_APPID_KEY);
if (AppIdData && AppIdData->GetType() == EOnlineKeyValuePairDataType::String)
{
(*Cached)->bIsPlayingThisGame = (StrongThis->TencentSubsystem->GetAppId() == AppIdData->ToString());
}
else
{
(*Cached)->bIsPlayingThisGame = false;
}
const FVariantData* StatusData = InResult.Metadata.Find(RAIL_PRESENCE_STATUS_KEY);
if (StatusData && StatusData->GetType() == EOnlineKeyValuePairDataType::String)
{
StatusData->GetValue((*Cached)->Status.StatusStr);
}
else
{
(*Cached)->Status.StatusStr.Empty();
}
EOnlinePresenceState::Type OnlineState = EOnlinePresenceState::Offline;
if (const EOnlinePresenceState::Type* UserOnlineState = StrongThis->UserOnlineStatus.Find(InResult.UserId.ToSharedRef()))
{
// Override the presence data with latest state from Rail status
OnlineState = *UserOnlineState;
(*Cached)->bIsOnline = (OnlineState != EOnlinePresenceState::Offline);
}
else if (PresenceBitsData)
{
// No cached data but presence key data
OnlineState = (*Cached)->bIsOnline ? EOnlinePresenceState::Online : EOnlinePresenceState::Offline;
}
else
{
// Neither pieces of information has to mean the user is offline
(*Cached)->bIsOnline = false;
}
(*Cached)->Status.State = OnlineState;
(*Cached)->Status.Properties = InResult.Metadata;
}
Delegate.ExecuteIfBound(*UserPtr, InResult.Error.WasSuccessful());
}));
TencentSubsystem->QueueAsyncTask(NewTask);
}
EOnlineCachedResult::Type FOnlinePresenceTencent::GetCachedPresence(const FUniqueNetId& User, TSharedPtr<FOnlineUserPresence>& OutPresence)
{
EOnlineCachedResult::Type Result = EOnlineCachedResult::NotFound;
const TSharedRef<FOnlineUserPresenceTencent>* Found = CachedPresence.Find(User.AsShared());
if (Found != nullptr)
{
Result = EOnlineCachedResult::Success;
OutPresence = *Found;
}
return Result;
}
EOnlineCachedResult::Type FOnlinePresenceTencent::GetCachedPresenceForApp(const FUniqueNetId& LocalUserId, const FUniqueNetId& User, const FString& AppId, TSharedPtr<FOnlineUserPresence>& OutPresence)
{
/** NYI */
return EOnlineCachedResult::Type::NotFound;
}
#endif // WITH_TENCENTSDK
#endif // WITH_TENCENT_RAIL_SDK