Steam Online Stats Interface

Hi, I’m looking for some clarification and help with interfacing with Steam and specifically Stats. Currently, I see that there is a general online interface system for accessing Achievements and Leadersboards using:

IOnlineAchievementsPtr Achievements = OnlineSub->GetAchievementsInterface();

However, I’m not seeing a Stats Interface system here. From the steam sdk it seems rather simple in setting the interface for doing stats work, but I don’t want to duplicate a back-end already in place.

What is the recommendation for the Steam workflow, Stats specifically?

Thanks for any help on this!

I’m having a similar issue with the friends interface (here). I’m hopeful that the answer to this will answer both of our questions! If we ever do get an answer. I’ve been unlucky in that regard recently lol.

I guess a main question of mine is that if I overwrite the “STEAM_CALLBACK” for my game module steam manager, will there be duplicate steam callbacks registered, or will it just replace the engine default delegate.

STEAM_CALLBACK( CSteamAchievements, OnUserStatsReceived, UserStatsReceived_t, 
		m_CallbackUserStatsReceived );
	STEAM_CALLBACK( CSteamAchievements, OnUserStatsStored, UserStatsStored_t, 
		m_CallbackUserStatsStored );
	STEAM_CALLBACK( CSteamAchievements, OnAchievementStored, 
		UserAchievementStored_t, m_CallbackAchievementStored );

If the engine default delegate is replaced, does it break anything. I really don’t want to modify & customize engine level code. If I can just keep the steam integration contained in my game module that would be preferred. :slight_smile:

If you see the below code, the engine default callback is assigned here, but then there is a “Event” calling system that is hard to follow and I’m not sure of any documentation on it. Please… some insight on this! :slight_smile: thanks

/**
 *	Event triggered from Steam when the current user's stats have been downloaded from the backend
 *	Possible that the result fails if they have no data for the current game
 *
 * @param CallbackData All the valid data from Steam related to this event 
 */
void FOnlineAsyncTaskManagerSteam::OnUserStatsReceived(UserStatsReceived_t* CallbackData)
{
	const CGameID GameID(SteamSubsystem->GetSteamAppId());
	if (GameID.ToUint64() == CallbackData->m_nGameID)
	{
		FUniqueNetIdSteam UserId(CallbackData->m_steamIDUser);
		if (CallbackData->m_eResult != k_EResultOK)
		{
			if (CallbackData->m_eResult == k_EResultFail)
			{
				UE_LOG_ONLINE(Warning, TEXT("Failed to obtain steam user stats, user: %s has no stats entries"), *UserId.ToDebugString());
			}
			else
			{
				UE_LOG_ONLINE(Warning, TEXT("Failed to obtain steam user stats, user: %s error: %s"), *UserId.ToDebugString(), 
					*SteamResultString(CallbackData->m_eResult));
			}
		}

		FOnlineAsyncEventSteamStatsReceived* NewEvent = new FOnlineAsyncEventSteamStatsReceived(SteamSubsystem, UserId, CallbackData->m_eResult);
		UE_LOG_ONLINE(Verbose, TEXT("%s"), *NewEvent->ToString());
		AddToOutQueue(NewEvent);
	}
	else
	{
		UE_LOG_ONLINE(Warning, TEXT("Obtained steam user stats, but for wrong game! Ignoring."));
	}
}

After digging around for a bit, here is what i understand:

During “FOnlineAsyncTaskManager::GameTick()”, The queued messages are processed. For each Steam callback there is a derived “FOnlineAsyncItem” object class.

The processing of these “FOnlineAsyncItem” classes trigger 2 functions:

  1. FOnlineAsyncItem->Finalize(); //finishes work

  2. FOnlineAsyncItem->TriggerDelegates(); //Calls delegates

There is a derived “FOnlineAsyncItem” calls for receiving player stats called “FOnlineAsyncEventSteamStatsReceived”, however, the “TriggerDelegates()” is not implemented for it at this level.

Is the only way for me to use the built in Async task manager to customize the engine code? Also beyond this, it seems like a lot of “figuring out” to get the delegate system to work. I see I need to create an interface class and use some macros such as:

DEFINE_ONLINE_PLAYER_DELEGATE_THREE_PARAM(MAX_LOCAL_PLAYERS, OnSteamStatsReceived, bool, const FString&, const FString&);

Any suggestions on this? Thanks.

I could really use some Epic guidance on this :slight_smile: … thanks!

How does someone get help on this? It seems overly difficult to create an interface for Steam stats. The more I try to create an interface, the more errors I run into like linking errors between the game and engine module …etc…

Thanks!

Did you ever get this working?

Any news on this issue? I am also stuck.

Well, to be clear on this problem there is a couple of ways to use SteamStats in your own game. In fact, UE4 does have most of the leg work done for you, you just don’t know it.

First off, the acheivements, leaderboards, & stats system is a shared steam system… SteamUserStats()

The problem is that UE4 implements parts of the system mainly for achievements or their leaderboard interface, where actually we could just lump it all into the same interface, but they chose not to. The async steam tasks are written, but just the shell and its hardcoded for use only for leaderboards.

Here are the functions you need to use:

//Async task to retrieve all stats for a single user from the Steam backend
FOnlineAsyncTaskSteamRequestUserStats

//Async task to update a single user's stats on the Steam backend
FOnlineAsyncTaskSteamUpdateStats

//Async task to retrieve a single user's stats from Steam
FOnlineAsyncTaskSteamRetrieveStats

//Async task to store written Stats to the Steam backend, Triggers an OnStatsStored callback
FOnlineAsyncTaskSteamStoreStats

The way that you implement it is up to you. Normally you request a task, and then wait on the steam callback to send the data back to the game. However, they are not doing it that way, they send the request to steam and save the callback handle. Then on every tick, they check the SteamUtilties…

SteamUtilsPtr->IsAPICallCompleted(CallbackHandle, &bFailedCall) 

to see if the request is done instead of using a delegated callback. There is nothing wrong with this way.

There are also 3 already bound steam callbacks for stats through the task manager when in turns calls the assoicated event tasks. Make sure to study the flow between the task manager and task and how the data is marshalled back to the game thread and you should be set.

/** Delegate registered with Steam to trigger when the currently logged in user receives their stats from RequestStats() */
	STEAM_CALLBACK(FOnlineAsyncTaskManagerSteam, OnUserStatsReceived, UserStatsReceived_t, OnUserStatsReceivedCallback);
	/** Delegate registered with Steam to trigger when the currently logged in user has their stats stored */
	STEAM_CALLBACK(FOnlineAsyncTaskManagerSteam, OnUserStatsStored, UserStatsStored_t, OnUserStatsStoredCallback);
	/** Delegate registered with Steam to trigger when any previously requested user's stat are unloaded by the system */
	STEAM_CALLBACK(FOnlineAsyncTaskManagerSteam, OnUserStatsUnloaded, UserStatsUnloaded_t, OnUserStatsUnloadedCallback);

Hope that helps!