// Copyright Epic Games, Inc. All Rights Reserved. #include "OnlineAsyncTasksTencent.h" #if WITH_TENCENT_RAIL_SDK #include "Interfaces/OnlineExternalUIInterface.h" #include "Internationalization/Culture.h" #include "Internationalization/FastDecimalFormat.h" #include "OnlineIdentityTencent.h" #include "OnlineSessionTencentRail.h" #include "OnlinePresenceTencent.h" #include "OnlineSubsystemTencent.h" #include "OnlineSubsystemTencentPrivate.h" #include "MetadataKeysRail.h" #include "OnlineSubsystemSessionSettings.h" #include "OnlineFriendsTencent.h" #include "OnlinePurchaseTencent.h" #include "OnlineUserTencent.h" #include "PlayTimeLimitImpl.h" using namespace rail; namespace { /** * Converts an engine key and its data type to an appropriate Rail key * Encoded as key in the form _ so that it is known client side * * @param Key online key * @param Data data associated with the key * @param KeyStr output Rail key for use with presence * * @return true if successful, false if data type unknown/unsupported */ inline bool VariantKeyToRailKey(const FString& Key, const FVariantData& Data, FString& KeyStr) { switch (Data.GetType()) { case EOnlineKeyValuePairDataType::Int32: KeyStr = FString::Printf(TEXT("%s_i"), *Key); break; case EOnlineKeyValuePairDataType::UInt32: KeyStr = FString::Printf(TEXT("%s_u"), *Key); break; case EOnlineKeyValuePairDataType::Int64: KeyStr = FString::Printf(TEXT("%s_l"), *Key); break; case EOnlineKeyValuePairDataType::Double: KeyStr = FString::Printf(TEXT("%s_d"), *Key); break; case EOnlineKeyValuePairDataType::String: KeyStr = FString::Printf(TEXT("%s_s"), *Key); break; case EOnlineKeyValuePairDataType::Json: KeyStr = FString::Printf(TEXT("%s_j"), *Key); break; case EOnlineKeyValuePairDataType::Float: KeyStr = FString::Printf(TEXT("%s_f"), *Key); break; case EOnlineKeyValuePairDataType::Bool: KeyStr = FString::Printf(TEXT("%s_b"), *Key); break; case EOnlineKeyValuePairDataType::Empty: case EOnlineKeyValuePairDataType::Blob: default: return false; } KeyStr.ToLowerInline(); return true; } /** * Converts a Rail key back to the variant data type it was originally * * @param RailKey key that represents the keyname_type syntax * @param RailValue the value that will be converted into a variant type * @param VariantData final converted value */ inline bool RailKeyToVariantData(const ANSICHAR* RailKey, const ANSICHAR* RailValue, FString& Key, FVariantData& OutVariantData) { bool bSuccess = false; TCHAR RailKeyCopy[kRailCommonMaxKeyLength]; FCString::Strncpy(RailKeyCopy, ANSI_TO_TCHAR(RailKey), UE_ARRAY_COUNT(RailKeyCopy)); TCHAR* DataType = FCString::Strrchr(RailKeyCopy, '_'); if (DataType) { bSuccess = true; // NULL Terminate the key *DataType = '\0'; Key = FString(RailKeyCopy).ToLower(); // Advance to the data type DataType += 1; switch (DataType[0]) { case 'i': OutVariantData.SetValue((int32)0); OutVariantData.FromString(ANSI_TO_TCHAR(RailValue)); break; case 'u': OutVariantData.SetValue((uint32)0); OutVariantData.FromString(ANSI_TO_TCHAR(RailValue)); break; case 'l': OutVariantData.SetValue((uint64)0); OutVariantData.FromString(ANSI_TO_TCHAR(RailValue)); break; case 'd': OutVariantData.SetValue((double)0); OutVariantData.FromString(ANSI_TO_TCHAR(RailValue)); break; case 's': OutVariantData.SetValue(ANSI_TO_TCHAR(RailValue)); break; case 'j': OutVariantData.SetJsonValueFromString(ANSI_TO_TCHAR(RailValue)); break; case 'f': OutVariantData.SetValue((float)0); OutVariantData.FromString(ANSI_TO_TCHAR(RailValue)); break; case 'b': OutVariantData.SetValue(false); OutVariantData.FromString(ANSI_TO_TCHAR(RailValue)); break; default: bSuccess = false; break; } } if (!bSuccess) { UE_LOG_ONLINE(Warning, TEXT("Unknown or unsupported data type from Rail key data %s %s"), ANSI_TO_TCHAR(RailKey), ANSI_TO_TCHAR(RailValue)); } return bSuccess; } /** * Create a mapping of key/value pairs after retrieving data from Rail * * @param MetadataResult output from async call to Rail * @param OutMetadata converted data into key/value pairs of variant data */ inline void FillMetadataFromRailResult(const rail_event::RailFriendsGetMetadataResult* MetadataResult, FMetadataPropertiesRail& OutMetadata) { for (uint32 KeyIdx = 0; KeyIdx < MetadataResult->friend_kvs.size(); ++KeyIdx) { const rail::RailKeyValueResult& KeyValuePair = MetadataResult->friend_kvs[KeyIdx]; if (KeyValuePair.error_code == rail::RailResult::kSuccess) { FString NewKey; FVariantData NewValue; if (RailKeyToVariantData(KeyValuePair.key.c_str(), KeyValuePair.value.c_str(), NewKey, NewValue)) { OutMetadata.Add(NewKey, NewValue); } else { UE_LOG_ONLINE(Warning, TEXT("Failed to convert metadata %s %s"), ANSI_TO_TCHAR(KeyValuePair.key.c_str()), ANSI_TO_TCHAR(KeyValuePair.value.c_str())); } } else { UE_LOG_ONLINE(Warning, TEXT("FillMetadataFromRail Key %s returned Result: %d"), ANSI_TO_TCHAR(KeyValuePair.key.c_str()), static_cast(KeyValuePair.error_code)); } } } inline void FillMetadataFromRailResult(const rail::RailFriendMetadata& FriendMetadata, FMetadataPropertiesRail& OutMetadata) { if (FriendMetadata.friend_rail_id != rail::kInvalidRailId) { for (uint32 KeyIdx = 0; KeyIdx < FriendMetadata.metadatas.size(); ++KeyIdx) { FString NewKey; FVariantData NewValue; const RailKeyValue& KeyValuePair = FriendMetadata.metadatas[KeyIdx]; if (RailKeyToVariantData(KeyValuePair.key.c_str(), KeyValuePair.value.c_str(), NewKey, NewValue)) { OutMetadata.Add(NewKey, NewValue); } else { UE_LOG_ONLINE(Warning, TEXT("Failed to convert metadata %s %s"), ANSI_TO_TCHAR(KeyValuePair.key.c_str()), ANSI_TO_TCHAR(KeyValuePair.value.c_str())); } } } } /** @return true if the existing cache, and the data that is externally visible, needs to be updated */ inline bool DoesMetadataNeedUpdate(const TMap& MetadataCache, const FString& Key, const FString& NewValue) { const FString* ExistingValue = MetadataCache.Find(Key); return (!ExistingValue || (ExistingValue && !ExistingValue->Equals(NewValue, ESearchCase::CaseSensitive))); } /** * Convert a raw Rail result into something resembling an FOnlineError * * @param InResult any rail result * @param OutOnlineError the converted error result */ void ParseRailResult(rail::RailResult InResult, FOnlineError& OutOnlineError) { if (InResult != rail::kSuccess) { OutOnlineError.bSucceeded = false; OutOnlineError.ErrorCode = LexToString(InResult); OutOnlineError.ErrorRaw = FString::Printf(TEXT("0x%08x"), static_cast(InResult)); } else { OutOnlineError.bSucceeded = true; } } /** * Parse a Rail event into something resembling an FOnlineError * * @param InResult any rail event * @param OutOnlineError the converted error result */ void ParseRailResult(const rail::EventBase* InResult, FOnlineError& OutOnlineError) { RailResult Result = InResult ? InResult->get_result() : RailResult::kErrorUnknown; ParseRailResult(Result, OutOnlineError); } /** * Helper class to print a RailAssetInfo in JSON format */ struct FRailAssetInfoJson : public FJsonSerializable { FRailAssetInfoJson(rail::RailAssetInfo& InRailAssetInfo) : RailAssetInfo(InRailAssetInfo) {} /** The asset info to print in JSON format */ rail::RailAssetInfo& RailAssetInfo; // non-const because our JSON serializers are used for both reading and writing and require non-const BEGIN_JSON_SERIALIZER if (Serializer.IsSaving()) { static_assert(std::is_same_v, "Change how we format asset_id"); FString AssetId = FString::Printf(TEXT("%llu"), RailAssetInfo.asset_id); JSON_SERIALIZE("asset_id", AssetId); JSON_SERIALIZE("product_id", RailAssetInfo.product_id); FString ProductName = LexToString(RailAssetInfo.product_name); JSON_SERIALIZE("product_name", ProductName); JSON_SERIALIZE("position", RailAssetInfo.position); FString Progress = LexToString(RailAssetInfo.progress); JSON_SERIALIZE("progress", Progress); JSON_SERIALIZE("quantity", RailAssetInfo.quantity); JSON_SERIALIZE("state", RailAssetInfo.state); JSON_SERIALIZE("flag", RailAssetInfo.flag); JSON_SERIALIZE("origin", RailAssetInfo.origin); if (RailAssetInfo.expire_time != 0) { FDateTime ExpireTime(FDateTime::FromUnixTimestamp(static_cast(RailAssetInfo.expire_time))); JSON_SERIALIZE("expires", ExpireTime); } else { static FString NeverExpires(TEXT("never")); JSON_SERIALIZE("expires", NeverExpires); } } END_JSON_SERIALIZER }; /** * Get a JSON string representation of a RailAssetInfo * @param RailAssetInfo the asset info to print in JSON format * @return an FString containing the JSON string representation of the RailAssetInfo */ FString RailAssetInfoToJsonString(const rail::RailAssetInfo& RailAssetInfo) { FRailAssetInfoJson RailAssetInfoJson = {const_cast(RailAssetInfo)}; return RailAssetInfoJson.ToJson(false); } void RailAssetInfosToPurchaseReceipt(const FUniqueNetId& UserId, const rail::RailArray& AssetInfos, FPurchaseReceipt& OutPurchaseReceipt) { FPurchaseReceipt::FReceiptOfferEntry ReceiptOfferEntry(TEXT(""), TEXT(""), 1); for (uint32 AssetIndex = 0; AssetIndex < AssetInfos.size(); ++AssetIndex) { FPurchaseReceipt::FLineItemInfo LineItemInfo; const rail::RailAssetInfo& AssetInfo(AssetInfos[AssetIndex]); static_assert(std::is_same_v, "Change how we construct string from AssetInfo.product_id"); static_assert(std::is_same_v, "Change how we construct string from AssetInfo.asset_id"); LineItemInfo.ItemName = FString::Printf(TEXT("%u"), AssetInfo.product_id); LineItemInfo.UniqueId = FString::Printf(TEXT("%llu"), AssetInfo.asset_id); if (AssetInfo.state == rail::EnumRailAssetState::kRailAssetStateNormal) { // Use the user ID as the ValidationInfo; there is no better information to provide here. LineItemInfo.ValidationInfo = UserId.ToString(); } ReceiptOfferEntry.LineItems.Emplace(MoveTemp(LineItemInfo)); } OutPurchaseReceipt.AddReceiptOffer(MoveTemp(ReceiptOfferEntry)); } } void FOnlineAsyncTaskRail::Initialize() { RailSdkWrapper& RailSDK = RailSdkWrapper::Get(); if (RailSDK.IsInitialized()) { for (auto EventId : RegisteredRailEvents) { RailSDK.RailRegisterEvent(EventId, this); } } } void FOnlineAsyncTaskRail::Tick() { // Failsafe timeout to eject the task from the queue if (bCanTimeout) { if (GetElapsedTime() >= ASYNC_RAIL_TASK_TIMEOUT) { UE_LOG_ONLINE(Error, TEXT("TIMEOUT: %s"), *ToString()); bIsComplete = true; bWasSuccessful = false; } } } void FOnlineAsyncTaskRail::Finalize() { RailSdkWrapper& RailSDK = RailSdkWrapper::Get(); if (RailSDK.IsInitialized()) { for (auto EventId : RegisteredRailEvents) { RailSDK.RailUnregisterEvent(EventId, this); } } } void FOnlineAsyncTaskRail::ParseRailResult(rail::RailResult InResult, FOnlineError& OutOnlineError) const { ::ParseRailResult(InResult, OutOnlineError); } void FOnlineAsyncTaskRail::ParseRailResult(const rail::EventBase* InResult, FOnlineError& OutOnlineError) const { const bool bSameAppId = (InResult && InResult->game_id.IsValid()) ? (Subsystem->GetAppId() == LexToString(InResult->game_id.get_id())) : false; UE_CLOG_ONLINE(InResult && !bSameAppId, Warning, TEXT("RailEvent app id mismatch %llu"), InResult->game_id.get_id()); RailResult Result = InResult ? InResult->get_result() : RailResult::kErrorUnknown; ::ParseRailResult(Result, OutOnlineError); } void FOnlineAsyncEventRail::ParseRailResult(rail::RailResult InResult, FOnlineError& OutOnlineError) const { ::ParseRailResult(InResult, OutOnlineError); } void FOnlineAsyncEventRail::ParseRailResult(const rail::EventBase* InResult, FOnlineError& OutOnlineError) const { const bool bSameAppId = (InResult && InResult->game_id.IsValid()) ? (Subsystem->GetAppId() == LexToString(InResult->game_id.get_id())) : false; UE_CLOG_ONLINE(InResult && !bSameAppId, Warning, TEXT("RailEvent app id mismatch %llu"), InResult->game_id.get_id()); RailResult Result = InResult ? InResult->get_result() : RailResult::kErrorUnknown; ::ParseRailResult(Result, OutOnlineError); } void FOnlineAsyncTaskRailAcquireSessionTicket::Initialize() { if (rail::IRailPlayer* RailPlayer = RailSdkWrapper::Get().RailPlayer()) { if (RailPlayer->GetRailID() == PlayerId) { FOnlineAsyncTaskRail::Initialize(); rail::RailString RailUserData; // @todo should we put anything here? RailResult Result = RailPlayer->AsyncAcquireSessionTicket(RailUserData); if (Result != RailResult::kSuccess) { OnlineError.SetFromErrorCode(FString::Printf(TEXT("AsyncAcquireSessionTicket initial call failed with result %u"), *LexToString(Result))); } } else { OnlineError.SetFromErrorCode(FString::Printf(TEXT("RailPlayer RailID does not match our expected ID (theirs %llu, ours %llu)"), RailPlayer->GetRailID().get_id(), PlayerId.get_id())); } } else { OnlineError.SetFromErrorCode(TEXT("RailSDK is not initialized")); } if (!OnlineError.GetErrorCode().IsEmpty()) { UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncTaskRailAcquireSessionTicket::Initialize failed %s"), *OnlineError.ToLogString()); bWasSuccessful = false; bIsComplete = true; } } void FOnlineAsyncTaskRailAcquireSessionTicket::TriggerDelegates() { FOnlineAsyncTaskRail::TriggerDelegates(); CompletionDelegate.ExecuteIfBound(OnlineError, SessionTicket); } void FOnlineAsyncTaskRailAcquireSessionTicket::OnRailEvent(rail::RAIL_EVENT_ID EventId, rail::EventBase* Param) { UE_LOG_ONLINE(VeryVerbose, TEXT("FOnlineAsyncTaskRailAcquireSessionTicket::OnRailEvent")); switch (EventId) { case rail::kRailEventSessionTicketGetSessionTicket: OnRailGetSessionTicket(static_cast(Param)); break; default: break; } } void FOnlineAsyncTaskRailAcquireSessionTicket::OnRailGetSessionTicket(const rail::rail_event::AcquireSessionTicketResponse* SessionTicketResponse) { ParseRailResult(SessionTicketResponse, OnlineError); UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncTaskRailAcquireSessionTicket::OnRailGetSessionTicket SessionTicket Result: %s"), *OnlineError.ToLogString()); if (OnlineError.WasSuccessful()) { SessionTicket = LexToString(SessionTicketResponse->session_ticket.ticket); OnlineError.bSucceeded = true; } bWasSuccessful = OnlineError.WasSuccessful(); bIsComplete = true; } void FOnlineAsyncTaskRailShowFloatingWindow::Initialize() { RailResult Result = RailResult::kErrorUnknown; RailSdkWrapper& RailSDK = RailSdkWrapper::Get(); if (RailSDK.IsInitialized()) { IRailFactory* RailFactory = RailSDK.RailFactory(); if (RailFactory) { IRailFloatingWindow* FloatingWindow = RailFactory->RailFloatingWindow(); if (FloatingWindow) { // Register events before triggering code FOnlineAsyncTaskRail::Initialize(); Result = FloatingWindow->AsyncShowRailFloatingWindow(WindowType, RailString()); FloatingWindow->SetNotifyWindowEnable(rail::kRailNotifyWindowAntiAddiction, false); //prevent AntiAddiction message from Tencent to be shown. } } } if (Result != RailResult::kSuccess) { ParseRailResult(Result, TaskResult.Error); UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncShowFloatingWindow::Initialize failed %s"), *TaskResult.Error.ToLogString()); bWasSuccessful = false; bIsComplete = true; } } void FOnlineAsyncTaskRailShowFloatingWindow::Finalize() { // Remove overload if nothing to do here // Unregister events last FOnlineAsyncTaskRail::Finalize(); } void FOnlineAsyncTaskRailShowFloatingWindow::TriggerDelegates() { // Duplicated by FOnlineAsyncEventRailShowFloatingWindow which is needed for close events } void FOnlineAsyncTaskRailShowFloatingWindow::OnRailEvent(RAIL_EVENT_ID event_id, EventBase* param) { UE_LOG_ONLINE(VeryVerbose, TEXT("FOnlineAsyncTaskRailShowFloatingWindow::OnRailEvent")); switch (event_id) { case rail::kRailEventShowFloatingWindow: OnRailShowFloatingWindow(static_cast(param)); break; default: break; } } void FOnlineAsyncTaskRailShowFloatingWindow::OnRailShowFloatingWindow(rail_event::ShowFloatingWindowResult* WindowResult) { ParseRailResult(WindowResult, TaskResult.Error); UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncShowFloatingWindow::OnRailShowFloatingWindow Result: %s"), *TaskResult.Error.ToLogString()); if (WindowResult) { TaskResult.bOpened = WindowResult->is_show; } bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } bool FOnlineAsyncTaskRailSetUserMetadata::GenerateRailMetadata(FMetadataPropertiesRail& InMetadata, TMap& OutFinalData, rail::RailArray& OutRailKeyValuePairs) { if (InMetadata.Num() <= kRailCommonMaxRepeatedKeys) { // Compare outgoing data to existing data and remove values that are the same const TMap& MetadataCache = Subsystem->GetMetadataCache(); FString KeyStr; rail::RailKeyValue RailKey; for (const TPair& Setting : InMetadata) { if (VariantKeyToRailKey(Setting.Key, Setting.Value, KeyStr)) { FString SettingStr = Setting.Value.ToString(); UE_LOG_ONLINE(VeryVerbose, TEXT("SetMetadata [%s] %s"), *KeyStr, *SettingStr); if ((KeyStr.Len() <= kRailCommonMaxKeyLength) && (SettingStr.Len() <= kRailCommonMaxValueLength)) { if (SettingStr.IsEmpty()) { UE_LOG_ONLINE(Log, TEXT("Clearing metadata key %s"), *KeyStr); } if (DoesMetadataNeedUpdate(MetadataCache, KeyStr, SettingStr)) { ToRailString(KeyStr, RailKey.key); ToRailString(SettingStr, RailKey.value); UE_LOG_ONLINE(Verbose, TEXT("SetMetadata Rail [%s] %s"), ANSI_TO_TCHAR(RailKey.key.c_str()), ANSI_TO_TCHAR(RailKey.value.c_str())); OutRailKeyValuePairs.push_back(RailKey); } else { UE_LOG_ONLINE(VeryVerbose, TEXT("Metadata already set, skipping key %s"), *KeyStr); } // Always record the final data, even if we aren't going to send it to RailSDK OutFinalData.Emplace(MoveTemp(KeyStr), MoveTemp(SettingStr)); } else { UE_LOG_ONLINE(Warning, TEXT("Metadata too large %s %s"), *KeyStr, *Setting.Value.ToString()); } } else { UE_LOG_ONLINE(Warning, TEXT("Unsupported metadata %s %s of type %s"), *Setting.Key, *Setting.Value.ToString(), EOnlineKeyValuePairDataType::ToString(Setting.Value.GetType())); } } return true; } else { UE_LOG_ONLINE(Warning, TEXT("Too many keys to set metadata %d"), InMetadata.Num()); return false; } } void FOnlineAsyncTaskRailSetUserMetadata::Initialize() { UE_LOG_ONLINE(Verbose, TEXT("FOnlineAsyncTaskRailSetUserMetadata::Initialize")); rail::RailArray RailKeyValuePairs; rail::RailResult RailResult = rail::RailResult::kErrorUnknown; if (rail::IRailFriends* RailFriends = RailSdkWrapper::Get().RailFriends()) { if (GenerateRailMetadata(Metadata, TaskResult.FinalData, RailKeyValuePairs)) { RailResult = rail::RailResult::kSuccess; if (RailKeyValuePairs.size() > 0) { FOnlineAsyncTaskRail::Initialize(); UE_LOG_ONLINE(VeryVerbose, TEXT("- setting %d keys"), RailKeyValuePairs.size()); RailResult = RailFriends->AsyncSetMyMetadata(RailKeyValuePairs, RailString()); } } } if ((RailResult != rail::RailResult::kSuccess) || (RailKeyValuePairs.size() == 0)) { ParseRailResult(RailResult, TaskResult.Error); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } } void FOnlineAsyncTaskRailSetUserMetadata::OnRailEvent(RAIL_EVENT_ID event_id, EventBase* param) { UE_LOG_ONLINE(VeryVerbose, TEXT("FOnlineAsyncTaskRailSetUserMetadata::OnRailEvent")); switch (event_id) { case rail::kRailEventFriendsSetMetadataResult: OnRailEventFriendsSetMetadataResult(static_cast(param)); break; default: break; } } void FOnlineAsyncTaskRailSetUserMetadata::OnRailEventFriendsSetMetadataResult(const rail_event::RailFriendsSetMetadataResult* MetadataResult) { ParseRailResult(MetadataResult, TaskResult.Error); UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncTaskRailSetUserMetadata::OnRailEventFriendsSetMetadataResult Result: %s"), *TaskResult.Error.ToLogString()); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } void FOnlineAsyncTaskRailSetUserMetadata::Finalize() { // Add all keys set here for debugging purposes Subsystem->AddToMetadataCache(TaskResult.FinalData); // Unregister events last FOnlineAsyncTaskRail::Finalize(); } void FOnlineAsyncTaskRailSetUserMetadata::TriggerDelegates() { CompletionDelegate.ExecuteIfBound(TaskResult); } bool FOnlineAsyncTaskRailSetUserPresence::GenerateRailMetadata(FMetadataPropertiesRail& InMetadata, TMap& OutFinalData, rail::RailArray& OutRailKeyValuePairs) { bool bSuccess = false; if ((InMetadata.Num() + 1) <= kRailCommonMaxRepeatedKeys) { // Generate the expected keys if (FOnlineAsyncTaskRailSetUserMetadata::GenerateRailMetadata(InMetadata, OutFinalData, OutRailKeyValuePairs)) { // Mark all the presence keys set above for later retrieval FString KeyStr = RAIL_PRESENCE_PRESENCE_KEYS TEXT("_s"); TArray FinalKeys; OutFinalData.GenerateKeyArray(FinalKeys); FString ValueStr = FString::Join(FinalKeys, RAIL_METADATA_KEY_SEPARATOR); if ((KeyStr.Len() <= kRailCommonMaxKeyLength) && (ValueStr.Len() <= kRailCommonMaxValueLength)) { const TMap& MetadataCache = Subsystem->GetMetadataCache(); if (DoesMetadataNeedUpdate(MetadataCache, KeyStr, ValueStr)) { rail::RailKeyValue RailKey; ToRailString(KeyStr, RailKey.key); ToRailString(ValueStr, RailKey.value); OutRailKeyValuePairs.push_back(RailKey); } else { UE_LOG_ONLINE(VeryVerbose, TEXT("Metadata already set, skipping key %s"), *KeyStr); } // Always record the final data, even if we aren't going to send it to RailSDK OutFinalData.Emplace(MoveTemp(KeyStr), MoveTemp(ValueStr)); bSuccess = true; } else { UE_LOG_ONLINE(Warning, TEXT("Presence metadata too large %s %s"), *KeyStr, *ValueStr); } } } else { UE_LOG_ONLINE(Warning, TEXT("Too many keys to set presence %d"), InMetadata.Num()); } return bSuccess; } void FOnlineAsyncTaskRailSetSessionMetadata::Initialize() { UE_LOG_ONLINE(Verbose, TEXT("FOnlineAsyncTaskRailSetSessionMetadata::Initialize")); rail::RailArray RailKeyValuePairs; rail::RailResult RailResult = rail::RailResult::kErrorUnknown; IOnlineSessionPtr SessionInt = Subsystem->GetSessionInterface(); if (SessionInt.IsValid()) { FOnlineSessionTencentRailPtr TencentSessionInt = StaticCastSharedPtr(SessionInt); if (TencentSessionInt.IsValid()) { if (rail::IRailFriends* RailFriends = RailSdkWrapper::Get().RailFriends()) { if (GenerateRailMetadata(Metadata, TaskResult.FinalData, RailKeyValuePairs)) { // Clear any keys previously set by session that is no longer referenced // Set here so that any previous task executing while this is pending can be included const TArray& CurrentKeys = TencentSessionInt->GetCurrentPresenceKeys(); for (const FString& Key : CurrentKeys) { if (!TaskResult.FinalData.Contains(Key)) { rail::RailKeyValue RailKey; ToRailString(Key, RailKey.key); RailKey.value = rail::RailString(); UE_LOG_ONLINE(Verbose, TEXT("Clearing previous key %s"), ANSI_TO_TCHAR(RailKey.key.c_str())); RailKeyValuePairs.push_back(RailKey); TaskResult.FinalData.Add(Key, FString()); } } RailResult = rail::RailResult::kSuccess; if (RailKeyValuePairs.size() > 0) { FOnlineAsyncTaskRail::Initialize(); UE_LOG_ONLINE(VeryVerbose, TEXT("- setting %d keys"), RailKeyValuePairs.size()); RailResult = RailFriends->AsyncSetMyMetadata(RailKeyValuePairs, RailString()); } } } } } if ((RailResult != rail::RailResult::kSuccess) || (RailKeyValuePairs.size() == 0)) { ParseRailResult(RailResult, TaskResult.Error); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } } void FOnlineAsyncTaskRailSetSessionMetadata::Finalize() { FOnlineSessionTencentRailPtr SessionInt = StaticCastSharedPtr(Subsystem->GetSessionInterface()); if (SessionInt.IsValid()) { // Mark the newly active session keys SessionInt->CurrentSessionPresenceKeys.Empty(TaskResult.FinalData.Num()); if (TaskResult.FinalData.Num() > 0) { TaskResult.FinalData.GenerateKeyArray(SessionInt->CurrentSessionPresenceKeys); } } FOnlineAsyncTaskRailSetUserMetadata::Finalize(); } void FOnlineAsyncTaskRailSetInviteCommandline::Initialize() { UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncTaskRailSetInviteCommandline::Initialize")); rail::RailResult RailResult = rail::RailResult::kErrorUnknown; if (rail::IRailFriends* RailFriends = RailSdkWrapper::Get().RailFriends()) { UE_LOG_ONLINE(Verbose, TEXT("Invite command line: %s"), *Cmdline); rail::RailString RailCmdLine; ToRailString(Cmdline, RailCmdLine); FOnlineAsyncTaskRail::Initialize(); RailResult = RailFriends->AsyncSetInviteCommandLine(RailCmdLine, RailString()); } if (RailResult != rail::RailResult::kSuccess) { ParseRailResult(RailResult, TaskResult.Error); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } } void FOnlineAsyncTaskRailSetInviteCommandline::OnRailEvent(RAIL_EVENT_ID event_id, EventBase* param) { UE_LOG_ONLINE(VeryVerbose, TEXT("FOnlineAsyncTaskRailSetInviteCommandline::OnRailEvent")); switch (event_id) { case rail::kRailEventFriendsSetMetadataResult: OnRailEventFriendsSetMetadataResult(static_cast(param)); break; default: break; } } void FOnlineAsyncTaskRailSetInviteCommandline::OnRailEventFriendsSetMetadataResult(const rail_event::RailFriendsSetMetadataResult* MetadataResult) { ParseRailResult(MetadataResult, TaskResult.Error); UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncTaskRailSetInviteCommandline::OnRailEventFriendsSetMetadataResult Result: %s"), *TaskResult.Error.ToLogString()); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } void FOnlineAsyncTaskRailSetInviteCommandline::Finalize() { // Remove overload if nothing to do here // Unregister events last FOnlineAsyncTaskRail::Finalize(); } void FOnlineAsyncTaskRailSetInviteCommandline::TriggerDelegates() { CompletionDelegate.ExecuteIfBound(TaskResult); } FOnlineAsyncTaskRailGetUserMetadata::FOnlineAsyncTaskRailGetUserMetadata(FOnlineSubsystemTencent* InSubsystem, const FUniqueNetIdRail& InUserId, const TArray& InMetadataKeys, const FOnOnlineAsyncTaskRailGetUserMetadataComplete& InCompletionDelegate) : FOnlineAsyncTaskRail(InSubsystem, {rail::kRailEventFriendsGetMetadataResult}) , RailUserId((rail::RailID)InUserId) , MetadataKeys(InMetadataKeys) , CompletionDelegate(InCompletionDelegate) { } FOnlineAsyncTaskRailGetUserMetadata::~FOnlineAsyncTaskRailGetUserMetadata() { } void FOnlineAsyncTaskRailGetUserMetadata::Initialize() { TaskResult.UserId = FUniqueNetIdRail::Create(RailUserId); rail::RailResult Result = QueryMetadata(MetadataKeys); if ((Result != rail::RailResult::kSuccess) || (MetadataKeys.Num() == 0)) { ParseRailResult(Result, TaskResult.Error); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } } rail::RailResult FOnlineAsyncTaskRailGetUserMetadata::QueryMetadata(const TArray& InMetadata) { rail::RailResult Result = rail::RailResult::kErrorUnknown; if (rail::IRailFriends* RailFriends = RailSdkWrapper::Get().RailFriends()) { Result = rail::RailResult::kSuccess; if (InMetadata.Num() > 0) { rail::RailArray RailPresenceKeys(InMetadata.Num()); for (int32 KeyIdx = 0; KeyIdx < InMetadata.Num(); ++KeyIdx) { const FPresenceKey& KeyStr = InMetadata[KeyIdx]; rail::RailString Key; ToRailString(KeyStr, Key); RailPresenceKeys.push_back(Key); } ensure(RailPresenceKeys.size() == InMetadata.Num()); FOnlineAsyncTaskRail::Initialize(); Result = RailFriends->AsyncGetFriendMetadata(RailUserId, RailPresenceKeys, RailString()); } } return Result; } void FOnlineAsyncTaskRailGetUserMetadata::OnRailEvent(RAIL_EVENT_ID event_id, EventBase* param) { UE_LOG_ONLINE(VeryVerbose, TEXT("FOnlineAsyncTaskRailGetUserMetadata::OnRailEvent")); switch (event_id) { case rail::kRailEventFriendsGetMetadataResult: OnRailEventFriendsGetMetadataResult(static_cast(param)); break; default: break; } } void FOnlineAsyncTaskRailGetUserMetadata::OnRailEventFriendsGetMetadataResult(const rail_event::RailFriendsGetMetadataResult* MetadataResult) { ParseRailResult(MetadataResult, TaskResult.Error); UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncTaskRailGetUserMetadata::OnRailEventFriendsGetMetadataResult Result: %s"), *TaskResult.Error.ToLogString()); if (MetadataResult) { ensure((rail::RailID)(*TaskResult.UserId) == MetadataResult->friend_id); FillMetadataFromRailResult(MetadataResult, TaskResult.Metadata); } bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } void FOnlineAsyncTaskRailGetUserMetadata::Finalize() { // Remove overload if nothing to do here // Unregister events last FOnlineAsyncTaskRail::Finalize(); } void FOnlineAsyncTaskRailGetUserMetadata::TriggerDelegates() { CompletionDelegate.ExecuteIfBound(TaskResult); } FString FOnlineAsyncTaskRailGetUserMetadata::ToString() const { return FString::Printf(TEXT("FOnlineAsyncTaskRailGetUserMetadata %s bWasSuccessful: %d"), TaskResult.UserId.IsValid() ? *TaskResult.UserId->ToString() : TEXT("Invalid"), WasSuccessful()); } FOnlineAsyncTaskRailGetUserPresence::FOnlineAsyncTaskRailGetUserPresence(FOnlineSubsystemTencent* InSubsystem, const FUniqueNetIdRail& InUserId, const FOnOnlineAsyncTaskRailGetUserMetadataComplete& InCompletionDelegate) : FOnlineAsyncTaskRailGetUserMetadata(InSubsystem, InUserId, TArray(), InCompletionDelegate) , bKeysQueried(false) { } FOnlineAsyncTaskRailGetUserPresence::~FOnlineAsyncTaskRailGetUserPresence() { } void FOnlineAsyncTaskRailGetUserPresence::Initialize() { TaskResult.UserId = FUniqueNetIdRail::Create(RailUserId); TArray PresenceKey; PresenceKey.Add(RAIL_PRESENCE_PRESENCE_KEYS TEXT("_s")); rail::RailResult Result = QueryMetadata(PresenceKey); if (Result != rail::RailResult::kSuccess) { ParseRailResult(Result, TaskResult.Error); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } } FString FOnlineAsyncTaskRailGetUserPresence::ToString() const { return FString::Printf(TEXT("FOnlineAsyncTaskRailGetUserPresence %s bWasSuccessful: %d"), TaskResult.UserId.IsValid() ? *TaskResult.UserId->ToString() : TEXT("Invalid"), WasSuccessful()); } void FOnlineAsyncTaskRailGetUserPresence::OnRailEventFriendsGetMetadataResult(const rail::rail_event::RailFriendsGetMetadataResult* MetadataResult) { if (bKeysQueried) { // Retrieve the real data FOnlineAsyncTaskRailGetUserMetadata::OnRailEventFriendsGetMetadataResult(MetadataResult); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } else { bKeysQueried = true; // Retrieve the keys FMetadataPropertiesRail PresenceKey; FillMetadataFromRailResult(MetadataResult, PresenceKey); if (PresenceKey.Num() > 0) { FString PresenceKeysStr; FVariantData* PresenceData = PresenceKey.Find(RAIL_PRESENCE_PRESENCE_KEYS); if (PresenceData) { PresenceData->GetValue(PresenceKeysStr); if (!PresenceKeysStr.IsEmpty()) { TArray PresenceKeys; int32 NumKeys = PresenceKeysStr.ParseIntoArray(PresenceKeys, RAIL_METADATA_KEY_SEPARATOR); rail::RailResult Result = QueryMetadata(PresenceKeys); if ((Result != rail::RailResult::kSuccess) || (PresenceKeys.Num() == 0)) { ParseRailResult(Result, TaskResult.Error); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } else { // Mark successful so far TaskResult.Error.bSucceeded = true; } } else { // User not online or presence is empty for some reason TaskResult.Error.bSucceeded = true; bWasSuccessful = true; bIsComplete = true; } } else { // Query failure - somehow had the key but something went wrong UE_LOG_ONLINE_PRESENCE(Warning, TEXT("FOnlineAsyncTaskRailGetUserPresence failure")); TaskResult.Error.SetFromErrorCode(TEXT("railPresence.query_failure")); } } else { // User not online or not in our engine with our keys TaskResult.Error.bSucceeded = true; bWasSuccessful = true; bIsComplete = true; } if (!TaskResult.Error.WasSuccessful()) { bWasSuccessful = false; bIsComplete = true; } } } FOnlineAsyncTaskRailGetInviteCommandline::FOnlineAsyncTaskRailGetInviteCommandline(FOnlineSubsystemTencent* InSubsystem, const FUniqueNetIdRail& InUserId, const FOnOnlineAsyncTaskRailGetInviteCommandLineComplete& InCompletionDelegate) : FOnlineAsyncTaskRail(InSubsystem, {rail::kRailEventFriendsGetInviteCommandLine}) , RailUserId((rail::RailID)InUserId) , CompletionDelegate(InCompletionDelegate) { } FOnlineAsyncTaskRailGetInviteCommandline::~FOnlineAsyncTaskRailGetInviteCommandline() { } void FOnlineAsyncTaskRailGetInviteCommandline::Initialize() { rail::RailResult Result = rail::RailResult::kErrorUnknown; TaskResult.UserId = FUniqueNetIdRail::Create(RailUserId); if (rail::IRailFriends* RailFriends = RailSdkWrapper::Get().RailFriends()) { FOnlineAsyncTaskRail::Initialize(); Result = RailFriends->AsyncGetInviteCommandLine(RailUserId, RailString()); } if (Result != rail::RailResult::kSuccess) { ParseRailResult(Result, TaskResult.Error); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } } void FOnlineAsyncTaskRailGetInviteCommandline::OnRailEvent(RAIL_EVENT_ID event_id, EventBase* param) { UE_LOG_ONLINE(VeryVerbose, TEXT("FOnlineAsyncTaskRailGetInviteCommandline::OnRailEvent")); switch (event_id) { case rail::kRailEventFriendsGetInviteCommandLine: OnRailEventFriendsGetInviteCommandLine(static_cast(param)); break; default: break; } } void FOnlineAsyncTaskRailGetInviteCommandline::OnRailEventFriendsGetInviteCommandLine(const rail_event::RailFriendsGetInviteCommandLine* InviteResult) { ParseRailResult(InviteResult, TaskResult.Error); UE_LOG_ONLINE(Verbose, TEXT("FOnlineAsyncTaskRailGetInviteCommandline::OnRailEventFriendsGetInviteCommandLine Result: %s"), *TaskResult.Error.ToLogString()); if (InviteResult) { ensure((rail::RailID)(*TaskResult.UserId) == InviteResult->friend_id); TaskResult.Commandline = FString(ANSI_TO_TCHAR(InviteResult->invite_command_line.c_str())); } bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } void FOnlineAsyncTaskRailGetInviteCommandline::Finalize() { // Remove overload if nothing to do here // Unregister events last FOnlineAsyncTaskRail::Finalize(); } void FOnlineAsyncTaskRailGetInviteCommandline::TriggerDelegates() { CompletionDelegate.ExecuteIfBound(TaskResult); } FOnlineAsyncTaskRailGetUserInvite::FOnlineAsyncTaskRailGetUserInvite(FOnlineSubsystemTencent* InSubsystem, const FUniqueNetIdRail& InUserId, const FOnOnlineAsyncTaskRailGetUserInviteComplete& InCompletionDelegate) : FOnlineAsyncTaskRail(InSubsystem, {rail::kRailEventFriendsGetMetadataResult, rail::kRailEventFriendsGetInviteCommandLine}) , bMetadataReceived(false) , MetadataReceivedResult(rail::RailResult::kErrorUnknown) , bCommandLineReceived(false) , CommandLineResult(rail::RailResult::kErrorUnknown) , RailUserId((rail::RailID)InUserId) , CompletionDelegate(InCompletionDelegate) { } FOnlineAsyncTaskRailGetUserInvite::~FOnlineAsyncTaskRailGetUserInvite() { } void FOnlineAsyncTaskRailGetUserInvite::Initialize() { rail::RailResult Result = rail::RailResult::kErrorUnknown; TaskResult.UserId = FUniqueNetIdRail::Create(RailUserId); if (rail::IRailFriends* RailFriends = RailSdkWrapper::Get().RailFriends()) { FOnlineAsyncTaskRail::Initialize(); // Retrieve the command line so that we can get the metadata keys from the remote user related to the invite Result = RailFriends->AsyncGetInviteCommandLine(RailUserId, RailString()); if (Result != RailResult::kSuccess) { bCommandLineReceived = true; CommandLineResult = Result; bMetadataReceived = true; } } else { bCommandLineReceived = true; bMetadataReceived = true; } // If either call is in flight, let it finish before finalizing if (bCommandLineReceived && bMetadataReceived) { OnEventComplete(); } } void FOnlineAsyncTaskRailGetUserInvite::OnRailEvent(RAIL_EVENT_ID event_id, EventBase* param) { UE_LOG_ONLINE(VeryVerbose, TEXT("FOnlineAsyncTaskRailGetUserInvite::OnRailEvent")); switch (event_id) { case rail::kRailEventFriendsGetMetadataResult: OnRailEventFriendsGetMetadataResult(static_cast(param)); break; case rail::kRailEventFriendsGetInviteCommandLine: OnRailEventFriendsGetInviteCommandLine(static_cast(param)); break; default: break; } } void FOnlineAsyncTaskRailGetUserInvite::OnRailEventFriendsGetMetadataResult(const rail_event::RailFriendsGetMetadataResult* MetadataResult) { MetadataReceivedResult = MetadataResult ? MetadataResult->get_result() : RailResult::kErrorUnknown; UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncTaskRailGetUserInvite::OnRailEventFriendsGetMetadataResult Result: %s"), *LexToString(MetadataReceivedResult)); if (MetadataResult) { ensure((rail::RailID)(*TaskResult.UserId) == MetadataResult->friend_id); FillMetadataFromRailResult(MetadataResult, TaskResult.Metadata); } bMetadataReceived = true; OnEventComplete(); } void FOnlineAsyncTaskRailGetUserInvite::OnRailEventFriendsGetInviteCommandLine(const rail_event::RailFriendsGetInviteCommandLine* InviteResult) { CommandLineResult = InviteResult ? InviteResult->get_result() : RailResult::kErrorUnknown; UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncTaskRailGetUserInvite::OnRailEventFriendsGetInviteCommandLine Result: %s"), *LexToString(CommandLineResult)); if (InviteResult) { ensure((rail::RailID)(*TaskResult.UserId) == InviteResult->friend_id); TaskResult.Commandline = FString(ANSI_TO_TCHAR(InviteResult->invite_command_line.c_str())); } bCommandLineReceived = true; // Possibly kick off the async key retrieval RetrieveFromInviteCommandLine(TaskResult.Commandline); // Event will end if there was nothing to do above OnEventComplete(); } void FOnlineAsyncTaskRailGetUserInvite::RetrieveFromInviteCommandLine(const FString& InviteCommandline) { if (!InviteCommandline.IsEmpty()) { InviteCommandline.ParseIntoArray(MetadataKeys, RAIL_METADATA_KEY_SEPARATOR); } if (MetadataKeys.Num() > 0) { rail::RailArray RailPresenceKeys(MetadataKeys.Num()); for (int32 KeyIdx = 0; KeyIdx < MetadataKeys.Num(); ++KeyIdx) { const FPresenceKey& KeyStr = MetadataKeys[KeyIdx]; rail::RailString Key; ToRailString(KeyStr, Key); RailPresenceKeys.push_back(Key); } ensure(RailPresenceKeys.size() == MetadataKeys.Num()); if (rail::IRailFriends* RailFriends = RailSdkWrapper::Get().RailFriends()) { rail::RailResult Result = RailFriends->AsyncGetFriendMetadata(RailUserId, RailPresenceKeys, RailString()); if (Result != RailResult::kSuccess) { bMetadataReceived = true; MetadataReceivedResult = Result; } } else { bMetadataReceived = true; MetadataReceivedResult = RailResult::kErrorFriends; } } else { // No keys to retrieve, mark this as success bMetadataReceived = true; MetadataReceivedResult = RailResult::kSuccess; } } void FOnlineAsyncTaskRailGetUserInvite::OnEventComplete() { if (bCommandLineReceived && bMetadataReceived) { if ((MetadataReceivedResult == RailResult::kSuccess) && (CommandLineResult == RailResult::kSuccess)) { TaskResult.Error.bSucceeded = true; } else if ((MetadataReceivedResult != RailResult::kSuccess) && (CommandLineResult != RailResult::kSuccess)) { TaskResult.Error.SetFromErrorCode(FString::Printf(TEXT("Both async tasks failed Meta: %d CmdLine: %d"), *LexToString(MetadataReceivedResult), *LexToString(CommandLineResult))); } else if (MetadataReceivedResult != RailResult::kSuccess) { ParseRailResult(MetadataReceivedResult, TaskResult.Error); } else if (CommandLineResult != RailResult::kSuccess) { ParseRailResult(CommandLineResult, TaskResult.Error); } bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } } void FOnlineAsyncTaskRailGetUserInvite::Finalize() { // Remove overload if nothing to do here // Unregister events last FOnlineAsyncTaskRail::Finalize(); } void FOnlineAsyncTaskRailGetUserInvite::TriggerDelegates() { CompletionDelegate.ExecuteIfBound(TaskResult); } FOnlineAsyncTaskRailClearAllMetadata::FOnlineAsyncTaskRailClearAllMetadata(FOnlineSubsystemTencent* InSubsystem, const FUniqueNetIdRail& InUserId) : FOnlineAsyncTaskRail(InSubsystem, {rail::kRailEventFriendsClearMetadataResult}) , RailUserId((rail::RailID)InUserId) { } FOnlineAsyncTaskRailClearAllMetadata::~FOnlineAsyncTaskRailClearAllMetadata() { } void FOnlineAsyncTaskRailClearAllMetadata::Initialize() { rail::RailResult Result = rail::RailResult::kErrorUnknown; if (rail::IRailFriends* RailFriends = RailSdkWrapper::Get().RailFriends()) { FOnlineAsyncTaskRail::Initialize(); Result = RailFriends->AsyncClearAllMyMetadata(RailString()); } if (Result != rail::RailResult::kSuccess) { ParseRailResult(Result, TaskResult); bWasSuccessful = TaskResult.WasSuccessful(); bIsComplete = true; } } void FOnlineAsyncTaskRailClearAllMetadata::OnRailEvent(RAIL_EVENT_ID event_id, EventBase* param) { UE_LOG_ONLINE(VeryVerbose, TEXT("FOnlineAsyncTaskRailClearAllMetadata::OnRailEvent")); switch (event_id) { case rail::kRailEventFriendsClearMetadataResult: OnRailEventFriendsClearAllMetadataResult(static_cast(param)); break; default: break; } } void FOnlineAsyncTaskRailClearAllMetadata::OnRailEventFriendsClearAllMetadataResult(const rail_event::RailFriendsClearMetadataResult* MetadataResult) { ParseRailResult(MetadataResult, TaskResult); UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncTaskRailClearAllMetadata::OnRailEventFriendsClearAllMetadataResult Result: %s"), *TaskResult.ToLogString()); bWasSuccessful = TaskResult.WasSuccessful(); bIsComplete = true; } void FOnlineAsyncTaskRailClearAllMetadata::Finalize() { // Remove overload if nothing to do here // Unregister events last FOnlineAsyncTaskRail::Finalize(); } void FOnlineAsyncTaskRailClearAllMetadata::TriggerDelegates() { } FOnlineAsyncTaskRailAddFriend::FOnlineAsyncTaskRailAddFriend(FOnlineSubsystemTencent* InSubsystem, const FUniqueNetIdRail& InUserId, const FOnOnlineAsyncTaskRailAddFriendComplete& InCompletionDelegate) : FOnlineAsyncTaskRail(InSubsystem, {rail::kRailEventFriendsAddFriendResult}) , RailUserId((rail::RailID)InUserId) , CompletionDelegate(InCompletionDelegate) { } FOnlineAsyncTaskRailAddFriend::~FOnlineAsyncTaskRailAddFriend() { } void FOnlineAsyncTaskRailAddFriend::Initialize() { rail::RailResult Result = rail::RailResult::kErrorUnknown; if (rail::IRailFriends* RailFriends = RailSdkWrapper::Get().RailFriends()) { FOnlineAsyncTaskRail::Initialize(); RailFriendsAddFriendRequest AddFriendRequest; AddFriendRequest.target_rail_id = RailUserId; Result = RailFriends->AsyncAddFriend(AddFriendRequest, rail::RailString()); } if (Result != rail::RailResult::kSuccess) { ParseRailResult(Result, TaskResult); bWasSuccessful = TaskResult.WasSuccessful(); bIsComplete = true; } } void FOnlineAsyncTaskRailAddFriend::OnRailEvent(RAIL_EVENT_ID event_id, EventBase* param) { UE_LOG_ONLINE(VeryVerbose, TEXT("FOnlineAsyncTaskRailClearAllMetadata::OnRailEvent")); switch (event_id) { case rail::kRailEventFriendsAddFriendResult: { OnRailEventFriendsAddFriendResult(static_cast(param)); break; } default: break; } } void FOnlineAsyncTaskRailAddFriend::OnRailEventFriendsAddFriendResult(const rail_event::RailFriendsAddFriendResult* AddFriendResult) { ParseRailResult(AddFriendResult, TaskResult); UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncTaskRailAddFriend::OnRailEventFriendsAddFriendResult Result: %s"), *TaskResult.ToLogString()); bWasSuccessful = TaskResult.WasSuccessful(); bIsComplete = true; } void FOnlineAsyncTaskRailAddFriend::TriggerDelegates() { CompletionDelegate.ExecuteIfBound(TaskResult); } FThreadSafeCounter FOnlineAsyncTaskRailGetUsersInfo::RequestIdCounter; FOnlineAsyncTaskRailGetUsersInfo::FOnlineAsyncTaskRailGetUsersInfo(FOnlineSubsystemTencent* InSubsystem, const TArray& InUserIds, const FOnOnlineAsyncTaskRailGetUsersInfoComplete& InCompletionDelegate) : FOnlineAsyncTaskRail(InSubsystem, {rail::kRailEventUsersGetUsersInfo}) , UserIds(InUserIds) , CompletionDelegate(InCompletionDelegate) { RequestId = RequestIdCounter.Increment(); } void FOnlineAsyncTaskRailGetUsersInfo::Initialize() { // TODO: Break into batches of kRailCommonMaxRepeatedKeys rail::RailResult Result = rail::RailResult::kErrorUnknown; if (rail::IRailUsersHelper* const UsersHelper = RailSdkWrapper::Get().RailUsersHelper()) { FOnlineAsyncTaskRail::Initialize(); rail::RailArray RailIds; for (const FUniqueNetIdRef& UserId : UserIds) { RailIds.push_back(StaticCastSharedRef(UserId)->RailID); } const FString UserData = FString::FromInt(RequestId); rail::RailString RailUserData; ToRailString(UserData, RailUserData); Result = UsersHelper->AsyncGetUsersInfo(RailIds, RailUserData); } if (Result != rail::RailResult::kSuccess) { ParseRailResult(Result, TaskResult.Error); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } } void FOnlineAsyncTaskRailGetUsersInfo::OnRailEvent(RAIL_EVENT_ID event_id, EventBase* param) { const int32 ParamRequestId = FCStringAnsi::Atoi(param->user_data.c_str()); const bool bMatchesRequestId = (ParamRequestId == RequestId); if (bMatchesRequestId) { UE_LOG_ONLINE(VeryVerbose, TEXT("FOnlineAsyncTaskRailGetUsersInfo::OnRailEvent")); switch (event_id) { case rail::kRailEventUsersGetUsersInfo: OnRailEventUsersGetUsersInfo(static_cast(param)); break; default: break; } } else { const bool bParamRequestIdIsEmpty = (param->user_data.size() == 0); if (bParamRequestIdIsEmpty) { UE_LOG_ONLINE(Warning, TEXT("FOnlineAsyncTaskRailGetUsersInfo::OnRailEvent: Received event %d with empty user_data"), static_cast(event_id)); } } } void FOnlineAsyncTaskRailGetUsersInfo::OnRailEventUsersGetUsersInfo(const rail_event::RailUsersInfoData* UsersInfoData) { ParseRailResult(UsersInfoData, TaskResult.Error); UE_LOG_ONLINE(Log, TEXT("OnRailEventUsersGetUsersInfo Result: %s"), *TaskResult.Error.ToLogString()); if (UsersInfoData) { TaskResult.UserInfos.Reserve(UsersInfoData->user_info_list.size()); for (uint32 Index = 0; Index < UsersInfoData->user_info_list.size(); ++Index) { const rail::PlayerPersonalInfo& RailInfo = UsersInfoData->user_info_list[Index]; if (RailInfo.error_code == rail::kSuccess) { FUniqueNetIdRailRef RailId = FUniqueNetIdRail::Create(RailInfo.rail_id); FOnlineUserInfoTencentRef OnlineUser = MakeShared(RailId); OnlineUser->SetUserAttribute(USER_ATTR_DISPLAYNAME, LexToString(RailInfo.rail_name)); TaskResult.UserInfos.Emplace(OnlineUser); } else { UE_LOG_ONLINE(Log, TEXT("OnRailEventUsersGetUsersInfo: User %llu has error %s"), RailInfo.rail_id.get_id(), *LexToString(RailInfo.error_code)); } } } bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } void FOnlineAsyncTaskRailGetUsersInfo::TriggerDelegates() { CompletionDelegate.ExecuteIfBound(TaskResult); } void FOnlineAsyncEventRailSystemStateChanged::Finalize() { } void FOnlineAsyncEventRailSystemStateChanged::TriggerDelegates() { switch (State) { case rail::RailSystemState::kSystemStateUnknown: case rail::RailSystemState::kSystemStatePlatformOffline: case rail::RailSystemState::kSystemStatePlatformExit: UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncEventRailSystemStateChanged: Calling identity logout on system state %s"), *LexToString(State)); { IOnlineIdentityPtr Identity = Subsystem->GetIdentityInterface(); if (ensure(Identity.IsValid())) { static const constexpr int32 LocalUserNum = 0; Identity->Logout(LocalUserNum); } } break; case rail::RailSystemState::kSystemStatePlatformOnline: UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncEventRailSystemStateChanged: Platform went online, but not doing anything, waiting for game to call identity login")); break; case rail::RailSystemState::kSystemStatePlayerOwnershipExpired: case rail::RailSystemState::kSystemStatePlayerOwnershipActivated: UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncEventRailSystemStateChanged: Unsure of what to do for player ownership state %s"), *LexToString(State)); break; case rail::RailSystemState::kSystemStateGameExitByAntiAddiction: UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncEventRailSystemStateChanged: Exiting the game by AntiAddiction on state %s"), *LexToString(State)); FPlayTimeLimitImpl::Get().GameExitByRequest(); break; default: UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncEventRailSystemStateChanged: Unexpected state %s"), *LexToString(State)); break; } } FOnlineAsyncEventRailShowFloatingWindow::FOnlineAsyncEventRailShowFloatingWindow(FOnlineSubsystemTencent* InSubsystem, const rail::rail_event::ShowFloatingWindowResult* InParams) : FOnlineAsyncEventRail(InSubsystem) { ParseRailResult(InParams, TaskResult.Error); TaskResult.bOpened = InParams ? InParams->is_show : false; } void FOnlineAsyncEventRailShowFloatingWindow::Finalize() { // Remove overload if nothing to do here FOnlineAsyncEventRail::Finalize(); } void FOnlineAsyncEventRailShowFloatingWindow::TriggerDelegates() { FOnlineAsyncEventRail::TriggerDelegates(); IOnlineExternalUIPtr ExternalUIInterface = Subsystem->GetExternalUIInterface(); ExternalUIInterface->TriggerOnExternalUIChangeDelegates(TaskResult.bOpened); } FOnlineAsyncEventRailShowFloatingNotifyWindow::FOnlineAsyncEventRailShowFloatingNotifyWindow(FOnlineSubsystemTencent* InSubsystem, const rail::rail_event::ShowNotifyWindow* InParams) : FOnlineAsyncEventRail(InSubsystem) , bShowAntiAddictionMessage(false) { if (InParams && InParams->window_type == rail::kRailNotifyWindowAntiAddiction) { // Currently only sending Event notification for Anti-Addiction messages bShowAntiAddictionMessage = true; Payload.FromJson(LexToString(InParams->json_content)); } } void FOnlineAsyncEventRailShowFloatingNotifyWindow::TriggerDelegates() { if (bShowAntiAddictionMessage) { FOnlineSubsystemTencent* OSS = StaticCast(IOnlineSubsystem::Get(TENCENT_SUBSYSTEM)); if (OSS) { OSS->TriggerOnAASDialogDelegates(Payload.DialogTitle, Payload.DialogText, Payload.ButtonText); } } } void FOnlineAsyncEventRailInviteSent::Finalize() { // nothing to do locally now that the invite has been sent } void FOnlineAsyncEventRailInviteSent::TriggerDelegates() { // nothing to do locally now that the invite has been sent } void FOnlineAsyncEventRailInviteSentEx::Finalize() { // nothing to do locally now that the invite has been sent } void FOnlineAsyncEventRailInviteSentEx::TriggerDelegates() { // nothing to do locally now that the invite has been sent } void FOnlineAsyncEventRailInviteResponse::Finalize() { if (Response == rail::kRailInviteResponseTypeAccepted) { IOnlineIdentityPtr IdentityInt = Subsystem->GetIdentityInterface(); if (IdentityInt.IsValid()) { FUniqueNetIdRailRef UserId = FUniqueNetIdRail::Create(InviteeId); if (IdentityInt->GetLoginStatus(*UserId) == ELoginStatus::LoggedIn) { FUniqueNetIdRailRef RemoteUserId = FUniqueNetIdRail::Create(InviterId); FOnlineSessionTencentRailPtr SessionInt = StaticCastSharedPtr(Subsystem->GetSessionInterface()); if (SessionInt.IsValid()) { SessionInt->QueryAcceptedUserInvitation(UserId, RemoteUserId); } else { UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncEventRailInviteResponse: No session interface")); } } else { UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncEventRailInviteResponse: No user logged in")); } } else { UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncEventRailInviteResponse: No identity interface")); } } else { UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncEventRailInviteResponse: Invite not accepted %d"), static_cast(Response)); } } void FOnlineAsyncEventRailInviteResponse::TriggerDelegates() { // noop, handled by QueryAcceptedUserInvitation() // ignoring LoggedOut state above or possibility that OSS interfaces are missing } void FOnlineAsyncEventRailJoinGameByUser::Finalize() { IOnlineIdentityPtr IdentityInt = Subsystem->GetIdentityInterface(); if (IdentityInt.IsValid()) { FUniqueNetIdRailPtr UserId = StaticCastSharedPtr(GetFirstSignedInUser(IdentityInt)); if (UserId.IsValid() && IdentityInt->GetLoginStatus(*UserId) == ELoginStatus::LoggedIn) { FUniqueNetIdRailRef RemoteUserId = FUniqueNetIdRail::Create(UserToJoin); FOnlineSessionTencentRailPtr SessionInt = StaticCastSharedPtr(Subsystem->GetSessionInterface()); if (SessionInt.IsValid()) { SessionInt->QueryAcceptedUserInvitation(UserId.ToSharedRef(), RemoteUserId); } else { UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncEventRailInviteResponse: No session interface")); } } else { UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncEventRailJoinGameByUser: No user logged in")); } } else { UE_LOG_ONLINE(Log, TEXT("FOnlineAsyncEventRailInviteResponse: No identity interface")); } } void FOnlineAsyncEventRailJoinGameByUser::TriggerDelegates() { // noop, handled by QueryAcceptedUserInvitation() // ignoring LoggedOut state above or possibility that OSS interfaces are missing } void FOnlineAsyncEventRailJoinGameResult::Finalize() { // nothing to do locally now that we know the remote user will be joining our session } void FOnlineAsyncEventRailJoinGameResult::TriggerDelegates() { // nothing to do locally now that we know the remote user will be joining our session } void FOnlineAsyncEventRailFriendsListChanged::TriggerDelegates() { IOnlineIdentityPtr IdentityInt = Subsystem->GetIdentityInterface(); FOnlineFriendsTencentPtr FriendsInt = StaticCastSharedPtr(Subsystem->GetFriendsInterface()); if (IdentityInt.IsValid() && FriendsInt.IsValid()) { FUniqueNetIdRailPtr UniqueNetId = StaticCastSharedPtr(IdentityInt->CreateUniquePlayerId(reinterpret_cast(&UserId), sizeof(UserId))); if (UniqueNetId.IsValid()) { ELoginStatus::Type LoginStatus = IdentityInt->GetLoginStatus(*UniqueNetId); if (LoginStatus == ELoginStatus::LoggedIn) { FriendsInt->OnRailFriendsListChanged(*UniqueNetId); } else { UE_LOG_ONLINE(Verbose, TEXT("FOnlineAsyncEventRailFriendsListChanged: User %s is not logged in, status is %s"), *UniqueNetId->ToDebugString(), ELoginStatus::ToString(LoginStatus)); } } else { UE_LOG_ONLINE(Warning, TEXT("FOnlineAsyncEventRailFriendsListChanged: Failed to create a unique player id for rail id %llu"), UserId.get_id()); } } else { UE_LOG_ONLINE(Verbose, TEXT("FOnlineAsyncEventRailFriendsListChanged: Missing identity or friends interface")); } } void FOnlineAsyncEventRailFriendsOnlineStateChanged::Finalize() { if (OnlineState.friend_rail_id != rail::kInvalidRailId) { IOnlinePresencePtr PresenceInt = Subsystem->GetPresenceInterface(); if (PresenceInt.IsValid()) { FOnlinePresenceTencentPtr TencentPresenceInt = StaticCastSharedPtr(PresenceInt); FUniqueNetIdRef UserId = FUniqueNetIdRail::Create(OnlineState.friend_rail_id); TencentPresenceInt->SetUserOnlineState(*UserId, RailOnlineStateToOnlinePresence(OnlineState.friend_online_state)); } } } FOnlineAsyncEventRailFriendsMetadataChanged::FOnlineAsyncEventRailFriendsMetadataChanged(FOnlineSubsystemTencent* InSubsystem, const rail::rail_event::RailFriendsMetadataChanged* InParams) : FOnlineAsyncEventRail(InSubsystem) { if (InParams) { for (uint32 Index = 0; Index < InParams->friends_changed_metadata.size(); ++Index) { const rail::RailFriendMetadata& ChangedFriend = InParams->friends_changed_metadata[Index]; if (ChangedFriend.friend_rail_id != rail::kInvalidRailId && ChangedFriend.metadatas.size() > 0) { FMetadataPropertiesRail Metadata; FillMetadataFromRailResult(ChangedFriend, Metadata); if (Metadata.Num() > 0) { ChangedMetadata.Emplace(FUniqueNetIdRail::Create(ChangedFriend.friend_rail_id), Metadata); } } } } } void FOnlineAsyncEventRailFriendsMetadataChanged::TriggerDelegates() { for (const TPair& FriendMetadata : ChangedMetadata) { UE_LOG_ONLINE(Verbose, TEXT("FriendsMetadataChanged: User: %s Count: %d"), *FriendMetadata.Key->ToDebugString(), FriendMetadata.Value.Num()); if (UE_LOG_ACTIVE(LogOnline, VeryVerbose)) { for (const TPair& Changes : FriendMetadata.Value) { UE_LOG_ONLINE(VeryVerbose, TEXT(" - %s : %s"), *Changes.Key, *Changes.Value.ToString()); } } Subsystem->TriggerOnFriendMetadataChangedDelegates(*FriendMetadata.Key, FriendMetadata.Value); } } void FOnlineAsyncTaskRailQueryFriendsPresence::Initialize() { IOnlinePresencePtr PresenceInt = Subsystem->GetPresenceInterface(); if (PresenceInt.IsValid()) { IOnlinePresence::FOnPresenceTaskCompleteDelegate UserCompletionDelegate; UserCompletionDelegate.BindLambda([this](const FUniqueNetId& User, bool bInWasSuccessful) { TaskResult.QueryCount++; TaskResult.SuccessCount += (bInWasSuccessful ? 1 : 0); if (TaskResult.QueryCount >= FriendsList.Num()) { TaskResult.Error.bSucceeded = (TaskResult.SuccessCount == FriendsList.Num()); if (!TaskResult.Error.WasSuccessful()) { TaskResult.Error.SetFromErrorCode(TEXT("railPresence.query_friends_failure")); } bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } }); for (const FUniqueNetIdRef& Friend : FriendsList) { PresenceInt->QueryPresence(*Friend, UserCompletionDelegate); } } } void FOnlineAsyncTaskRailQueryFriendsPresence::Finalize() { } void FOnlineAsyncTaskRailQueryFriendsPresence::TriggerDelegates() { CompletionDelegate.ExecuteIfBound(TaskResult); } void FOnlineAsyncTaskRailSendInvite::Initialize() { rail::RailResult Result = rail::RailResult::kErrorUnknown; if (rail::IRailUsersHelper* const UsersHelper = RailSdkWrapper::Get().RailUsersHelper()) { FOnlineAsyncTaskRail::Initialize(); RailInviteOptions Options; Options.invite_type = EnumRailUsersInviteType::kRailUsersInviteTypeGame; rail::RailArray RailIds; for (const FUniqueNetIdRef& UserId : UserIds) { RailIds.push_back(StaticCastSharedRef(UserId)->RailID); } rail::RailString RailInviteStr; ToRailString(TaskResult.InviteStr, RailInviteStr); Result = UsersHelper->AsyncInviteUsers(RailInviteStr, RailIds, Options, RailString()); } if (Result != rail::RailResult::kSuccess) { ParseRailResult(Result, TaskResult.Error); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } } void FOnlineAsyncTaskRailSendInvite::Finalize() { FOnlineAsyncTaskRail::Finalize(); } void FOnlineAsyncTaskRailSendInvite::TriggerDelegates() { CompletionDelegate.ExecuteIfBound(TaskResult); } void FOnlineAsyncTaskRailSendInvite::OnRailEvent(rail::RAIL_EVENT_ID event_id, rail::EventBase* param) { UE_LOG_ONLINE(VeryVerbose, TEXT("FOnlineAsyncTaskRailSendInvite::OnRailEvent")); switch (event_id) { case rail::kRailEventUsersInviteUsersResult: OnRailEventFriendsSendInviteResult(static_cast(param)); break; default: break; } } void FOnlineAsyncTaskRailSendInvite::OnRailEventFriendsSendInviteResult(const rail::rail_event::RailUsersInviteUsersResult* InviteResult) { ParseRailResult(InviteResult, TaskResult.Error); UE_LOG_ONLINE(Verbose, TEXT("FOnlineAsyncTaskRailSendInvite::OnRailEventFriendsSendInviteResult Result: %s"), *TaskResult.Error.ToLogString()); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } FOnlineAsyncTaskRailGetInviteDetails::FOnlineAsyncTaskRailGetInviteDetails(FOnlineSubsystemTencent* InSubsystem, const FUniqueNetIdRail& InUserId, const FOnOnlineAsyncTaskRailGetInviteDetailsComplete& InCompletionDelegate) : FOnlineAsyncTaskRail(InSubsystem, {rail::kRailEventUsersGetInviteDetailResult}) , RailUserId((rail::RailID)InUserId) , CompletionDelegate(InCompletionDelegate) { } void FOnlineAsyncTaskRailGetInviteDetails::Initialize() { rail::RailResult Result = rail::RailResult::kErrorUnknown; if (rail::IRailUsersHelper* const UsersHelper = RailSdkWrapper::Get().RailUsersHelper()) { FOnlineAsyncTaskRail::Initialize(); Result = UsersHelper->AsyncGetInviteDetail(RailUserId, rail::kRailUsersInviteTypeGame, RailString()); } if (Result != rail::RailResult::kSuccess) { ParseRailResult(Result, TaskResult.Error); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } } void FOnlineAsyncTaskRailGetInviteDetails::Finalize() { FOnlineAsyncTaskRail::Finalize(); } void FOnlineAsyncTaskRailGetInviteDetails::TriggerDelegates() { CompletionDelegate.ExecuteIfBound(TaskResult); } void FOnlineAsyncTaskRailGetInviteDetails::OnRailEvent(rail::RAIL_EVENT_ID event_id, rail::EventBase* param) { UE_LOG_ONLINE(VeryVerbose, TEXT("FOnlineAsyncTaskRailGetInviteDetails::OnRailEvent")); switch (event_id) { case rail::kRailEventUsersGetInviteDetailResult: OnRailEventFriendsGetInviteDetailsResult(static_cast(param)); break; default: break; } } void FOnlineAsyncTaskRailGetInviteDetails::OnRailEventFriendsGetInviteDetailsResult(const rail::rail_event::RailUsersGetInviteDetailResult* InviteDetails) { ParseRailResult(InviteDetails, TaskResult.Error); UE_LOG_ONLINE(Log, TEXT("OnRailEventFriendsGetInviteDetailsResult Result: %s"), *TaskResult.Error.ToLogString()); if (InviteDetails) { if (RailUserId == InviteDetails->inviter_id && InviteDetails->invite_type == EnumRailUsersInviteType::kRailUsersInviteTypeGame) { TaskResult.InviteString = LexToString(InviteDetails->command_line); } else { const FUniqueNetIdRailRef RailInviterId = FUniqueNetIdRail::Create(InviteDetails->inviter_id); UE_LOG_ONLINE(Verbose, TEXT("Unsupported or invalid invite details %s %s"), *RailInviterId->ToDebugString(), *LexToString(InviteDetails->invite_type)); } } bWasSuccessful = TaskResult.Error.WasSuccessful() && !TaskResult.InviteString.IsEmpty(); bIsComplete = true; } FOnlineAsyncTaskRailQueryFriendPlayedGamesInfo::FOnlineAsyncTaskRailQueryFriendPlayedGamesInfo(FOnlineSubsystemTencent* InSubsystem, const FUniqueNetIdRail& InUserId, const FOnOnlineAsyncTaskRailQueryFriendPlayedGamesComplete& InCompletionDelegate) : FOnlineAsyncTaskRail(InSubsystem, {rail::kRailEventFriendsGetFriendPlayedGamesResult}) , CompletionDelegate(InCompletionDelegate) { TaskResult.FriendId = StaticCastSharedRef(InUserId.AsShared()); } void FOnlineAsyncTaskRailQueryFriendPlayedGamesInfo::Initialize() { rail::RailResult Result = rail::RailResult::kErrorUnknown; if (rail::IRailFriends* const RailFriends = RailSdkWrapper::Get().RailFriends()) { FOnlineAsyncTaskRail::Initialize(); Result = RailFriends->AsyncQueryFriendPlayedGamesInfo((rail::RailID)(*TaskResult.FriendId), RailString()); } if (Result != rail::RailResult::kSuccess) { ParseRailResult(Result, TaskResult.Error); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } } void FOnlineAsyncTaskRailQueryFriendPlayedGamesInfo::Finalize() { FOnlineAsyncTaskRail::Finalize(); } void FOnlineAsyncTaskRailQueryFriendPlayedGamesInfo::TriggerDelegates() { CompletionDelegate.ExecuteIfBound(TaskResult); } void FOnlineAsyncTaskRailQueryFriendPlayedGamesInfo::OnRailEvent(rail::RAIL_EVENT_ID event_id, rail::EventBase* param) { UE_LOG_ONLINE(VeryVerbose, TEXT("FOnlineAsyncTaskRailQueryFriendPlayedGamesInfo::OnRailEvent")); switch (event_id) { case rail::kRailEventFriendsGetFriendPlayedGamesResult: OnRailEventQueryFriendPlayedGamesResult(static_cast(param)); break; default: break; } } void FOnlineAsyncTaskRailQueryFriendPlayedGamesInfo::OnRailEventQueryFriendPlayedGamesResult(const rail::rail_event::RailFriendsQueryFriendPlayedGamesResult* PlayedGamesDetail) { ParseRailResult(PlayedGamesDetail, TaskResult.Error); UE_LOG_ONLINE(Log, TEXT("OnRailEventQueryFriendPlayedGamesResult Result: %s"), *TaskResult.Error.ToLogString()); if (PlayedGamesDetail) { for (uint32 Index = 0; Index < PlayedGamesDetail->friend_played_games_info_list.size(); ++Index) { const RailFriendPlayedGameInfo& PlayedGameInfo = PlayedGamesDetail->friend_played_games_info_list[Index]; if ((rail::RailID)(*TaskResult.FriendId) == PlayedGameInfo.friend_id) { FQueryFriendPlayedGamesTaskResult::FRailGamePlayedInfo GameInfo; GameInfo.bInServer = PlayedGameInfo.in_game_server; GameInfo.bInRoom = PlayedGameInfo.in_room; // NYI ServerId and RoomId support GameInfo.GameId = PlayedGameInfo.game_id; GameInfo.GamePlayState = PlayedGameInfo.friend_played_game_play_state; TaskResult.GameInfos.Emplace(GameInfo); } else { UE_LOG_ONLINE(Verbose, TEXT("OnRailEventQueryFriendPlayedGamesResult friend id doesn't match")); } } } bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } FOnlineAsyncTaskRailReportPlayedWithUsers::FOnlineAsyncTaskRailReportPlayedWithUsers(FOnlineSubsystemTencent* InSubsystem, const TArray& InUsersReported, const FOnOnlineAsyncTaskRailReportPlayedWithUsersComplete& InCompletionDelegate) : FOnlineAsyncTaskRail(InSubsystem, {rail::kRailEventFriendsReportPlayedWithUserListResult}) , CompletionDelegate(InCompletionDelegate) { TaskResult.UsersReported.Append(InUsersReported); } void FOnlineAsyncTaskRailReportPlayedWithUsers::Initialize() { rail::RailResult Result = rail::RailResult::kErrorUnknown; if (TaskResult.UsersReported.Num() > 0) { if (TaskResult.UsersReported.Num() < kRailMaxPlayedWithUsersCount) { if (rail::IRailFriends* const RailFriends = RailSdkWrapper::Get().RailFriends()) { FOnlineAsyncTaskRail::Initialize(); rail::RailArray RailPlayedWith(TaskResult.UsersReported.Num()); for (const FReportPlayedWithUser& UserReport : TaskResult.UsersReported) { rail::RailUserPlayedWith PlayedWith; PlayedWith.rail_id = StaticCastSharedRef(UserReport.UserId)->RailID; if (UserReport.PresenceStr.Len() < kRailMaxPlayedWithUserContentLen) { ToRailString(UserReport.PresenceStr, PlayedWith.user_rich_content); } RailPlayedWith.push_back(PlayedWith); } Result = RailFriends->AsyncReportPlayedWithUserList(RailPlayedWith, RailString()); } } else { UE_LOG_ONLINE(Warning, TEXT("Too many users to report to RailSDK %d"), TaskResult.UsersReported.Num()); } } else { UE_LOG_ONLINE(Warning, TEXT("Report users called with empty user list")); } if (Result != rail::RailResult::kSuccess) { ParseRailResult(Result, TaskResult.Error); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } } void FOnlineAsyncTaskRailReportPlayedWithUsers::Finalize() { FOnlineAsyncTaskRail::Finalize(); } void FOnlineAsyncTaskRailReportPlayedWithUsers::TriggerDelegates() { CompletionDelegate.ExecuteIfBound(TaskResult); } void FOnlineAsyncTaskRailReportPlayedWithUsers::OnRailEvent(rail::RAIL_EVENT_ID event_id, rail::EventBase* param) { UE_LOG_ONLINE(VeryVerbose, TEXT("FOnlineAsyncTaskRailReportPlayedWithUsers::OnRailEvent")); switch (event_id) { case rail::kRailEventFriendsReportPlayedWithUserListResult: OnRailEventReportPlayedWithUsers(static_cast(param)); break; default: break; } } void FOnlineAsyncTaskRailReportPlayedWithUsers::OnRailEventReportPlayedWithUsers(const rail::rail_event::RailFriendsReportPlayedWithUserListResult* PlayedWithUsersResult) { ParseRailResult(PlayedWithUsersResult, TaskResult.Error); UE_LOG_ONLINE(Log, TEXT("OnRailEventReportPlayedWithUsers Result: %s"), *TaskResult.Error.ToLogString()); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } FOnlineAsyncTaskRailQueryPlayedWithFriendsList::FOnlineAsyncTaskRailQueryPlayedWithFriendsList(FOnlineSubsystemTencent* InSubsystem, const FOnOnlineAsyncTaskRailQueryPlayedWithFriendsListComplete& InCompletionDelegate) : FOnlineAsyncTaskRail(InSubsystem, {rail::kRailEventFriendsQueryPlayedWithFriendsListResult}) , CompletionDelegate(InCompletionDelegate) { } void FOnlineAsyncTaskRailQueryPlayedWithFriendsList::Initialize() { rail::RailResult Result = rail::RailResult::kErrorUnknown; if (rail::IRailFriends* const RailFriends = RailSdkWrapper::Get().RailFriends()) { FOnlineAsyncTaskRail::Initialize(); Result = RailFriends->AsyncQueryPlayedWithFriendsList(RailString()); } if (Result != rail::RailResult::kSuccess) { ParseRailResult(Result, TaskResult.Error); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } } void FOnlineAsyncTaskRailQueryPlayedWithFriendsList::Finalize() { FOnlineAsyncTaskRail::Finalize(); } void FOnlineAsyncTaskRailQueryPlayedWithFriendsList::TriggerDelegates() { CompletionDelegate.ExecuteIfBound(TaskResult); } void FOnlineAsyncTaskRailQueryPlayedWithFriendsList::OnRailEvent(rail::RAIL_EVENT_ID event_id, rail::EventBase* param) { UE_LOG_ONLINE(VeryVerbose, TEXT("FOnlineAsyncTaskRailQueryPlayedWithFriendsList::OnRailEvent")); switch (event_id) { case rail::kRailEventFriendsQueryPlayedWithFriendsListResult: OnRailEventPlayedWithFriendsList(static_cast(param)); break; default: break; } } void FOnlineAsyncTaskRailQueryPlayedWithFriendsList::OnRailEventPlayedWithFriendsList(const rail::rail_event::RailFriendsQueryPlayedWithFriendsListResult* PlayedFriendsList) { ParseRailResult(PlayedFriendsList, TaskResult.Error); UE_LOG_ONLINE(Log, TEXT("OnRailEventPlayedWithFriendsList Result: %s"), *TaskResult.Error.ToLogString()); if (PlayedFriendsList) { for (uint32 RailIdx = 0; RailIdx < PlayedFriendsList->played_with_friends_list.size(); ++RailIdx) { if (PlayedFriendsList->played_with_friends_list[RailIdx] != kInvalidRailId) { TaskResult.UsersPlayedWith.Add(FUniqueNetIdRail::Create(PlayedFriendsList->played_with_friends_list[RailIdx])); } } } bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } FOnlineAsyncTaskRailQueryPlayedWithFriendsTime::FOnlineAsyncTaskRailQueryPlayedWithFriendsTime(FOnlineSubsystemTencent* InSubsystem, const TArray& InUserIds, const FOnOnlineAsyncTaskRailQueryPlayedWithFriendsTimeComplete& InCompletionDelegate) : FOnlineAsyncTaskRail(InSubsystem, {rail::kRailEventFriendsQueryPlayedWithFriendsTimeResult}) , CompletionDelegate(InCompletionDelegate) { TaskResult.UserIds.Append(InUserIds); } void FOnlineAsyncTaskRailQueryPlayedWithFriendsTime::Initialize() { rail::RailResult Result = rail::RailResult::kErrorUnknown; if (rail::IRailFriends* const RailFriends = RailSdkWrapper::Get().RailFriends()) { FOnlineAsyncTaskRail::Initialize(); rail::RailArray RailIds; for (const FUniqueNetIdRef& UserId : TaskResult.UserIds) { RailIds.push_back(StaticCastSharedRef(UserId)->RailID); } Result = RailFriends->AsyncQueryPlayedWithFriendsTime(RailIds, RailString()); } if (Result != rail::RailResult::kSuccess) { ParseRailResult(Result, TaskResult.Error); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } } void FOnlineAsyncTaskRailQueryPlayedWithFriendsTime::Finalize() { FOnlineAsyncTaskRail::Finalize(); } void FOnlineAsyncTaskRailQueryPlayedWithFriendsTime::TriggerDelegates() { CompletionDelegate.ExecuteIfBound(TaskResult); } void FOnlineAsyncTaskRailQueryPlayedWithFriendsTime::OnRailEvent(rail::RAIL_EVENT_ID event_id, rail::EventBase* param) { UE_LOG_ONLINE(VeryVerbose, TEXT("FOnlineAsyncTaskRailQueryPlayedWithFriendsTime::OnRailEvent")); switch (event_id) { case rail::kRailEventFriendsQueryPlayedWithFriendsTimeResult: OnRailEventPlayedWithFriendsTime(static_cast(param)); break; default: break; } } void FOnlineAsyncTaskRailQueryPlayedWithFriendsTime::OnRailEventPlayedWithFriendsTime(const rail::rail_event::RailFriendsQueryPlayedWithFriendsTimeResult* PlayedTimeResult) { ParseRailResult(PlayedTimeResult, TaskResult.Error); UE_LOG_ONLINE(Log, TEXT("OnRailEventPlayedWithFriendsTime Result: %s"), *TaskResult.Error.ToLogString()); if (PlayedTimeResult) { for (uint32 RailIdx = 0; RailIdx < PlayedTimeResult->played_with_friends_time_list.size(); ++RailIdx) { const RailPlayedWithFriendsTimeItem& FriendItem = PlayedTimeResult->played_with_friends_time_list[RailIdx]; if (FriendItem.rail_id != kInvalidRailId) { TaskResult.LastPlayedWithUsers.Emplace(FUniqueNetIdRail::Create(FriendItem.rail_id), FDateTime::FromUnixTimestamp(FriendItem.play_time)); } } } bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } void FOnlineAsyncTaskRailRequestAllAssets::Initialize() { rail::RailResult Result = rail::RailResult::kErrorUnknown; if (rail::IRailAssets* const RailAssets = RailSdkWrapper::Get().RailAssets()) { FOnlineAsyncTaskRail::Initialize(); rail::RailString UserData; Result = RailAssets->AsyncRequestAllAssets(UserData); } else { UE_LOG_ONLINE(Warning, TEXT("FOnlineAsyncTaskRailRequestAllAssets: Could not find IRailAssets")); } if (Result != rail::RailResult::kSuccess) { ParseRailResult(Result, TaskResult.Error); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } } void FOnlineAsyncTaskRailRequestAllAssets::TriggerDelegates() { FOnlineAsyncTaskRail::TriggerDelegates(); CompletionDelegate.ExecuteIfBound(TaskResult); } void FOnlineAsyncTaskRailRequestAllAssets::OnRailEvent(rail::RAIL_EVENT_ID EventId, rail::EventBase* Param) { UE_LOG_ONLINE(Verbose, TEXT("FOnlineAsyncTaskRailRequestAllAssets::OnRailEvent")); switch (EventId) { case rail::kRailEventAssetsRequestAllAssetsFinished: OnRailRequestAllAssetsFinished(static_cast(Param)); break; } } void FOnlineAsyncTaskRailRequestAllAssets::OnRailRequestAllAssetsFinished(const rail::rail_event::RequestAllAssetsFinished* AssetsResponse) { ParseRailResult(AssetsResponse, TaskResult.Error); if (TaskResult.Error.WasSuccessful()) { const rail::RailArray& AssetInfos(AssetsResponse->assetinfo_list); UE_LOG_ONLINE(VeryVerbose, TEXT("RequestAllAssets: %u assets"), AssetInfos.size()); for (uint32 AssetIndex = 0; AssetIndex < AssetInfos.size(); ++AssetIndex) { const rail::RailAssetInfo& AssetInfo(AssetInfos[AssetIndex]); UE_LOG_ONLINE_PURCHASE(VeryVerbose, TEXT(" %u: %s"), AssetIndex, *RailAssetInfoToJsonString(AssetInfo)); } RailAssetInfosToPurchaseReceipt(*UserId, AssetInfos, TaskResult.PurchaseReceipt); } bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } FOnlineAsyncEventRailAssetsChanged::FOnlineAsyncEventRailAssetsChanged(FOnlineSubsystemTencent* InSubsystem, const rail::rail_event::RailAssetsChanged* InParams) : FOnlineAsyncEventRail(InSubsystem) { // RailAssetsChanged has no data } void FOnlineAsyncEventRailAssetsChanged::TriggerDelegates() { IOnlineIdentityPtr IdentityInt = Subsystem->GetIdentityInterface(); FOnlinePurchaseTencentPtr PurchaseInt = StaticCastSharedPtr(Subsystem->GetPurchaseInterface()); if (LIKELY(IdentityInt.IsValid() && PurchaseInt.IsValid())) { const FUniqueNetIdPtr UniqueNetId = GetFirstSignedInUser(IdentityInt); if (UniqueNetId.IsValid()) { const ELoginStatus::Type LoginStatus = IdentityInt->GetLoginStatus(*UniqueNetId); if (LoginStatus == ELoginStatus::LoggedIn) { PurchaseInt->OnRailAssetsChanged(*UniqueNetId); } else { UE_LOG_ONLINE(Verbose, TEXT("FOnlineAsyncEventRailAssetsChanged: User %s is not logged in, status is %s"), *UniqueNetId->ToDebugString(), ELoginStatus::ToString(LoginStatus)); } } else { UE_LOG_ONLINE(Warning, TEXT("FOnlineAsyncEventRailAssetsChanged: No signed in user")); } } else { UE_LOG_ONLINE(Verbose, TEXT("FOnlineAsyncEventRailAssetsChanged: Missing identity or purchase interface")); } } void FOnlineAsyncTaskRailRequestAllPurchasableProducts::Initialize() { UE_LOG_ONLINE(Verbose, TEXT("FOnlineAsyncTaskRailSetSessionMetadata::Initialize")); rail::IRailInGamePurchase* const RailInGamePurchase = RailSdkWrapper::Get().RailInGamePurchase(); if (RailInGamePurchase) { FOnlineAsyncTaskRail::Initialize(); rail::RailString UserData; const rail::RailResult RailResult = RailInGamePurchase->AsyncRequestAllPurchasableProducts(UserData); ParseRailResult(RailResult, TaskResult.Error); } else { TaskResult.Error.SetFromErrorCode(TEXT("missing RailInGamePurchase")); } if (!TaskResult.Error.WasSuccessful()) { UE_LOG_ONLINE(Warning, TEXT("FOnlineAsyncTaskRailRequestAllPurchasableProducts::Initialize failed with result=[%s]"), *TaskResult.Error.ToLogString()); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } } void FOnlineAsyncTaskRailRequestAllPurchasableProducts::TriggerDelegates() { CompletionDelegate.ExecuteIfBound(TaskResult); } void FOnlineAsyncTaskRailRequestAllPurchasableProducts::OnRailEvent(rail::RAIL_EVENT_ID EventId, rail::EventBase* Param) { UE_LOG_ONLINE(Verbose, TEXT("FOnlineAsyncTaskRailRequestAllPurchasableProducts::OnRailEvent")); switch (EventId) { case rail::kRailEventInGamePurchaseAllPurchasableProductsInfoReceived: OnRailRequestAllPurchasableProductsResponse(static_cast(Param)); break; default: break; } } static int32 AdjustPriceForLocalization(const FString& InCurrencyCode, const double InPrice) { // Convert the backend stated price into its base units FInternationalization& I18N = FInternationalization::Get(); const FCulture& Culture = *I18N.GetCurrentCulture(); const FDecimalNumberFormattingRules& FormattingRules = Culture.GetCurrencyFormattingRules(InCurrencyCode); const FNumberFormattingOptions& FormattingOptions = FormattingRules.CultureDefaultFormattingOptions; double FormattedPrice = InPrice * static_cast(FMath::Pow(10.0f, FormattingOptions.MaximumFractionalDigits)); return FMath::TruncToInt(FormattedPrice + 0.5); } void FOnlineAsyncTaskRailRequestAllPurchasableProducts::OnRailRequestAllPurchasableProductsResponse(rail::rail_event::RailInGamePurchaseRequestAllPurchasableProductsResponse* Response) { ParseRailResult(Response, TaskResult.Error); if (TaskResult.Error.WasSuccessful()) { const FDateTime UtcNow = FDateTime::UtcNow(); TaskResult.Offers.Reserve(Response->purchasable_products.size()); for (int32 Index = 0; Index < Response->purchasable_products.size(); ++Index) { const rail::RailPurchaseProductInfo& ProductInfo(Response->purchasable_products[Index]); FOnlineStoreOfferRef StoreOffer = MakeShared(); static_assert(std::is_same_v, "Change how we construct string from ProductInfo.product_id"); StoreOffer->OfferId = FString::Printf(TEXT("%u"), ProductInfo.product_id); StoreOffer->Title = FText::FromString(LexToString(ProductInfo.name)); StoreOffer->Description = FText::FromString(LexToString(ProductInfo.description)); StoreOffer->LongDescription = StoreOffer->Description; StoreOffer->CurrencyCode = LexToString(ProductInfo.currency_type); StoreOffer->RegularPrice = AdjustPriceForLocalization(StoreOffer->CurrencyCode, ProductInfo.original_price); switch (ProductInfo.discount.type) { case kPurchaseProductDiscountTypeInvalid: break; case kPurchaseProductDiscountTypeNone: StoreOffer->NumericPrice = AdjustPriceForLocalization(StoreOffer->CurrencyCode, ProductInfo.original_price); break; case kPurchaseProductDiscountTypePermanent: StoreOffer->NumericPrice = AdjustPriceForLocalization(StoreOffer->CurrencyCode, ProductInfo.discount.discount_price); break; case kPurchaseProductDiscountTypeTimed: { const FDateTime UtcStartTime = FDateTime::FromUnixTimestamp(ProductInfo.discount.start_time); const FDateTime UtcEndTime = FDateTime::FromUnixTimestamp(ProductInfo.discount.end_time); if (UtcNow >= UtcStartTime && UtcNow <= UtcEndTime) { StoreOffer->NumericPrice = AdjustPriceForLocalization(StoreOffer->CurrencyCode, ProductInfo.discount.discount_price); } else { StoreOffer->NumericPrice = AdjustPriceForLocalization(StoreOffer->CurrencyCode, ProductInfo.original_price); } if (UtcNow < UtcStartTime) { StoreOffer->ExpirationDate = UtcStartTime; } else if (UtcNow < UtcEndTime) { StoreOffer->ExpirationDate = UtcEndTime; } break; } } TaskResult.Offers.Emplace(StoreOffer); } } bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } void FOnlineAsyncTaskRailPurchaseProducts::Initialize() { UE_LOG_ONLINE(Verbose, TEXT("FOnlineAsyncTaskRailPurchaseProducts::Initialize")); rail::IRailInGamePurchase* const RailInGamePurchase = RailSdkWrapper::Get().RailInGamePurchase(); if (RailInGamePurchase) { FOnlineAsyncTaskRail::Initialize(); rail::RailArray RailProductItems; for (const FPurchaseCheckoutRequest::FPurchaseOfferEntry& OfferEntry : CheckoutRequest.PurchaseOffers) { rail::RailProductItem ProductItem; ProductItem.product_id = FCString::Atoi(*OfferEntry.OfferId); ProductItem.quantity = OfferEntry.Quantity; RailProductItems.push_back(ProductItem); } rail::RailString UserData; const rail::RailResult RailResult = RailInGamePurchase->AsyncPurchaseProductsToAssets(RailProductItems, UserData); if (RailResult == rail::kSuccess) { TaskResult.Error.bSucceeded = true; } else { HandlePurchaseProductsResult(RailResult); } } else { TaskResult.Error.SetFromErrorCode(TEXT("com.epicgames.purchase.missing_ingamepurchase")); } if (!TaskResult.Error.bSucceeded) { UE_LOG_ONLINE(Warning, TEXT("FOnlineAsyncTaskRailPurchaseProducts::Initialize failed with result=[%s]"), *TaskResult.Error.ToLogString()); bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } } void FOnlineAsyncTaskRailPurchaseProducts::TriggerDelegates() { CompletionDelegate.ExecuteIfBound(TaskResult); } void FOnlineAsyncTaskRailPurchaseProducts::OnRailEvent(rail::RAIL_EVENT_ID EventId, rail::EventBase* Param) { UE_LOG_ONLINE(VeryVerbose, TEXT("FOnlineAsyncTaskRailPurchaseProducts::OnRailEvent")); switch (EventId) { case rail::kRailEventInGamePurchasePurchaseProductsToAssetsResult: OnRailPurchaseProductsToAssetsResult(static_cast(Param)); break; default: break; } } void FOnlineAsyncTaskRailPurchaseProducts::HandlePurchaseProductsResult(const TOptional RailResult) { TaskResult.Error.bSucceeded = false; if (RailResult.IsSet()) { TaskResult.Error.ErrorRaw = FString::Printf(TEXT("0x%08x"), static_cast(RailResult.GetValue())); switch (RailResult.GetValue()) { case rail::kErrorInGamePurchasePaymentCancle: // User cancelled the payment TaskResult.Error.SetFromErrorCode(TEXT("com.epicgames.catalog_helper.user_cancelled")); break; case rail::kErrorInGamePurchaseProductInfoExpired: // Intentional fall-through case rail::kErrorInGamePurchaseProductIsNotExist: // Offers are out of date TaskResult.Error.SetFromErrorCode(TEXT("com.epicgames.purchase.offers_out_of_date")); break; case rail::kErrorInGamePurchasePaymentFailed: // Intentional fall-through case rail::kErrorInGamePurchaseAcquireSessionTicketFailed: // Intentional fall-through case rail::kErrorInGamePurchaseParseWebContentFaild: // Intentional fall-through case rail::kErrorInGamePurchaseOrderIDIsNotExist: // Intentional fall-through case rail::kErrorInGamePurchasePreparePaymentRequestTimeout: // Intentional fall-through case rail::kErrorInGamePurchaseCreateOrderFailed: // Intentional fall-through case rail::kErrorInGamePurchaseQueryOrderFailed: // Intentional fall-through case rail::kErrorInGamePurchaseFinishOrderFailed: // Intentional fall-through case rail::kErrorInGamePurchaseCreatePaymentBrowserFailed: // Intentional fall-through default: TaskResult.Error.SetFromErrorCode(TEXT("com.epicgames.purchase.failure")); break; } } else { TaskResult.Error.SetFromErrorCode(TEXT("com.epicgames.purchase.noresponse")); } } void FOnlineAsyncTaskRailPurchaseProducts::OnRailPurchaseProductsToAssetsResult(rail::rail_event::RailInGamePurchasePurchaseProductsToAssetsResponse* Response) { // Intentionally does not call ParseRailResult if (Response && Response->result == rail::kSuccess) { TaskResult.PurchaseReceipt = MakeShared(); const rail::RailArray& AssetInfos(Response->delivered_assets); RailAssetInfosToPurchaseReceipt(*UserId, AssetInfos, *TaskResult.PurchaseReceipt); FString OrderId = LexToString(Response->order_id); FString DeliveredAssetsLogString; for (uint32 AssetIndex = 0; AssetIndex < AssetInfos.size(); ++AssetIndex) { if (AssetIndex != 0) { DeliveredAssetsLogString += TEXT(","); } DeliveredAssetsLogString += RailAssetInfoToJsonString(AssetInfos[AssetIndex]); } UE_LOG_ONLINE_PURCHASE(Log, TEXT("PurchaseProductsToAssets: Successful with OrderId=[%s] DeliveredAssets=[%s]"), *OrderId, *DeliveredAssetsLogString); } else { HandlePurchaseProductsResult(Response ? Response->result : TOptional()); } bWasSuccessful = TaskResult.Error.WasSuccessful(); bIsComplete = true; } #endif // WITH_TENCENT_RAIL_SDK