haxis/Source/UnrealProject/GameState/SessionManager.cpp

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);
}