#include "UnrealProject.h" #include "SessionManager.h" #include "DefaultGameInstance.h" #include "MatchMaking.h" #if PLATFORM_SPECIFIC_WIN == 0 // Steam includes + disable warnings for unsafe functions #pragma warning(push) #pragma warning(disable:4996) #include "steam/steam_api.h" #include "steam/isteamutils.h" #include "steam/isteamfriends.h" #pragma warning(pop) #endif // For avatar to UTexture2D #include "ImageUtils.h" #include #define POST_DESTROY_CREATE 1 #define POST_DESTROY_JOIN 2 #define POST_DESTROY_FIND 3 namespace { uint64 NetIDToInt(const FUniqueNetId& id) { std::wstring str = id.ToString().GetCharArray().GetData(); uint64 asInt = 0; str >> asInt; return asInt; } } static UTexture2D* defaultAvatar; USessionManager::USessionManager() { onCreateSessionCompleteDelegate = FOnCreateSessionCompleteDelegate::CreateUObject(this, &USessionManager::m_OnCreateSessionComplete); onDestroySessionCompleteDelegate = FOnDestroySessionCompleteDelegate::CreateUObject(this, &USessionManager::m_OnDestroySessionComplete); onStartSessionCompleteDelegate = FOnStartSessionCompleteDelegate::CreateUObject(this, &USessionManager::m_OnStartOnlineGameComplete); onFindSessionsCompleteDelegate = FOnFindSessionsCompleteDelegate::CreateUObject(this, &USessionManager::m_OnFindSessionsComplete); onJoinSessionCompleteDelegate = FOnJoinSessionCompleteDelegate::CreateUObject(this, &USessionManager::m_OnJoinSessionComplete); onPingSearchResultsCompleteDelegate = FOnPingSearchResultsCompleteDelegate::CreateUObject(this, &USessionManager::m_OnPingSearchResultsComplete); // Default avatar in case some doesn't have an avatar or the online subsystem does not provide one defaultAvatar = ConstructorHelpers::FObjectFinder(TEXT("/Game/Assets/GUI/564e176e1a279230")).Object; m_postDestroyAction = 0; sessionSearch = MakeShareable(new FOnlineSessionSearch()); sessionSearch->bIsLanQuery = false; sessionSearch->MaxSearchResults = 100; sessionSearch->PingBucketSize = 50; sessionSearch->QuerySettings.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals); // Cached online settings m_onlineSystem = IOnlineSubsystem::Get(); check(m_onlineSystem); m_session = m_onlineSystem->GetSessionInterface(); check(m_session.IsValid()); GPRINT("Session manager created with subsystem: " + m_onlineSystem->GetInstanceName()); uint32 sessionCount = m_session->GetNumSessions(); GPRINT(sessionCount + " initial sessions found."); // Set default session settings sessionSettings.bIsLANMatch = false; sessionSettings.bUsesPresence = true; sessionSettings.NumPublicConnections = 4; sessionSettings.NumPrivateConnections = 0; sessionSettings.bAllowInvites = true; sessionSettings.bAllowJoinInProgress = true; sessionSettings.bShouldAdvertise = true; sessionSettings.bAllowJoinViaPresence = true; sessionSettings.bAllowJoinViaPresenceFriendsOnly = false; sessionSettings.Set(SETTING_MAPNAME, FString("No Map"), EOnlineDataAdvertisementType::ViaOnlineServiceAndPing); // Register ping callback onPingSearchResultsCompleteDelegateHandle = m_session->AddOnPingSearchResultsCompleteDelegate_Handle(onPingSearchResultsCompleteDelegate); } void USessionManager::Shutdown() { m_session->ClearOnPingSearchResultsCompleteDelegate_Handle(onPingSearchResultsCompleteDelegateHandle); // Clean up sessions uint32 sessionCount = m_session->GetNumSessions(); if(sessionCount > 0) { DestroySession(); } // Close net connections CloseNetConnections(); } void USessionManager::CreateSession(bool destroyOld) { if(destroyOld && SessionExists()) { m_postDestroyAction = POST_DESTROY_CREATE; DestroySession(); return; } APlayerController* pc = m_instance->GetFirstLocalPlayerController(); if(!pc) { GERROR("There is no player controller!!"); onCreateSessionComplete.Broadcast(false); return; } // Register delegate onCreateSessionCompleteDelegateHandle = m_session->AddOnCreateSessionCompleteDelegate_Handle(onCreateSessionCompleteDelegate); const FUniqueNetId& netID = *pc->PlayerState->UniqueId; if(!m_session->CreateSession(netID, GameSessionName, sessionSettings)) { GERROR("m_session->CreateSession call failed!"); onCreateSessionComplete.Broadcast(false); m_session->ClearOnCreateSessionCompleteDelegate_Handle(onCreateSessionCompleteDelegateHandle); } } void USessionManager::FindSessions(bool destroyOld) { APlayerController* pc = m_instance->GetFirstLocalPlayerController(); if(!pc) { GERROR("There is no player controller!!"); onFindSessionsComplete.Broadcast(false); return; } if(destroyOld && SessionExists()) { m_postDestroyAction = POST_DESTROY_FIND; DestroySession(); return; } // Register delegate onFindSessionsCompleteDelegateHandle = m_session->AddOnFindSessionsCompleteDelegate_Handle(onFindSessionsCompleteDelegate); const FUniqueNetId& netID = *pc->PlayerState->UniqueId; if(!m_session->FindSessions(netID, sessionSearch.ToSharedRef())) { GERROR("m_session->CreateSession call failed!"); onCreateSessionComplete.Broadcast(false); m_session->ClearOnCreateSessionCompleteDelegate_Handle(onCreateSessionCompleteDelegateHandle); } } void USessionManager::JoinSession(const FOnlineSessionSearchResult& searchResult, bool destroyOld) { joinSessionResult = searchResult; if(destroyOld && SessionExists()) { m_postDestroyAction = POST_DESTROY_JOIN; DestroySession(); return; } APlayerController* pc = m_instance->GetFirstLocalPlayerController(); if(!pc) { GERROR("There is no player controller!!"); onFindSessionsComplete.Broadcast(false); return; } // Register delegate onJoinSessionCompleteDelegateHandle = m_session->AddOnJoinSessionCompleteDelegate_Handle(onJoinSessionCompleteDelegate); const FUniqueNetId& netID = *pc->PlayerState->UniqueId; if(!m_session->JoinSession(netID, GameSessionName, joinSessionResult)) { GERROR("m_session->CreateSession call failed!"); onCreateSessionComplete.Broadcast(false); m_session->ClearOnJoinSessionCompleteDelegate_Handle(onJoinSessionCompleteDelegateHandle); } } void USessionManager::DestroySession() { onDestroySessionCompleteDelegateHandle = m_session->AddOnDestroySessionCompleteDelegate_Handle(onDestroySessionCompleteDelegate); if(!m_session->DestroySession(GameSessionName)) { GERROR("m_session->DestroySession call failed!"); onDestroySessionComplete.Broadcast(false); m_session->ClearOnDestroySessionCompleteDelegate_Handle(onDestroySessionCompleteDelegateHandle); } } void USessionManager::PingResult(const FOnlineSessionSearchResult& result) { if(!m_session->PingSearchResults(result)) { onPingComplete.Broadcast(false); } } bool USessionManager::SessionExists() const { return m_session->GetNumSessions() > 0; } void USessionManager::StartSession() { m_session->StartSession(GameSessionName); } void USessionManager::EndSession() { m_session->EndSession(GameSessionName); } bool USessionManager::HasNetConnection() { // Server? UWorld* world = m_instance->GetWorld(); if(world->GetNetDriver()) return true; // Client? APlayerController* pc = m_instance->GetFirstLocalPlayerController(); if(pc->GetNetConnection()) return true; return false; } void USessionManager::CloseNetConnections() { if(!m_instance) { GERROR("INSTANCE NOT SET/INVALID??"); return; } // Close the network connection UWorld* world = m_instance->GetWorld(); if(!world) { GERROR("Can't close net connections, No world set"); return; } ENetMode netMode = world->GetNetMode(); GPRINT("Leaving client's net mode = " + netMode); UNetDriver* netDriver = world->GetNetDriver(); APlayerController* pc = m_instance->GetFirstLocalPlayerController(); UNetConnection* netConnection = pc ? pc->GetNetConnection() : nullptr; if(netConnection) { check(netMode == NM_Client); // Close the client connection netConnection->Close(); } else if(netDriver) { check(netMode == NM_ListenServer); // Shutdown the net driver world->SetNetDriver(nullptr); GEngine->DestroyNamedNetDriver(world, NAME_GameNetDriver); } } void USessionManager::AcceptUserInvite(const bool bWasSuccess, const int32 ControllerId, TSharedPtr Us, const FOnlineSessionSearchResult& res) { onAcceptInvite.Broadcast(bWasSuccess, res); } FString USessionManager::GetPlayerName(const FUniqueNetId& netID) { FString ret; #if PLATFORM_SPECIFIC_WIN == 0 ISteamFriends* friends = SteamFriends(); if(friends) { FString steamIDString = netID.ToString(); CSteamID steamID((uint64)_wtoi64(steamIDString.GetCharArray().GetData())); const char* name = SteamFriends()->GetFriendPersonaName(steamID); if(!name) ret = FString(TEXT("")); else ret = FString(name); } else #endif { IOnlineIdentityPtr identity = m_onlineSystem->GetIdentityInterface(); ret = identity->GetPlayerNickname(netID); } return ret; } FString USessionManager::GetSteamID(const FUniqueNetId& netID) { FString ret; #if PLATFORM_SPECIFIC_WIN == 0 ISteamFriends* friends = SteamFriends(); if(friends) { FString steamIDString = netID.ToString(); CSteamID steamID((uint64)_wtoi64(steamIDString.GetCharArray().GetData())); return steamIDString; } #endif return ret; } UTexture2D* USessionManager::GetPlayerAvatar(const FUniqueNetId& netID) { #if PLATFORM_SPECIFIC_WIN == 0 FString idString = netID.GetHexEncodedString(); if(m_profilePictures.Contains(idString)) { UTexture2D* ret = m_profilePictures[idString]; if(ret) { return ret; } else { m_profilePictures.Remove(idString); } } FString steamIDString = netID.ToString(); CSteamID steamID((uint64)_wtoi64(steamIDString.GetCharArray().GetData())); ISteamFriends* steamFriends = SteamFriends(); if(!steamFriends) return defaultAvatar; int imgId = steamFriends->GetMediumFriendAvatar(steamID); if(imgId == -1) return defaultAvatar; uint32 w, h; if(!SteamUtils()->GetImageSize(imgId, &w, &h)) return defaultAvatar; TArray imagePixels; imagePixels.SetNumUninitialized(w * h); if(!SteamUtils()->GetImageRGBA(imgId, (uint8*)imagePixels.GetData(), w * h * 4)) return defaultAvatar; for(uint32 i = 0; i < (w*h); i++) { imagePixels[i] = FColor(imagePixels[i].B, imagePixels[i].G, imagePixels[i].R, imagePixels[i].A); } FString imageName = FString("T_Avatar_") + netID.ToString(); FCreateTexture2DParameters params; params.bDeferCompression = false; params.bSRGB = false; params.bUseAlpha = true; UTexture2D* tex = m_CreateTexture2D(w, h, imagePixels, true); //FImageUtils::CreateTexture2D(w, h, imagePixels, GetWorld(), imageName, EObjectFlags)0, params); check(tex); m_profilePictures.Add(idString, tex); return tex; #else return defaultAvatar; #endif } // Solution to ConstructTexture2D not being supported on consoles. // From: https://answers.unrealengine.com/questions/33571/fimageutilscreatetexture2d-causes-brief-hang-is-th.html UTexture2D* USessionManager::m_CreateTexture2D(const int32 srcWidth, const int32 srcHeight, const TArray& srcColor, const bool useAlpha) { UTexture2D* texture = UTexture2D::CreateTransient(srcWidth, srcHeight, PF_B8G8R8A8); uint8* MipData = static_cast(texture->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE)); // Create base mip. uint8* DestPtr = NULL; const FColor* SrcPtr = NULL; for(int32 y = 0; y < srcHeight; y++) { DestPtr = &MipData[(srcHeight - 1 - y) * srcWidth * sizeof(FColor)]; SrcPtr = const_cast(&srcColor[(srcHeight - 1 - y) * srcWidth]); for(int32 x = 0; x < srcWidth; x++) { *DestPtr++ = SrcPtr->B; *DestPtr++ = SrcPtr->G; *DestPtr++ = SrcPtr->R; if(useAlpha) { *DestPtr++ = SrcPtr->A; } else { *DestPtr++ = 0xFF; } SrcPtr++; } } // Unlock the texture texture->PlatformData->Mips[0].BulkData.Unlock(); texture->UpdateResource(); return texture; } void USessionManager::m_OnCreateSessionComplete(FName sessionName, bool successful) { GWARNING("OnCreateSessionComplete " + sessionName.ToString().GetCharArray().GetData() + ", " + successful); m_session->ClearOnCreateSessionCompleteDelegate_Handle(onCreateSessionCompleteDelegateHandle); // Start listen server UWorld* world = m_instance->GetWorld(); if(world->Listen(m_listenURL)) { GPRINT("Started listen server on " + m_listenURL.ToString()); } else { successful = false; GERROR("Failed to start listen server!"); } onCreateSessionComplete.Broadcast(successful); } void USessionManager::m_OnStartOnlineGameComplete(FName sessionName, bool successful) { GWARNING("OnStartOnlineGameComplete " + sessionName.ToString().GetCharArray().GetData() + ", " + successful); m_session->ClearOnStartSessionCompleteDelegate_Handle(onStartSessionCompleteDelegateHandle); } void USessionManager::m_OnFindSessionsComplete(bool successful) { GWARNING("OnFindSessionsComplete " + successful + " with " + sessionSearch->SearchResults.Num() + " sessions"); m_session->ClearOnFindSessionsCompleteDelegate_Handle(onFindSessionsCompleteDelegateHandle); onFindSessionsComplete.Broadcast(true); } void USessionManager::m_OnJoinSessionComplete(FName sessionName, EOnJoinSessionCompleteResult::Type result) { GWARNING("OnJoinSessionComplete " + sessionName.ToString().GetCharArray().GetData() + ", " + int32(result)); m_session->ClearOnJoinSessionCompleteDelegate_Handle(onJoinSessionCompleteDelegateHandle); if(result == EOnJoinSessionCompleteResult::Success) { if(!m_session->GetResolvedConnectString(joinSessionResult, GamePort, joinConnectionString)) result = EOnJoinSessionCompleteResult::CouldNotRetrieveAddress; else if(joinConnectionString.IsEmpty()) result = EOnJoinSessionCompleteResult::CouldNotRetrieveAddress; else GPRINT("Resolved joined session address to: " + joinConnectionString); } onJoinSessionComplete.Broadcast(result); } void USessionManager::m_OnDestroySessionComplete(FName sessionName, bool successful) { GWARNING("OnDestroySessionComplete " + sessionName.ToString().GetCharArray().GetData() + ", " + successful); m_session->ClearOnDestroySessionCompleteDelegate_Handle(onDestroySessionCompleteDelegateHandle); if(m_postDestroyAction != 0) { if(m_postDestroyAction == POST_DESTROY_CREATE) { CreateSession(false); } else if(m_postDestroyAction == POST_DESTROY_JOIN) { JoinSession(joinSessionResult, false); } else if(m_postDestroyAction == POST_DESTROY_FIND) { FindSessions(false); } m_postDestroyAction = 0; return; } onDestroySessionComplete.Broadcast(successful); } void USessionManager::m_OnPingSearchResultsComplete(bool successful) { onPingComplete.Broadcast(successful); }