question

Philip Alexander Prates Machado avatar image
Philip Alexander Prates Machado asked

How to make a ranked system in playab

I have been tackling this for 20 days now, with little to no documentation and i decided to share this with every one so that you dont have to go throught the same thing.

In order for you to update your players MMR(match makin rating) you will need to do the following.

  • Get a session token
  • Call GetMatch
  • Order the information you got
  • Get your players playfabID's
  • set your developer secret key
  • call update player statistics

Before you do all that you need to change a setting in unreal so that you can actualy make calls please refer to this thread.

https://community.playfab.com/articles/43928/compatibility-issue-discovered-with-ue425-and-play.html?fbclid=IwAR0XVm_jdxU095-gTpLlzxWMaN_MZPfAEshiKDE_0Sv4H6QMg_E8hlyJve4

first all the includes you are going to need:

#include "PlayFab.h"
#include "PlayFabCommon.h"


#include "Core/PlayFabClientDataModels.h"
#include "Core/PlayFabError.h"


#include "Core/PlayFabMultiplayerAPI.h"
#include "Core/PlayFabAuthenticationAPI.h"
#include "Core/PlayFabServerAPI.h"
#include "Core/PlayFabAdminAPI.h"
#include "Core/PlayFabProfilesAPI.h"

How to get the session token :

std::unordered_map<std::string, std::string> config = Microsoft::Azure::Gaming::GSDK::getConfigSettings();

		//Retrieve a particular configuration value
		auto it = config.find(Microsoft::Azure::Gaming::GSDK::SESSION_ID_KEY);

		if (it != config.end())
		{
			
			std::string sessionIDraw = config[Microsoft::Azure::Gaming::GSDK::SESSION_ID_KEY];


			sessionID = FString(sessionIDraw.c_str());
	
			//Set settings
			PlayFab::PlayFabSettings::SetTitleId(FString("XXX"));//not sure if needed, didnt test without

			//get Token
			PlayFabAuthenticationPtr authenticationAPI = IPlayFabModuleInterface::Get().GetAuthenticationAPI();


			TSharedPtr<FJsonObject> EntityItems = MakeShareable(new FJsonObject);
			TSharedPtr<FJsonObject> Entity = MakeShareable(new FJsonObject);
			////make json
			EntityItems->SetField(TEXT("id"), MakeShareable(new FJsonValueString(FString("42C9E"))));
			EntityItems->SetField(TEXT("Type"), MakeShareable(new FJsonValueString(FString("title"))));
			EntityItems->SetField(TEXT("TypeString"), MakeShareable(new FJsonValueString(FString(""))));
			Entity->SetField(TEXT("Entity"), MakeShareable(new FJsonValueObject(EntityItems)));




			PlayFab::AuthenticationModels::FGetEntityTokenRequest tokenRequest = PlayFab::AuthenticationModels::FGetEntityTokenRequest(Entity);


			
			authenticationAPI->GetEntityToken(tokenRequest, PlayFab::UPlayFabAuthenticationAPI::FGetEntityTokenDelegate::CreateUObject(this, &AArenaGameMode::OnGetTokenSuccess), PlayFab::FPlayFabErrorDelegate::CreateUObject(this, &AArenaGameMode::OnGetTokenFailed));


The json needs to be done like that, for some reason the request doesnt get filled properly when you try to acess and fill the members directly in the request.

Now onto the Get match, if getting the token is successfull we get the match in the success callback:

PlayFab::PlayFabSettings::SetEntityToken(result.EntityToken);
	//Get match


	PlayFabMultiplayerPtr multiplayerAPI = IPlayFabModuleInterface::Get().GetMultiplayerAPI();




	PlayFab::MultiplayerModels::FGetMatchRequest request;
	request.EscapeObject = false;
	request.MatchId = sessionID;
	request.QueueName = "QuickPlay5";
	request.ReturnMemberAttributes = true;


	
	//PlayFab::FPlayFabCppRequestCommon::AuthenticationContext x = PlayFab::FPlayFabCppRequestCommon::AuthenticationContext();


	UE_LOG(LogTemp, Warning, TEXT("getting match"));
	UE_LOG(LogTemp, Warning, TEXT("%s"), *request.toJSONString());
	//LogPlayFabCpp: Error: You must call GetEntityToken API Method before calling this function.
	multiplayerAPI->GetMatch(request, PlayFab::UPlayFabMultiplayerAPI::FGetMatchDelegate::CreateUObject(this, &AArenaGameMode::OnGetMatchSuccess), PlayFab::FPlayFabErrorDelegate::CreateUObject(this, &AArenaGameMode::OnGetMatchFailed));


Here you can make the request and acess the members directly, i dont know why i think it had to do with unreal smart pointers and references, if any one that knows more about that please let me know in the answers, so if the get match is sucessfull you are gonna get a success callback where you can order ther info from all the players:

	for (int32 x = 0; x < result.Members.Num()/*result.Members.Num()*/; x++)
	{
		
		playersInfo[x].team = result.Members[x].TeamId;
		

		//get mmr
		FString atributes = result.Members[x].Attributes->toJSONString(); //
		

		TSharedPtr<FJsonObject> JsonObj = MakeShareable(new FJsonObject());
		TSharedRef<TJsonReader<TCHAR>> JsonReader = TJsonReaderFactory<TCHAR>::Create(*result.Members[0].Attributes.Get()->toJSONString());


		FString mmrString;


		if(FJsonSerializer::Deserialize(JsonReader, JsonObj))
		{
			UE_LOG(LogTemp, Warning, TEXT("deserialized sucessfully"));
			
			TSharedPtr<FJsonObject> tempObj = JsonObj->GetObjectField("DataObject");


			mmrString = tempObj->GetStringField("MMR");
		}
	
		
		int mmr = FCString::Atoi(*mmrString);

		playersInfo[x].MMR = mmr;
		
		//-----

		//get player ID
		playersInfo[x].entityID = result.Members[x].Entity.Id;
		//---------

		if (result.Members[x].TeamId == FString("team1"))
		{


			GetGameState<AProjectArenaGameState>()->Team1MMR += mmr;
		}
		else
		{
			GetGameState<AProjectArenaGameState>()->Team2MMR += mmr;


		}

	}

At this point you should have a struct with all the info from the match for all players, after that we need to get the playfab ID for the players so that we can call the function that updates the statistic because it requires a playfab ID, we do it in the login, which is the unreal function that first gets called when a player tries to connect.

APlayerController* AArenaGameMode::Login(UPlayer* NewPlayer, ENetRole InRemoteRole, const FString& Portal, const FString& Options, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage)
{


	if(playfab)//playfab
	{
		if (UGameplayStatics::HasOption(Options, FString("ID")))
		{
			UE_LOG(LogTemp, Warning, TEXT("in ID section"));
			playerID = UGameplayStatics::ParseOption(Options, FString("ID"));


			PlayFabProfilesPtr profilesAPI = IPlayFabModuleInterface::Get().GetProfilesAPI();


			TSharedPtr<FJsonObject> EntityItems = MakeShareable(new FJsonObject);
			TSharedPtr<FJsonObject> Entity = MakeShareable(new FJsonObject);
			////make json
			EntityItems->SetField(TEXT("id"), MakeShareable(new FJsonValueString(playerID)));
			EntityItems->SetField(TEXT("Type"), MakeShareable(new FJsonValueString(FString("title_player_account"))));
			EntityItems->SetField(TEXT("TypeString"), MakeShareable(new FJsonValueString(FString(""))));
			Entity->SetField(TEXT("Entity"), MakeShareable(new FJsonValueObject(EntityItems)));




			PlayFab::ProfilesModels::FGetEntityProfileRequest profileRequest = PlayFab::ProfilesModels::FGetEntityProfileRequest(Entity);
			


			UE_LOG(LogTemp, Warning, TEXT("Get player profile!!!!!!"));
			profilesAPI->GetProfile(profileRequest, PlayFab::UPlayFabProfilesAPI::FGetProfileDelegate::CreateUObject(this, &AArenaGameMode::OnGetProfileSuccess), PlayFab::FPlayFabErrorDelegate::CreateUObject(this, &AArenaGameMode::OnGetProfileFailed));


		}


		
	}
	
	return Super::Login(NewPlayer, InRemoteRole, Portal, Options, FUniqueNetIdRepl(), ErrorMessage);
}


If the callback is sucessfull we are going to need to know which player requested that "GetProfile" so in order to identify the players we connect with the entity token ID without that we wouldnt know which player called that function and we would have the value but we woulnt know who which player it belonged to so make sure you connect with a identity token ID in the server parameters.

Sucess callback looks like so:

	for (int32 x = 0; x < playersInfo.Num()/*result.Members.Num()*/; x++)
	{
		//entity ID that we connect with that comes in get match //entity ID in the response
		if (playersInfo[x].entityID == result.Profile.Get()->Entity.Get()->Id)
		{
			playersInfo[x].playfabID = result.Profile.Get()->Lineage.Get()->MasterPlayerAccountId; //the actual playfab ID needed


		}
	}

After we have all this info, all that is left is updating the MMR bu first you gota set ur secret key:

IPlayFabCommonModuleInterface::Get().SetDeveloperSecretKey(FString("yourkeyherexxxxx"));
//key found on your dashboard

		PlayFabServerPtr serverAPI = IPlayFabModuleInterface::Get().GetServerAPI();

After that just make the call when the game ends:

PlayFab::ServerModels::FUpdatePlayerStatisticsRequest statisticRequest;
						TArray<PlayFab::ServerModels::FStatisticUpdate> statisticsArray;
						PlayFab::ServerModels::FStatisticUpdate statistic;


						statistic.StatisticName = FString("MatchMakingRating");
						statistic.Value = 10;


						statisticsArray.Add(statistic);


						statisticRequest.Statistics = statisticsArray;
						statisticRequest.PlayFabId = playersInfo[x].playfabID;
						serverAPI->UpdatePlayerStatistics(statisticRequest, PlayFab::UPlayFabServerAPI::FUpdatePlayerStatisticsDelegate::CreateUObject(this, &AArenaGameMode::OnStatisticUpdateSuccess), PlayFab::FPlayFabErrorDelegate::CreateUObject(this, &AArenaGameMode::OnStatisticUpdateFailed));
					}

.H callbacks:

void OnGetMatchSuccess(const PlayFab::MultiplayerModels::FGetMatchResult & result);


	void OnGetMatchFailed(const PlayFab::FPlayFabCppError & error);




	void OnGetTokenSuccess(const PlayFab::AuthenticationModels::FGetEntityTokenResponse & result);


	void OnGetTokenFailed(const PlayFab::FPlayFabCppError & error);


	void OnStatisticUpdateSuccess(const PlayFab::ServerModels::FUpdatePlayerStatisticsResult & result);


	void OnStatisticUpdateFailed(const PlayFab::FPlayFabCppError & error);


	void OnGetProfileSuccess(const PlayFab::ProfilesModels::FGetEntityProfileResponse & result);


	void OnGetProfileFailed(const PlayFab::FPlayFabCppError & error);


Thats it!


Please keep in mind that this might have some bugs and is probably not the best code in the world, i am not even sure if this is the correct or intended way to do things, i just know that it works and it gave me a lot of trouble to figure all of this out. I just hope this helps some one.

If any Playfab dev sees this and knows of a better way to do things please let us know, i am just trying to fill a gap in the documentation (god knows we need it haha).


Also if this helped you please buy me a beer if you ever see me.

unreal
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

1 Answer

·
Rick Chen avatar image
Rick Chen answered

Thank you for sharing this solution. The Unreal smart pointer is designed to ease the burden of memory allocation and tracking, it basically improves the performance of the program.

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.