489 lines
15 KiB
C++
489 lines
15 KiB
C++
#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 <string>
|
|
|
|
#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<UTexture2D>(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<const FUniqueNetId> 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("<unknown>"));
|
|
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<FColor> 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<FColor>& srcColor, const bool useAlpha)
|
|
{
|
|
UTexture2D* texture = UTexture2D::CreateTransient(srcWidth, srcHeight, PF_B8G8R8A8);
|
|
|
|
uint8* MipData = static_cast<uint8*>(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<FColor*>(&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);
|
|
}
|