Does the Steam online subsystem support inviting and joining friends with the new 4.6 session nodes?

I’ve recently hooked up our game into Steam and written a very simple server browser with the new blueprint nodes for Create Session, Join Session, etc. I can play the game with other players online through Steam with the demo appId (480).

If a player is in a session, they can right click somebody on their Steam friend list and select “Invite to game” which will send them an invitation that the friend can see. Likewise, a player can right click a friend in a session and select “Join game”.

However, accepting an invitation or clicking “Join game” do not do anything, even if the game is already open. (I don’t expect Steam to be able to launch the game while in development and using the demo appId)

Should these features work by default? Do they require a real appId? Are there any steps I can take to get this functionality working?

Thanks.

1 Like

At the moment this requires you to dive into c++. It is not that hard. I am working on integrating inviting friends into the engine via blueprints. If you want I can send you some demo code.

1 Like

I’m fine with C++, that would be great.

Here you go:
Its purpose is to provied the friends Interface to blueprints but you can learn pretty well from it as well.
If you need any clarification, either post it here or when I get the invite system to work I will post an indepth wiki article.

Thanks for this. I had a sniff around the engine code, and it looks accepting an invite will successfully join the session, however the joining client won’t travel to the map the server is on.

The delegate to travel when a session is joined should be registered here, but it looks like it’s commented out for some reason…?

https://github.com/EpicGames/UnrealEngine/blob/4.6/Engine/Source/Runtime/Online/OnlineSubsystemUtils/Private/OnlineSessionClient.cpp#L280

The Join Session blueprint node does travel when the session is joined, which is why it actually works.

https://github.com/EpicGames/UnrealEngine/blob/4.6/Engine/Source/Runtime/Online/OnlineSubsystemUtils/Private/JoinSessionCallbackProxy.cpp#L34

I wonder if this can be overridden without modifying engine code… (I’m using the prebuilt engine)

Alright, I actually got the accepting invite and joining game functionality working.
Not sure if this is the best way of doing it but it’s the only thing I’ve tried that works.

#pragma once

#include "Engine/GameInstance.h"
#include "OnlineSessionInterface.h"
#include "CCGameInstance.generated.h"

UCLASS()
class SPELLBRAID_API UCCGameInstance : public UGameInstance
{
	GENERATED_BODY()

	virtual void Init() override;

	void OnSessionInviteAccepted(int32 LocalUserNum, bool bWasSuccessful, const FOnlineSessionSearchResult& SearchResult);

	void OnJoinSessionCompleted(FName SessionName, EOnJoinSessionCompleteResult::Type Result);
};

#include "Spellbraid.h"
#include "CCGameInstance.h"
#include "OnlineSubsystemUtils.h"


void UCCGameInstance::Init()
{
	IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
	if (OnlineSub)
	{
		IOnlineSessionPtr SessionInt = OnlineSub->GetSessionInterface();
		if (SessionInt.IsValid())
		{
			int32 ControllerId = 0;
			if (ControllerId != 255)
			{
				FOnSessionInviteAcceptedDelegate OnSessionInviteAcceptedDelegate = FOnSessionInviteAcceptedDelegate::CreateUObject(this, &UCCGameInstance::OnSessionInviteAccepted);
				SessionInt->AddOnSessionInviteAcceptedDelegate(ControllerId, OnSessionInviteAcceptedDelegate);

				FOnJoinSessionCompleteDelegate joinDelegate = FOnJoinSessionCompleteDelegate::CreateUObject(this, &UCCGameInstance::OnJoinSessionCompleted);
				SessionInt->AddOnJoinSessionCompleteDelegate(joinDelegate);
			}
		}
	}
}

void UCCGameInstance::OnSessionInviteAccepted(int32 LocalUserNum, bool bWasSuccessful, const FOnlineSessionSearchResult& SearchResult)
{
	UE_LOG(LogOnline, Verbose, TEXT("OnSessionInviteAccepted LocalUserNum: %d bSuccess: %d"), LocalUserNum, bWasSuccessful);

	if (bWasSuccessful)
	{
		if (SearchResult.IsValid())
		{
			IOnlineSessionPtr SessionInt = IOnlineSubsystem::Get()->GetSessionInterface();
			SessionInt->JoinSession(LocalUserNum, GameSessionName, SearchResult);
		}
		else
		{
			UE_LOG(LogOnline, Warning, TEXT("Invite accept returned no search result."));
		}
	}
}

void UCCGameInstance::OnJoinSessionCompleted(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
	UE_LOG(LogOnline, Verbose, TEXT("JoinSessionCompleted"));
	IOnlineSessionPtr Sessions = IOnlineSubsystem::Get()->GetSessionInterface();
	if (Sessions.IsValid())
	{
		UE_LOG(LogOnline, Verbose, TEXT("Sessions Valid"));
		if (Result == EOnJoinSessionCompleteResult::Success)
		{
			// Client travel to the server
			FString ConnectString;
			if (Sessions->GetResolvedConnectString(GameSessionName, ConnectString))
			{
				UE_LOG(LogOnline, Log, TEXT("Join session: traveling to %s"), *ConnectString);
				GetFirstLocalPlayerController()->ClientTravel(ConnectString, TRAVEL_Absolute);
			}
		}
	}
}

@anonymous_user_c4f1fb6c Thanks for your help with integrating the steam friend system with UE4. I have a couple of questions about your code, you said:

“GetFriendsListCallBackProxy: This is a class which is a whole blueprint node”

Where do you call this node? Is it meant to be inside the pawn event graph? My next question is, can you help me understand what the “UObject* WorldContextObject;” is suppose to be? Would that be the GUI, player or game instance?

Thanks again for your help!

Sorry I did not see your reply earlier. The WorldContextObject is not important (It will not show up in the blueprint editor, if it does just compile). You can call this node every where you want. I call it in my UMG widget which is the friendslist.

1 Like

Sorry for the noob question, but how would I go about implementing this? I mean actually putting the files into the engine?

Now (Feb 2016) Some of the above code sample does not build. Here is my version for UE 4.10.
It also is for a different game so the base class name is UDSS… instead of the above sample’s UCC… naming.

#include "MyGame.h"
#include "DSSGameInstanceBase.h"
#include "OnlineSubsystemUtils.h"

 void UDSSGameInstanceBase::Init()
 {
	 IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
	 if (OnlineSub)
	 {
		 IOnlineSessionPtr SessionInt = OnlineSub->GetSessionInterface();
		 if (SessionInt.IsValid())
		 {
			 int32 ControllerId = 0;
			 if (ControllerId != 255)
			 {
				 FOnSessionUserInviteAcceptedDelegate inviteDelegate = FOnSessionUserInviteAcceptedDelegate::CreateUObject( this, &UDSSGameInstanceBase::OnSessionUserInviteAccepted);
				 SessionInt->AddOnSessionUserInviteAcceptedDelegate_Handle(inviteDelegate);

				 FOnJoinSessionCompleteDelegate joinDelegate = FOnJoinSessionCompleteDelegate::CreateUObject(this, &UDSSGameInstanceBase::OnJoinSessionCompleted);
				 SessionInt->AddOnJoinSessionCompleteDelegate_Handle(joinDelegate);
			 }
		 }
	 }
 }

 void UDSSGameInstanceBase::OnSessionUserInviteAccepted(bool bWasSuccessful, int32 LocalUserNum, TSharedPtr<const FUniqueNetId>, const FOnlineSessionSearchResult& SearchResult)
 {
	 UE_LOG(DSS_STEAM, Verbose, TEXT("OnSessionInviteAccepted LocalUserNum: %d bSuccess: %d"), LocalUserNum, bWasSuccessful);

	 if (bWasSuccessful)
	 {
		 if (SearchResult.IsValid())
		 {
			 IOnlineSessionPtr SessionInt = IOnlineSubsystem::Get()->GetSessionInterface();
			 SessionInt->JoinSession(LocalUserNum, GameSessionName, SearchResult);
		 }
		 else
		 {
			 UE_LOG(DSS_STEAM, Warning, TEXT("Invite accept returned no search result."));
		 }
	 }
 }

 void UDSSGameInstanceBase::OnJoinSessionCompleted(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
 {
	 UE_LOG(DSS_STEAM, Verbose, TEXT("JoinSessionCompleted"));
	 IOnlineSessionPtr Sessions = IOnlineSubsystem::Get()->GetSessionInterface();
	 if (Sessions.IsValid())
	 {
		 UE_LOG(DSS_STEAM, Verbose, TEXT("Sessions Valid"));
		 if (Result == EOnJoinSessionCompleteResult::Success)
		 {
			 // Client travel to the server
			 FString ConnectString;
			 if (Sessions->GetResolvedConnectString(GameSessionName, ConnectString))
			 {
				 UE_LOG(DSS_STEAM, Log, TEXT("Join session: traveling to %s"), *ConnectString);
				 GetFirstLocalPlayerController()->ClientTravel(ConnectString, TRAVEL_Absolute);
			 }
		 }
	 }
 }
1 Like

The ShooterGame example has this implemented in a slightly different way. It Still uses the OnSessionUserInviteAccepted method but it then sets the PendingInvite.bPrivilegesCheckedAndAllowed to true and in Tick() if it is true it joins the game session. The way the shooter game does it just takes the player to the welcome screen first. As long as you are implementing OnSessionUserInviteAccepted and then calling JoinSession it doesn’t matter how you go about it. Your code is going to help anyone looking on how to implement this.
It sucks that the OnlineSubsystem documentation is pretty lacking and everything that we know so far has been pretty much reverse engineered.