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
#
property | description |
---|---|
partyId | unique identifier for the party |
maxPlayerCount | number representing the maximum amount of players for the party set via the PartyService config |
disableMaxPlayerCount | boolean indicating if the automatic check for maximum player count is disabled or not, set via the PartyService config |
gameServerVersion | computed game server version based on the PartyPlayer s in the party, updated whenever a player joins or leaves the party |
preferredGameServerZones | list of game server zones that the party can play a match on |
inviteCode | invite code for the party that players can use to join |
overrideGameServerVersion | boolean indicating if the Game Server Version Compatibility Plugin should validate this party or not, set via PartyService config |
partyMembersByPlayerId | map of all players in the party index by their playerId |
extPartySelections | ext proto of selections for the party |
extHiddenPartyData | ext proto of sensitive data for the party that is not sent to players’ game clients |
The PartyPlayer
Class
#
property | description |
---|---|
sessionKey | identification object including the player’s pragmaId , also known as their playerId |
displayName | object containing the player’s name and discriminator |
inventory | snapshot of the player’s inventory from when they entered the party or last completed a match |
gameClientVersion | version of the game client the player is currently running |
gameServerZoneToPing | map of this player’s ping to various game server zones |
leader | boolean indicating if this player is a leader in this party |
ready | boolean indicating if this player ready to play a match |
extPlayerSelections | ext proto of personal selections this player has made while in the party |
extPrivatePlayerSelections | ext proto of personal selections that can only be seen by this player |
extHiddenPlayerData | ext 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.
config | description |
---|---|
maxPlayersPerParty | maximum players allowed in each party |
disableMaxPlayerCount | whether to disable maximum player count limits in each party |
enableLocalGameServerVersions | whether to allow local game clients to override their game server version for local development purposes |
repeatInviteDelaySeconds | defines the delay between a player inviting another player consecutive times |
inventoryTagsToInclude | tags used to filter player inventories, and items that match one or more tags will be included |
enableTransferPartyLeader | whether 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.
RPC | section |
---|---|
Create | Creating a party |
SendInvite | Managing players |
RespondToInvite | Managing players |
JoinWithInviteCode | Managing players |
JoinWithPartyId | Managing players |
Leave | Managing players |
Kick | Managing players |
AssignPartyLeader | Managing players |
SetGameServerZoneToPing | Managing players |
SetPreferredGameServerZones | Managing players |
UpdatePlayerSelections | Making selections |
UpdatePartySelections | Making selections |
SetReadyState | Setting ready states |
StartMatchmaking | Entering 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
booleanis_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.
name | visibility | description | example |
---|---|---|---|
ExtPartySelections | party | designated by party leader and applies to entire party | map or game mode |
ExtHiddenPartyData | server | scoped to the entire party but hidden to all players | team win/loss streak tracker |
ExtPlayerSelections | party | individual player data broadcast to entire party | character/skin selections or map/game mode votes |
ExtPrivatePlayerSelections | player | individual player data invisible to the party | player deck selections in a collectible/trading card game |
ExtHiddenPlayerData | server | per-player data visible to the Party service and game servers only | hidden 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
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.
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 creationExtPlayerJoinRequest
: 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.
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 thePlayersNotReady
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)