Party #

The Party service provides an entry point into the game flow. This service allows players to create a party, configure their game, and make selections. Using the Party Plugin and leveraging extension data, developers can create custom party configurations and workflows.

This service coordinates invites and shared player states in real time. Pragma Engine’s deterministic routing capability allows for calls from players to arrive at the correct service node, eliminating unnecessary load. The engine uses a persistent WebSocket connection to send broadcasts to clients so that each party is synchronized to maintain integrity of the party state. The Party service is massively parallel to support thousands of parties per box and scale horizontally to any number of Party service instances.

On this page, we’ll take a look at some key concepts, and then we’ll dive into the Party Plugin specifics, including the plugin’s functions and associated RPC calls.

Key Concepts #

The Party Data Classes #

There are two main data classes within the Party service: Party and PartyPlayer. These classes are created by the platform and handed to plugins to customize at the extension points described later on this page.

The Party Class #

propertydescription
partyIdunique identifier for the party
maxPlayerCountnumber representing the maximum amount of players for the party set via the PartyService config
disableMaxPlayerCountboolean indicating if the automatic check for maximum player count is disabled or not, set via the PartyService config
gameServerVersioncomputed game server version based on the PartyPlayers in the party, updated whenever a player joins or leaves the party
preferredGameServerZoneslist of game server zones that the party can play a match on
inviteCodeinvite code for the party that players can use to join
overrideGameServerVersionboolean indicating if the Game Server Version Compatibility Plugin should validate this party or not, set via PartyService config
partyMembersByPlayerIdmap of all players in the party index by their playerId
extPartySelectionsext proto of selections for the party
extHiddenPartyDataext proto of sensitive data for the party that is not sent to players’ game clients

The PartyPlayer Class #

propertydescription
sessionKeyidentification object including the player’s pragmaId, also known as their playerId
displayNameobject containing the player’s name and discriminator
inventorysnapshot of the player’s inventory from when they entered the party or last completed a match
gameClientVersionversion of the game client the player is currently running
gameServerZoneToPingmap of this player’s ping to various game server zones
leaderboolean indicating if this player is a leader in this party
readyboolean indicating if this player ready to play a match
extPlayerSelectionsext proto of personal selections this player has made while in the party
extPrivatePlayerSelectionsext proto of personal selections that can only be seen by this player
extHiddenPlayerDataext proto of sensitive data for this player that is not sent to players’ game clients

Configuration #

The PartyConfig configuration class includes everything that can be defined in the main configuration YAML file.

configdescription
maxPlayersPerPartymaximum players allowed in each party
disableMaxPlayerCountwhether to disable maximum player count limits in each party
enableLocalGameServerVersionswhether to allow local game clients to override their game server version for local development purposes
repeatInviteDelaySecondsdefines the delay between a player inviting another player consecutive times
inventoryTagsToIncludetags used to filter player inventories, and items that match one or more tags will be included
enableTransferPartyLeaderwhether to transfer party leader status from one to another when invoking AssignPartyLeaderV1

Player Inventory Validation #

When a player is added to a party, a snapshot of their inventory is taken and stored on ServiceInventoryData. This data is then used to validate party selections against player inventory–for example, players should only be able to select skins they have purchased.

Use the inventoryTagsToInclude configuration map to determine which parts of a player inventory are required for this validation.

Party RPCs #

Most actions in the Party service are done using the Party RPCs. The table below contains a full list of all the Party service RPCs, along with a link to the section in this document that explains it in more detail.

RPCsection
CreateCreating a party
SendInviteManaging players
RespondToInviteManaging players
JoinWithInviteCodeManaging players
JoinWithPartyIdManaging players
LeaveManaging players
KickManaging players
AssignPartyLeaderManaging players
SetGameServerZoneToPingManaging players
SetPreferredGameServerZonesManaging players
UpdatePlayerSelectionsMaking selections
UpdatePartySelectionsMaking selections
SetReadyStateSetting ready states
StartMatchmakingEntering matchmaking

Party State #

When players make changes to the party state, such as by making party or player selections, all players receive an up-to-date payload along with a PartyDetailsV1Notification that contains information about any changes from other players.

This notification contains the BroadcastParty payload, which includes the following information.

For the party:

  • ExtPartySelections
  • ExtPrivatePlayerSelections
  • party ID
  • a list of all public info about each party member
  • the invite code
  • preferred game server zones

For each player:

  • ExtPlayerSelections
  • player ID
  • display name
  • is_ready boolean
  • is_leader boolean

Types of Extension Data #

During a party’s construction, you can populate all the custom data that will be present on the party throughout the entire game flow lifecycle. This data persists on the party. On Match End, the pieces of data that are changed can be defined in PartyPlugin.returnFromMatch.

There are two types of data related to the Party service: player data and party data. These are further defined by visibility; data can be visible to the entire party, to an individual player, or to the server only (hidden). To initialize data, populate the related ext fields in the Party plugin during a party’s construction when the initializeParty function is triggered. These fields are stored on the Party or PartyPlayer classes, and are passed to other plugins while the party journeys through the game loop. By default, this data can be referenced by other plugins, but cannot be changed.

Below is a chart containing descriptions and examples for every type of Party service data.

namevisibilitydescriptionexample
ExtPartySelectionspartydesignated by party leader and applies to entire partymap or game mode
ExtHiddenPartyDataserverscoped to the entire party but hidden to all playersteam win/loss streak tracker
ExtPlayerSelectionspartyindividual player data broadcast to entire partycharacter/skin selections or map/game mode votes
ExtPrivatePlayerSelectionsplayerindividual player data invisible to the partyplayer deck selections in a collectible/trading card game
ExtHiddenPlayerDataserverper-player data visible to the Party service and game servers onlyhidden player MMR

Game Client Versions #

Game servers and game clients must be updated in parallel as updates are added to live service games. Pragma Engine provides the capability to enforce game client versions via the PartyService.gameServerCompatibilityPlugin. Lists of game server versions and corresponding compatible game client versions can be defined.

Note that the same game client version can match to different game server versions. If players in a party are compatible with multiple game server versions, the highest game server version is used, as defined by the keys in versionCompatibility. The {{USER_DEFINED_GAME_SERVER_VERSION}} value is passed to the Matchmaking service.

A valid config must be defined, and it must include:

  • a versionCompatibility config block
  • a clientVersions config block
  • unique versionCompatibility keys which are positive ints–greater ints designate higher priority
  • a unique {{USER_DEFINED_GAME_SERVER_VERSION}} which is not an empty string
  • a unique {{USER_DEFINED_GAME_CLIENT_VERSION}} which is not an empty string
Example: OrderedGameServerCompatibilityPlugin

This is a sample implementation of the Game Server Compatibility Plugin. It has a configuration file and a Kotlin code file.

Configuration:

game:
  pluginConfigs:
    PartyService.gameServerCompatibilityPlugin:
      class: "pragma.party.OrderedGameServerCompatibilityPlugin"
      config:
        versionCompatibility:
          1:
            serverVersion: "gameServerVersion1"
            clientVersions:
              1: "gameClientVersion0"
              2: "gameClientVersion1"
          2:
            serverVersion: "gameServerVersion2"
            clientVersions:
              1: "gameClientVersion1"
              2: "gameClientVersion2"

Kotlin:

@Suppress("unused")
class OrderedGameServerCompatibilityPlugin(
   override val service: Service,
   override val contentDataNodeService: ContentDataNodeService,
) : GameServerCompatibilityPlugin, ConfigurablePlugin<OrderedGameServerCompatibilityPlugin.Config> {
   var logger: Logger = LoggerFactory.getLogger(OrderedGameServerCompatibilityPlugin::class.simpleName)

   constructor(service: Service, contentDataNodeService: ContentDataNodeService, logger: Logger) : this(service, contentDataNodeService) {
       this.logger = logger
   }

   data class ServerVersionInfo(val gameServerVersionName: String, val gameServerVersionNumber: Int)

   private lateinit var clientVersionToServerVersions: Map<String, Set<ServerVersionInfo>>

   private var onStartup = true

   class Config private constructor(type: BackendType) : PluginConfig<Config>(type) {
       override val description = "OrderedGameServerCompatibility plugin configuration."

       var versionCompatibility by types.mapOfObject(
           VersionCompatibility::class,
           "map of all the server versions to compatible game client versions"
       )

       companion object : ConfigBackendModeFactory<Config> {
           override fun getFor(type: BackendType): Config {
               return Config(type)
           }
       }
   }

   override suspend fun onConfigChanged(config: Config) {
       val exception = isValid(config)
       if (exception != null) {
           if (onStartup) {
               throw exception
           } else {
               logger.warn("Could not parse bad configuration: ${exception.message}")
               return
           }
       }
       onStartup = false

       val clientVersionToServerVersions = mutableMapOf<String, MutableSet<ServerVersionInfo>>()
       config.versionCompatibility.entries.map {
           val serverVersionName = it.value.serverVersion
           val serverVersionNumber = it.key.toInt()
           it.value.clientVersions.values.forEach { gameClientVersion ->
               val serverVersionInfo = ServerVersionInfo(serverVersionName, serverVersionNumber)
               if (gameClientVersion in clientVersionToServerVersions) {
                   clientVersionToServerVersions[gameClientVersion]!!.add(serverVersionInfo)
               } else {
                   clientVersionToServerVersions[gameClientVersion] = mutableSetOf(serverVersionInfo)
               }
           }
       }

       if (this::clientVersionToServerVersions.isInitialized) {
           val oldServerVersions =
               this.clientVersionToServerVersions.entries.flatMap { it.value.map { serverVersionInfo -> serverVersionInfo.gameServerVersionName } }
                   .toSet()
           val newServerVersions = config.versionCompatibility.entries.map { it.value.serverVersion }.toSet()

           val removedServerVersions = oldServerVersions - newServerVersions
           if (removedServerVersions.any()) {
               service.requestRpc(
                   MatchmakingRpc.RemoveGameServerVersionV1Request.newBuilder().addAllGameServerVersions(removedServerVersions).build(),
                   MatchmakingRpc.RemoveGameServerVersionV1Response::class
               )
           }
       }

       this.clientVersionToServerVersions = clientVersionToServerVersions
   }

   private fun isValid(config: Config): Exception? {
       val versionCompatibilityKeys = config.versionCompatibility.keys
       val notValidInts = versionCompatibilityKeys.any {
           val num = it.toIntOrNull()
           num == null || num <= 0
       }
       if (notValidInts) {
           return Exception("version compatibility keys be a positive integer")
       }

       config.versionCompatibility.entries.forEach {
           val gameClientVersions = it.value.clientVersions
           if (it.key.isEmpty()) {
               return Exception("serverVersion must not be an empty string")
           }
           if (gameClientVersions.isEmpty()) {
               return Exception("clientVersions must contain at least one value")
           }
           if (gameClientVersions.any { clientVersion -> clientVersion.value.isEmpty() }) {
               return Exception("clientVersions must not contain empty values")
           }
       }

       if (config.versionCompatibility.entries.map { it.value.serverVersion }.toSet().size != config.versionCompatibility.entries.size) {
           return Exception("serverVersion was repeated")
       }

       return null
   }

   override fun calculateGameServerVersion(party: Party, newPlayer: PartyPlayer?): String? {
       var possibleGameServerVersion = setOf<ServerVersionInfo>()
       val allPlayers = (party.partyMembersByPlayerId.values + newPlayer).filterNotNull()
       allPlayers.forEachIndexed { index, it ->
           val clientVersion = it.gameClientVersion
           val supportedGameServerVersions = clientVersionToServerVersions[clientVersion] ?: emptySet()
           possibleGameServerVersion = if (index == 0) {
               supportedGameServerVersions
           } else {
               possibleGameServerVersion.intersect(supportedGameServerVersions)
           }

           if (possibleGameServerVersion.isEmpty()) {
               return null
           }
       }

       return possibleGameServerVersion.maxByOrNull { it.gameServerVersionNumber }!!.gameServerVersionName
   }

   override fun getValidGameClientVersions(): List<String> {
       return clientVersionToServerVersions.keys.toList()
   }
}

class VersionCompatibility private constructor(type: BackendType) :
   ConfigObject<VersionCompatibility>(type) {
   constructor(serverVersion: String, clientVersions: Map<String, String>) : this(BackendType.GAME) {
       this.serverVersion = serverVersion
       this.clientVersions = ConfigMapOfPrimitive(this)
       this.clientVersions.putAll(clientVersions)
   }

   override val description = "clientVersions to serverVersion support configuration."

   var serverVersion by types.string("game server version string, must not be empty")
   var clientVersions by types.mapOfString("game client versions supported by this server version")

   companion object : ConfigBackendModeFactory<VersionCompatibility> {
       override fun getFor(type: BackendType): VersionCompatibility {
           return VersionCompatibility(type)
       }
   }
}

Party Plugin #

The Party Plugin provides a variety of calls to allow developers to customize party functionality.

We’ve grouped the functions into 5 main categories for clarity, but note that Party service RPCs can be called at any time.

When entering matchmaking, a snapshot of the party’s state is captured, and is not automatically updated with changes.

Each section will describe the category, plugin functions, and any related RPC calls from the game server to the SDK.

Party Plugin interface
interface PartyPlugin {
    
        suspend fun initializeParty(party: Party, requestExt: ExtCreateRequest, partyConfig: PartyConfig)
        
        suspend fun onAddPlayer(requestExt: ExtPlayerJoinRequest, playerToAdd: PartyPlayer, party: Party, partyConfig: PartyConfig)
            
        suspend fun onRemovePlayer(party: Party, removedPlayer: PartyPlayer, removalReason: RemovalReason)
        
        suspend fun updateParty(requestingPlayer: PartyPlayer, requestExt: ExtUpdatePartySelectionsRequest, party: Party, partyConfig: PartyConfig)
        
        suspend fun updatePlayer(playerToUpdate: PartyPlayer, requestExt: ExtUpdatePlayerSelectionsRequest, party: Party, partyConfig: PartyConfig)
        
        suspend fun canChangeReady(player: PartyPlayer, newReadyState: Boolean, party: Party, partyConfig: PartyConfig): Boolean
        
        suspend fun buildMatchmakingKey(party: Party): ExtMatchmakingKey
        
        suspend fun buildExtEnterMatchmakingV2Request(party: Party): ExtEnterMatchmakingV2Request
        
        suspend fun returnFromMatchmaking(party: Party, returningPlayers: List<PartyPlayer>, config: PartyConfig)
    
        suspend fun returnFromMatch(party: Party, returningPlayers: List<PartyPlayer>, config: PartyConfig)

Creating a party #

A party is initialized and the first player is added as the leader.

Actions #

The Create RPC call requires the following data:

  • ExtCreateRequest: custom-defined content related to party creation
  • ExtPlayerJoinRequest: custom-defined content related to players joining
  • (optional) Game server zones: preferred game server zones, which can be changed later (see SetPreferredGameServerZones)
  • (optional) Game server zone to ping map: a ping map that can be changed later (see SetGameServerZoneToPing)
TArray<FString> preferredGameServerZones;
preferredGameServerZones.Add("gameServerZone1");
preferredGameServerZones.Add("gameServerZone2");

TMap<FString, int32> gameServerZoneToPing;
gameServerZoneToPing.Add("gameServerZone1", 10);
gameServerZoneToPing.Add("gameServerZone2", 20);

UPragmaPartyService::FOnCompleteDelegate OnPartyCreatedDelegate;
OnPartyCreatedDelegate.BindWeakLambda(this, [this](TPragmaResult<> Result)
{
});
Session->Party().CreateV1(FPragma_Party_ExtCreateRequest{}, FPragma_Party_ExtPlayerJoinRequest{},
preferredGameServerZones, gameServerZoneToPing, OnPartyCreatedDelegate);
session.Party.CreatePartyV1(new ExtCreateRequest(), new ExtPlayerJoinRequest());
  {
    "requestId": 1,
    "type": "PartyRpc.CreateV1Request",
    "payload": {
      "createRequestExt":{},
      "playerJoinRequestExt":{},
      "gameClientVersion":"gameClientVersion1",
      "preferredGameServerZones": [ "gameServerZone1", "gameServerZone2" ],
      "gameServerZoneToPing": { "gameServerZone1": 10, "gameServerZone2": 20 }
    }
  }

Functions #

The following function initializes the party, leveraging the data from the ExtCreateRequest.

suspend fun initializeParty(party: Party, requestExt: ExtCreateRequest, partyConfig: PartyConfig)

Once the blank party is created, the first player is added as the party’s leader using the onAddPlayer function. Note that this is the same function that is used to add other players in the next section.

suspend fun onAddPlayer(requestExt: ExtPlayerJoinRequest, playerToAdd: PartyPlayer, party: Party, partyConfig: PartyConfig)
If a player is already in a party when attempting to make a new party, they’re automatically removed from the existing party.

Managing players #

Players join the party using invites, assign leader roles, or are removed from the party.

Actions #

Sending an invite

The RPC call SendInvite sends an invitation to the listed player with a PartyRpc.InviteReceivedV1Notification. This request contains the player ID of the invited player.

UPragmaPartyService::FOnCompleteDelegate SendInviteDelegate;

SendInviteDelegate.BindWeakLambda(this, [this](TPragmaResult<> Result) {});

Session->Party().SendInviteV1(
FPragma_Party_SendInviteV1Request{"f9525239-cd99-425d-9f14-1ff948c02553"}, SendInviteDelegate
);
session.Party.SendInviteV1(
  new SendInviteV1Request {
    InviteePlayerId = new PragmaId("f9525239-cd99-425d-9f14-1ff948c02553")
  }
));
{
  "requestId": 1,
  "type": "PartyRpc.SendInviteV1Request",
  "payload": {
    "inviteePlayerId": "f9525239-cd99-425d-9f14-1ff948c02553"
  }
}

Responding to an invite

The RespondToInvite call determines whether the player will join the party they’ve been invited to.

This request contains the following information:

  • ExtPlayerJoinRequest: a developer-defined ext used to declare publicly visible player selections
  • invite ID including in the SendInvite call
  • boolean stating whether the player accepted or declined the invite
  • game client version, which is used to calculate compatible game servers
  • map of pings to game server zones
UPragmaPartyService::FOnCompleteDelegate RespondToPartyInviteDelegate;
RespondToPartyInviteDelegate.BindWeakLambda(this, [this](TPragmaResult<> Result)
{
});

Session->Party().RespondToInviteV1(FPragma_Party_ExtPlayerJoinRequest{},"89962f84-7edd-45b9-97c0-b6fd49fc8497",true,RespondToPartyInviteDelegate);
session.Party.RespondToInviteV1(new ExtPlayerJoinRequest(), invite.InviteId, true);
{
  "requestId": 2,
  "type": "PartyRpc.RespondToInviteV1Request",
  "payload": {
    "requestExt":{},
    "inviteId":"89962f84-7edd-45b9-97c0-b6fd49fc8497",
    "accepted":true,
    "gameClientVersion":"gameClientVersion1"
  }
}

Joining a party

There are two ways for players to join parties: with an invite code, or by party ID.

  • party ID: standard party join by ID
  • invite code: 6 character strings

To join by invite code, use the JoinWithInviteCode request. This includes the following information:

  • ExtPlayerJoinRequest
  • invite code
  • game client version
  • map of pings to game server zones
UPragmaPartyService::FOnCompleteDelegate JoinWithInviteCodeDelegate;
JoinWithInviteCodeDelegate.BindWeakLambda(this, [this](TPragmaResult<> Result)
{
});

Session->Party().JoinWithInviteCodeV1(FPragma_Party_ExtPlayerJoinRequest {},"CDCLGP",JoinWithInviteCodeDelegate);
session.Party.JoinWithInviteCodeV1(new ExtPlayerJoinRequest(), "CDCLGP");
{
  "requestId": 3,
  "type": "PartyRpc.JoinWithInviteCodeV1Request",
  "payload": {
    "requestExt":{},
    "inviteCode":"CDCLGP",
    "gameClientVersion":"gameClientVersion1"
  }
}

To join by party ID, use the JoinWithPartyId request. This includes the following information:

  • ExtPlayerJoinRequest
  • party ID
  • game client version
  • map of pings to game server zones
UPragmaPartyService::FOnCompleteDelegate JoinWithPartyIdDelegate;
JoinWithPartyIdDelegate.BindWeakLambda(this, [this](TPragmaResult<> Result)
{
});

Session->Party().JoinWithPartyIdV1(FPragma_Party_ExtPlayerJoinRequest{},"5e359a34-8112-4a84-81d5-86f11f261f67",JoinWithPartyIdDelegate);
session.Party.JoinWithPartyIdV1(new ExtPlayerJoinRequest(), "5e359a34-8112-4a84-81d5-86f11f261f67");
{
  "requestId": 4,
  "type": "PartyRpc.JoinWithPartyIdV1Request",
  "payload": {
    "requestExt":{},
    "partyId":"5e359a34-8112-4a84-81d5-86f11f261f67",
    "gameClientVersion":"gameClientVersion1"
  }
}

Leaving a party

The Leave call allows players to voluntarily leave a party.

If a leader leaves a party, a random player in the party is made leader.
UPragmaPartyService::FOnCompleteDelegate LeavePartyDelegate;
LeavePartyDelegate.BindWeakLambda(this, [this](TPragmaResult<> Result)
{
});

session.Party.LeaveV1();
session.Party.JoinWithPartyIdV1(new ExtPlayerJoinRequest(), "5e359a34-8112-4a84-81d5-86f11f261f67");
{
  "requestId": 4,
  "type": "PartyRpc.LeaveV1Request",
  "payload": {
  }
}
If a player disconnects from the engine or if they log into a second client, that player will be removed from the party.

Setting map to ping

Individual players can call SetGameServerZoneToPing to update the map of their ping to different game server zones.

UPragmaPartyService::FOnCompleteDelegate SetGameServerZoneToPingDelegate;
SetGameServerZoneToPingDelegate.BindWeakLambda(this,
[this](TPragmaResult<> Result)
		{
		});

const TMap<FString, int32> ZoneToPing = {{"us-west", 100}};
Session()->Party().SetGameServerZoneToPingV1(ZoneToPing, SetGameServerZoneToPingDelegate);
var pingMap = new Dictionary<string, int> { { "us-west", 30 } };
var future = Runtime.Player().Party.SetGameServerZoneToPingV1(pingMap);
{
	"requestId": 9,
	"type": "PartyRpc.SetGameServerZoneToPingV1",
	"payload": {
		"gameServerZoneToPing": {"us-west": 100}
	}
}

Leader only calls

Only a leader can kick another player, assign the leader roles, or set game server zones.

To assign the leader role to another player, use the AssignPartyLeader call. This takes the player ID to assign leader status.

If PartyConfig.enableTransferPartyLeader is set to true, this call transfers the party leader role to the indicated player. If it’s set to false, a new leader is made, resulting in multiple leaders.

UPragmaPartyService::FOnCompleteDelegate AssignPartyLeaderDelegate;
AssignPartyLeaderDelegate.BindWeakLambda(this, [this](TPragmaResult<> Result)
{
});

Session->Party().AssignPartyLeaderV1("5e359a34-8112-4a84-81d5-87f11f261f77",AssignPartyLeaderDelegate);
session.Party.AssignPartyLeaderV1("5e359a34-8112-4a84-81d5-86f11f261f67");
{
  "requestId": 3,
  "type": "PartyRpc.AssignPartyLeaderV1Request",
  "payload": {
    "playerId":"5e359a34-8112-4a84-81d5-87f11f261f77"
  }
}

To kick a player from the party, use the Kick call, which takes the player ID for the kicked player.

In the case of multiple leaders, one leader can kick another leader.
UPragmaPartyService::FOnCompleteDelegate KickPlayerFromPartyDelegate;
KickPlayerFromPartyDelegate.BindWeakLambda(this, [this](TPragmaResult<> Result)
{
});

Session->Party().KickPlayerV1("5e359a34-8112-4a84-81d5-87f11f261f77",KickPlayerFromPartyDelegate);
session.Party.KickPlayerV1("5e359a34-8112-4a84-81d5-86f11f261f67");
{
  "requestId": 5,
  "type": "PartyRpc.KickV1Request",
  "payload": {
    "playerId":"5e359a34-8112-4a84-81d5-87f11f261f77"
  }
}

To manually specify preferred game server zones, use the SetPreferredGameServerZones call. This sets a list of acceptable game server zones for the entire party. Matchmaking can then take into account both manual game server zone selections and all party members’ pings to each game server zone as part of its game server allocation logic.

UPragmaPartyService::FOnCompleteDelegate SetPreferredGameServerZonesDelagate;
SetPreferredGameServerZonesDelagate.BindWeakLambda(this,
[this](TPragmaResult<> Result)
		{
		});

const TArray<FString> GameServerZones = {"us-west"};
Session()->Party().SetPreferredGameServerZonesV1(GameServerZones, SetPreferredGameServerZonesDelagate);
var serverZones = new List<string> { "local" } 
var future = Runtime.Player().Party.SetPreferredGameServerZonesV1(serverZones);
{
	"requestId": 8,
	"type": "PartyRpc.SetPreferredGameServerZonesV1Request",
	"payload": {
		"preferredGameServerZones": ["us-west"]
	}
}

Functions #

The onAddPlayer function is used for the following calls which add players to a party: JoinWithInviteCode and JoinWithPartyId.

suspend fun onAddPlayer(requestExt: ExtPlayerJoinRequest, playerToAdd: PartyPlayer, party: Party, partyConfig: PartyConfig)

The onRemovePlayers function is used for calls that remove players from the party: Leave and Kick.

  • This function is used when a player opts to leave a party and when a player is kicked; the RemovalReason includes which case. If a party is in matchmaking when either of these functions is called, the party is also removed from matchmaking.
suspend fun onRemovePlayer(party: Party, removedPlayer: PartyPlayer, removalReason: RemovalReason)

Making selections #

Players make selections that define both player and party selections.

Actions #

Update player selections using UpdatePlayerSelections. This takes the ExtUpdatePlayerSelectionsRequest parameter.

UPragmaPartyService::FOnCompleteDelegate UpdatePlayerSelectionsDelegate;
UpdatePlayerSelectionsDelegate.BindWeakLambda(this, [this](TPragmaResult<> Result)
{
});

Session->Party().UpdatePlayerSelectionsV1(FPragma_Party_ExtUpdatePlayerSelectionsRequest{}, UpdatePlayerSelectionsDelegate);
session.Party.UpdatePlayerSelectionsV1(new FPragma_Party_ExtUpdatePlayerSelectionsRequest());
{
  "requestId": 6,
  "type": "PartyRpc.UpdatePlayerSelectionsV1Request",
  "payload": {
    "requestExt": {}
  }
}

Update party selections using UpdatePartySelections. This takes the ExtUpdatePartySelectionsRequest parameter.

UPragmaPartyService::FOnCompleteDelegate UpdatePartySelectionsDelegate;
UpdatePartySelectionsDelegate.BindWeakLambda(this, [this](TPragmaResult<> Result)
{
});

Session->Party().UpdatePartyV1(FPragma_Party_ExtUpdatePartySelectionsRequest{}, UpdatePartySelectionsDelegate);
session.Party.UpdatePartySelectionsV1(new FPragma_Party_ExtUpdatePartySelectionsRequest());
{
  "requestId": 7,
  "type": "PartyRpc.UpdatePartySelectionsV1Request",
  "payload": {
    "requestExt": {}
  }
}

The following code block provides sample ext data.

Example: ext data
// request ext field that contains data about a party that can only be used on the server in plugins
message ExtHiddenPartyData {
}

// request ext field that contains data about a player that can only be used on the server in plugins
message ExtHiddenPlayerData {
}

// ext field for party state
message ExtPartySelections {
  bool can_queue_ranked = 1;
  party.GameMode game_mode = 2;
  party.AdventureModeDifficulty adventure_mode_difficulty = 3;
}

// ext field for player selection state within a party
message ExtPlayerSelections {
  pragma.inventory.ext.Rank current_rank = 1;
  string visible_hero_id = 2; // if your hero selection is visible, show it here
  int64 team_number = 3;
  string skin_catalog_id = 4;
}

// ext field for private player selection state within a party
message ExtPrivatePlayerSelections {
  string desired_hero_id = 1; // which hero you have picked
}

// request ext field for creating a party
message ExtCreateRequest {
}

// request ext field for adding a player to a party
message ExtPlayerJoinRequest {
}

// request ext field for updating a player selection
message ExtUpdatePlayerSelectionsRequest {
  oneof update {
    string new_hero_id = 1;
    party.ChosenTeam chosen_team = 2;
    string new_skin_catalog_id = 3;
  }
}

// request ext field for updating a party selection
message ExtUpdatePartySelectionsRequest {
  oneof update {
    party.GameMode new_game_mode = 1;
    party.AdventureModeDifficulty adventure_mode_difficulty = 2;
  }
}

Functions #

The updatePlayer function allows players to make individual choices like characters, skins, or votes for maps using the ExtUpdatePlayerSelectionsRequest.

suspend fun updatePlayer(
 playerToUpdate: PartyPlayer, requestExt: ExtUpdatePlayerSelectionsRequest, party: Party, partyConfig: PartyConfig
)

The updateParty function allows players to set shared information like game mode, map, or difficulty using the ExtUpdatePartySelectionsRequest.

  • This call can be restricted to leader-only by writing the plugin implementation to reject changes based on roles.
suspend fun updateParty(
 requestingPlayer: PartyPlayer, requestExt: ExtUpdatePartySelectionsRequest, party: Party, partyConfig: PartyConfig
)

Setting ready states #

Determining whether players are ready to enter matchmaking.

Actions #

If players can set their ready state, they should use SetReadyState which contains the ready boolean.

UPragmaPartyService::FOnCompleteDelegate SetPartyReadyDelegate;
SetPartyReadyDelegate.BindWeakLambda(this, [this](TPragmaResult<> Result)
{
});

Session->Party().SetReadyStateV1(true, SetPartyReadyDelegate);
session.Party.SetReadyStateV1(true);
{
  "requestId": 5,
  "type": "PartyRpc.SetReadyStateV1Request",
  "payload": {
    "ready": "true"
  }
}

Functions #

The canChangeReady function determines whether players are able to set their own ready state.

suspend fun canChangeReady(player: PartyPlayer, newReadyState: Boolean, party: Party, partyConfig: PartyConfig): Boolean

Entering matchmaking #

Entering matchmaking and sending custom match data.

Actions #

To enter matchmaking, the leader can send StartMatchmaking.

  • If a player without the leader role attempts to make this call, they receive the PlayerNotLeader error.
  • If any player has not set themselves to ready, the leader who called StartMatchmaking receives the PlayersNotReady error.
UPragmaPartyService::FOnCompleteDelegate StartMatchmakingDelegate;
StartMatchmakingDelegate.BindWeakLambda(this, [this](TPragmaResult<> Result)
{
});

Session->Party().StartMatchmakingV1(StartMatchmakingDelegate);
session.Party.StartMatchmakingV1();
{
  "requestId": 8,
  "type": "PartyRpc.StartMatchmakingV1Request",
  "payload": {
  }
}

Functions #

Once the StartMatchmaking call is made by the party leader, the buildMatchmakingKey and buildExtEnterMatchmaking functions are called by the plugin. These allow for customization of matchmaking keys and any other custom matchmaking content.

suspend fun buildMatchmakingKey(party: Party): ExtMatchmakingKey
suspend fun buildExtEnterMatchmakingV2Request(party: Party): ExtEnterMatchmakingV2Request

Handling match end #

Dealing with expected and unexpected match exit scenarios.

Functions #

The returnFromMatchmaking and returnFromMatch methods provide two ways to handle parties leaving matchmaking or leaving a match.

  • By default, no selections or relevant party fields are changed when a match ends or a party leaves matchmaking.
suspend fun returnFromMatchmaking(party: Party, returningPlayers: List<PartyPlayer>, config: PartyConfig)
suspend fun returnFromMatch(party: Party, returningPlayers: List<PartyPlayer>, config: PartyConfig)
Example: Character selection

You may require players to reselect a character after completing a match to encourage diverse character pools. This would use the returnFromMatch method.

However, if the party leaves matchmaking due to a player disconnecting, you may want to retain character selections so players don’t need to reselect their character. This would use the returnFromMatchmaking method.