Skip to content

Instantly share code, notes, and snippets.

@iDevelopThings
Created November 28, 2025 22:43
Show Gist options
  • Select an option

  • Save iDevelopThings/5a9ea3be1f777130cb0fae5fcffd192a to your computer and use it in GitHub Desktop.

Select an option

Save iDevelopThings/5a9ea3be1f777130cb0fae5fcffd192a to your computer and use it in GitHub Desktop.
#include "SteamManagerSubsystem.h"
#include "OnlineSessionSettings.h"
#include "OnlineSubsystem.h"
#include "OnlineSubsystemUtils.h"
#include "Interfaces/OnlineSessionInterface.h"
#include "Kismet/GameplayStatics.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(SteamManagerSubsystem)
#define LOG_RESULT(FormatStr, ...) \
{ \
FString Str = FString::Printf(TEXT(FormatStr), ##__VA_ARGS__); \
UKismetSystemLibrary::PrintString(this, Str, true, true, FColor::Cyan, 30.f); \
}
#define LOG_ERROR(FormatStr, ...) \
{ \
FString Str = FString::Printf(TEXT(FormatStr), ##__VA_ARGS__); \
UKismetSystemLibrary::PrintString(this, Str, true, true, FColor::Red, 30.f); \
}
#define RETURN_IF_NOT_INITIALIZED() \
if (!OnlineSubsystem || !Identity.IsValid() || !Sessions.IsValid()) { \
SteamMPLOG(Error, "Subsystem not initialized"); \
return; \
}
USteamManagerSubsystem* USteamManagerSubsystem::Get(const UObject* WorldContextObject) {
if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull)) {
return UGameInstance::GetSubsystem<USteamManagerSubsystem>(
World->GetGameInstance()
);
}
return nullptr;
}
void USteamManagerSubsystem::Initialize(FSubsystemCollectionBase& Collection) {
Super::Initialize(Collection);
// Get the session interface early
if (IOnlineSubsystem* OSS = Online::GetSubsystem(GetWorld())) {
OnlineSubsystem = OSS;
SessionInterface = OSS->GetSessionInterface();
}
// Bind delegates (we keep handlers simple)
if (SessionInterface.IsValid()) {
OnCreateCompleteHandle = SessionInterface->AddOnCreateSessionCompleteDelegate_Handle(
FOnCreateSessionCompleteDelegate::CreateUObject(this, &USteamManagerSubsystem::OnCreateSessionComplete)
);
OnDestroyCompleteHandle = SessionInterface->AddOnDestroySessionCompleteDelegate_Handle(
FOnDestroySessionCompleteDelegate::CreateUObject(this, &USteamManagerSubsystem::OnDestroySessionComplete)
);
SessionUserInviteAcceptedHandle = SessionInterface->AddOnSessionUserInviteAcceptedDelegate_Handle(
FOnSessionUserInviteAcceptedDelegate::CreateUObject(this, &USteamManagerSubsystem::OnInviteAccepted)
);
JoinSessionCompleteHandle = SessionInterface->AddOnJoinSessionCompleteDelegate_Handle(
FOnJoinSessionCompleteDelegate::CreateUObject(this, &USteamManagerSubsystem::OnJoinSessionComplete)
);
}
// Optionally: create directly now or after a specific map loads.
// We'll hook map load: when the persistent map is loaded, create a session.
//FCoreUObjectDelegates::PostLoadMapWithWorld.AddWeakLambda(this, [this](UWorld* World) {
// CreateSteamLobby();
//});
// Also check command line for join request
// JoinLobbyFromCommandLine();
}
void USteamManagerSubsystem::Deinitialize() {
if (SessionInterface.IsValid()) {
SessionInterface->ClearOnCreateSessionCompleteDelegate_Handle(OnCreateCompleteHandle);
SessionInterface->ClearOnDestroySessionCompleteDelegate_Handle(OnDestroyCompleteHandle);
SessionInterface->ClearOnSessionUserInviteAcceptedDelegate_Handle(SessionUserInviteAcceptedHandle);
SessionInterface->ClearOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteHandle);
SessionInterface.Reset();
}
Super::Deinitialize();
}
void USteamManagerSubsystem::CreateSteamLobby() {
if (!SessionInterface.IsValid()) {
UE_LOG(LogTemp, Warning, TEXT("No session interface (OSS not available)."));
return;
}
// If a session already exists, optionally destroy and recreate (hacky)
if (SessionInterface->GetNamedSession(NAME_GameSession)) {
SessionInterface->DestroySession(NAME_GameSession);
// OnDestroySessionComplete will be called; you could re-create there.
return;
}
SessionSettings = MakeShared<FOnlineSessionSettings>();
SessionSettings->bIsLANMatch = false; // Steam - not LAN
SessionSettings->NumPublicConnections = 4;
SessionSettings->bShouldAdvertise = true;
SessionSettings->bUsesPresence = true; // important for Steam presence join
SessionSettings->bAllowJoinInProgress = true;
SessionSettings->bAllowJoinViaPresence = true; // allow friends to join via overlay
SessionSettings->bAllowJoinViaPresenceFriendsOnly = false;
SessionSettings->bAllowInvites = true;
SessionSettings->bIsDedicated = false;
SessionSettings->bUseLobbiesIfAvailable = true;
SessionSettings->BuildUniqueId = 1;
SessionSettings->Set(FName("SERVER_NAME"), FString("Hacky Steam Lobby"), EOnlineDataAdvertisementType::ViaOnlineService);
// Any custom metadata you want to set
SessionSettings->Set(FName("MAP"), FString("/Game/Variant_Shooter/Lvl_Shooter_Prototyping"), EOnlineDataAdvertisementType::ViaOnlineService);
// Create the session (local player 0)
if (!SessionInterface->CreateSession(0, NAME_GameSession, *SessionSettings)) {
UE_LOG(LogTemp, Warning, TEXT("CreateSession call failed immediately."));
} else {
UE_LOG(LogTemp, Log, TEXT("CreateSession requested..."));
}
}
void USteamManagerSubsystem::LeaveLobby() {
if (!SessionInterface.IsValid()) {
UE_LOG(LogTemp, Warning, TEXT("No session interface (OSS not available)."));
return;
}
if (SessionInterface->GetNamedSession(NAME_GameSession)) {
SessionInterface->DestroySession(NAME_GameSession);
}
}
void USteamManagerSubsystem::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful) {
UE_LOG(LogTemp, Log, TEXT("OnCreateSessionComplete: %s - success=%d"), *SessionName.ToString(), bWasSuccessful);
if (!bWasSuccessful)
return;
FNamedOnlineSession* Session = SessionInterface->GetNamedSession(SessionName);
// Optionally travel to gameplay map (listen server) so others can connect:
FString MapName;
Session->SessionSettings.Get(FName("MAP"), MapName);
LOG_RESULT("Session map name: %s", *MapName)
UGameplayStatics::OpenLevel(GetWorld(), FName("/Game/Variant_Shooter/Lvl_Lobby"), true, "listen");
// GetWorld()->ServerTravel("/Game/Maps/MyTestMap?listen");
}
void USteamManagerSubsystem::OnDestroySessionComplete(FName SessionName, bool bWasSuccessful) {
UE_LOG(LogTemp, Log, TEXT("OnDestroySessionComplete: %s - success=%d"), *SessionName.ToString(), bWasSuccessful);
if (bIsDestroyingExistingSessionForCreation) {
bIsDestroyingExistingSessionForCreation = false;
CreateSteamLobby();
}
if (bIsDestroyingExistingSessionForJoin) {
bIsDestroyingExistingSessionForJoin = false;
JoinSession(PendingInviteRequest);
}
}
void USteamManagerSubsystem::OnInviteAccepted(bool bWasSuccessful, int ControllerId, TSharedPtr<const FUniqueNetId> UserId, const FOnlineSessionSearchResult& InviteResult) {
if (SessionInterface->GetNamedSession(NAME_GameSession)) {
LOG_ERROR("Session already exists, destroying it before we leave...");
bIsDestroyingExistingSessionForJoin = true;
PendingInviteRequest = InviteResult;
SessionInterface->DestroySession(NAME_GameSession);
return;
}
JoinSession(InviteResult);
}
void USteamManagerSubsystem::OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result) {
LOG_RESULT("[OnJoinSessionComplete] SessionName: %s, Result: %s", *SessionName.ToString(), LexToString(Result));
if (Result != EOnJoinSessionCompleteResult::Success) {
LOG_ERROR("Join session complete returned non-success! %s", LexToString(Result));
return;
}
FString Address = "";
if (!SessionInterface->GetResolvedConnectString(SessionName, Address)) {
LOG_ERROR("[a] Failed to get connect string");
return;
}
LOG_RESULT("Connect Address: %s", *Address);
LOG_RESULT("Travelling client");
GetPlayer()->ClientTravel(Address, TRAVEL_Absolute);
}
void USteamManagerSubsystem::InviteUi() {
auto ExternalUI = OnlineSubsystem->GetExternalUIInterface();
if (ExternalUI.IsValid()) {
ExternalUI->ShowInviteUI(0);
}
}
void USteamManagerSubsystem::StartGame() {
if (!SessionInterface.IsValid()) {
UE_LOG(LogTemp, Warning, TEXT("No session interface (OSS not available)."));
return;
}
// If a session already exists, optionally destroy and recreate (hacky)
auto Session = SessionInterface->GetNamedSession(NAME_GameSession);
if (!Session)
return;
FString MapName;
Session->SessionSettings.Get(FName("MAP"), MapName);
GetWorld()->SeamlessTravel(MapName, true);
// GetWorld()->ServerTravel(MapName);
}
void USteamManagerSubsystem::JoinLobbyFromCommandLine() {
// Hack: check for "-lobby=STEAM_LOBBY_ID" or "-joinlobby=steam://joinlobby/..."
FString CmdLine = FCommandLine::Get();
FString LobbyId;
if (FParse::Value(*CmdLine, TEXT("lobby="), LobbyId)) {
UE_LOG(LogTemp, Log, TEXT("Commandline lobby id found: %s"), *LobbyId);
// Implementing direct join by LobbyId via OSS is platform-specific.
// A simple approach: call OnlineSubsystem Steam API directly to join by lobby id if you add Steam SDK includes.
// For now, we just log it. You can build a direct Steam call here for dev testing.
}
}
void USteamManagerSubsystem::JoinSession(const FOnlineSessionSearchResult& SearchResult) {
LOG_RESULT("[Joining session] -> %s", *SearchResult.GetSessionIdStr());
if (!SessionInterface->JoinSession(0, NAME_GameSession, SearchResult)) {
LOG_ERROR("Failed to join session");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment