Created
November 28, 2025 22:43
-
-
Save iDevelopThings/5a9ea3be1f777130cb0fae5fcffd192a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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