# Client Relayed Multiplayer

**URL:** https://heroiclabs.com/docs/nakama/concepts/multiplayer/relayed/
**Summary:** The real-time multiplayer engine makes it easy for users to set up and join matches where they can rapidly exchange data with opponents. Users can create, join, and leave matches with messages sent from clients, with matches existing on the server until their last participant has left.
**Keywords:** create match, join match, list opponents, send data, send packets, receive data, receive packets, relayed, matches, leave match
**Categories:** nakama, relayed, multiplayer

---


# Client Relayed Multiplayer

The real-time multiplayer engine makes it easy for users to set up and join matches where they can rapidly exchange data with opponents. This relayed multiplayer (also known as client-authoritative) model is suitable for many game types, such as simple 1vs1 or co-op games, where authoritative control on the server is not important (e.g. cheating is a non-factor).

In relayed multiplayer, Nakama facilitates the exchange of data regardless of message size or content. Any data sent through a match is immediately routed to the client requested match opponents. The only **match data maintained by Nakama** are the **match ID and list of presences** in that match.

Client messages destined for the other connected clients are forwarded by the server without inspection. Since Nakama does not track the amount or content of the data forwarded in a relayed multiplayer match, there is no cheat detection, error correction, or other such functionality available. This approach relies on one client in each match, decided upon by the clients themselves, to act as the [host](#match-host-rotation). This host will reconcile state changes between peers and perform arbitration on ambiguous or malicious messages sent from bad clients.

Any user can participate in matches with other users, there is no ability to password protect or otherwise restrict access to a match. There is no explicit limit on the number of players in a match, only the practical limits imposed by your game design (the message size and frequency) and available resources (server hardware and network capability).

Users can [create](#create-a-match), [join](#join-a-match), and [leave](#leave-a-match) matches with messages sent from clients. 

Matches exists on the server only until the last participant has left. They are kept in-memory and cannot be persisted.

## Create a match

A new match can be created by any user by explicitly calling the "match create" operation. No match state can be provided by the creator when creating a match. 
 
The server will assign a unique ID to the new match. This ID can be shared with other users for them to [join the match](#join-a-match).

{{< code type="client" >}}
```javascript
var response = await socket.createMatch();
console.log("Created match with ID:", response.match.match_id);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var match = await socket.CreateMatchAsync();
Console.WriteLine("New match with id '{0}'.", match.Id);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
rtClient->createMatch([](const NMatch& match)
{
    std::cout << "Created Match with ID: " << match.matchId << std::endl;
});
```
{{< / code >}}

{{< code type="client" >}}
```java
Match match = socket.createMatch().get();
System.out.format("Created match with ID %s.", match.getId());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var created_match : NakamaRTAPI.Match = yield(socket.create_match_async(), "completed")

if created_match.is_exception():
    print("An error occurred: %s" % created_match)
    return

print("New match with id %s.", created_match.match_id)
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var created_match : NakamaRTAPI.Match = await socket.create_match_async()

if created_match.is_exception():
    print("An error occurred: %s" % created_match)
    return

print("New match with id %s.", created_match.match_id)
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local result = socket.match_create()

if result.error then
  print(result.error.message)
  return
end

print("Created match with ID", result.match.match_id)
```
{{< / code >}}

{{< missing type="client" lang="bash" />}}
{{< missing type="client" lang="shell" />}}

Users can optionally provide a `name` when creating a new match. This name is used to generate the match ID, meaning two players creating matches with the same name will end up with identical match IDs and, as a result, in the same match.

{{< code type="client" >}}
```csharp
var matchName = "Heroes";
var match = await socket.CreateMatchAsync(matchName);
```
{{< / code >}}

{{< code type="client" >}}
```swift
let matchName = "Heroes"
let match = try await socket.createMatch()
```
{{< / code >}}

{{< code type="client" >}}
```dart
const matchName = 'Heroes';
final match = await socket.createMatch();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var match_name = "Heroes";
var match : NakamaRTAPI.Match = yield(socket.create_match_async(matchName), "completed")
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var match_name = "Heroes";
var match : NakamaRTAPI.Match = await socket.create_match_async(matchName)
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local match_name = "Heroes"
local result = socket.match_create(match_name)
```
{{< / code >}}

{{< code type="client" >}}
```js
var matchName = "NoImpostersAllowed";
var match = await socket.createMatch(matchName);
```
{{< / code >}}

{{< missing type="client" lang="cpp" />}}
{{< missing type="client" lang="java" />}}
{{< missing type="client" lang="bash" />}}
{{< missing type="client" lang="shell" />}}

A user can [leave a match](#leave-a-match) at any point, which will notify all other users. When all users have left the match it ceases to exist.

## Join a match

A user can join a specific match using its ID. Matches cannot be password protected or otherwise closed, if a user has the match ID then they are able to join. Matches can be joined at any point until the last participant leaves.

{{< code type="client" >}}
```javascript
var id = "<matchid>";
var match = await socket.joinMatch(id);

var connectedOpponents = match.presences.filter((presence) => {
  // Remove your own user from list.
  return presence.user_id != match.self.user_id;
});

connectedOpponents.forEach((opponent) => {
  console.log("User id %o, username %o.", opponent.user_id, opponent.username);
});
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var matchId = "<matchid>";
var match = await socket.JoinMatchAsync(matchId);

foreach (var presence in match.Presences)
{
    Console.WriteLine("User id '{0}' name '{1}'.", presence.UserId, presence.Username);
}
```
{{< / code >}}

{{< code type="client" >}}
```swift
let matchId = "<matchid>"
let match = try await socket.joinMatch(matchId: matchId)

for presence in match.presences {
    print("User id \(presence.userID) name \(presence.username).")
}
```
{{< / code >}}

{{< code type="client" >}}
```dart
const matchId = '<matchid>';
final match = await socket.joinMatch(matchId);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
string matchId = "<matchid>";
rtClient->joinMatch(matchId, {}, [](const NMatch& match)
{
    std::cout << "Joined Match!" << std::endl;

    for (auto& presence : match.presences)
    {
        if (presence.userId != match.self.userId)
        {
            std::cout << "User id " << presence.userId << " username " << presence.username << std::endl;
        }
    }
});
```
{{< / code >}}

{{< code type="client" >}}
```java
String matchId = "<matchid>";
Match match = socket.joinMatch(matchId).get();

for (UserPresence presence : match.getPresences()) {
    System.out.format("User id %s name %s.", presence.getUserId(), presence.getUsername());
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var match_id = "<matchid>"
var joined_match = yield(socket.join_match_async(match_id), "completed")

if joined_match.is_exception():
    print("An error occurred: %s" % joined_match)
    return

for presence in joined_match.presences:
    print("User id %s name %s'." % [presence.user_id, presence.username])
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var match_id = "<matchid>"
var joined_match = await socket.join_match_async(match_id)

if joined_match.is_exception():
    print("An error occurred: %s" % joined_match)
    return

for presence in joined_match.presences:
    print("User id %s name %s'." % [presence.user_id, presence.username])
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local match_id = "<matchid>"
local result = socket.match_join(match_id)

if result.error then
  print(result.error.message)
  return
end

for _,user in  ipairs(result.match.presences) do
  print("User id", user.user_id, "name", user.name)
end
```
{{< / code >}}

{{< missing type="client" lang="bash" />}}
{{< missing type="client" lang="shell" />}}

Upon joining a match, a list of match opponents is returned in the success callback. Keep in mind this list might not include all users, it contains users who are connected to the match _at that point in time_.

## List opponents

When a user creates or joins a new match they receive an initial list of connected opponents. After this initial list, the server pushes events to connected clients with the match joins and match leaves that occur - if no changes to the presence list occur then no server updates are sent. Events are batched for efficiency, meaning any event can contain multiple joins and/or leaves.
 
These events can be used to update the list of connected opponents so your players can see an accurate view of all match participants.

{{< code type="client" >}}
```javascript
var connectedOpponents = [];
socket.onmatchpresence = (presences) => {
  // Remove all users who left.
  connectedOpponents = connectedOpponents.filter(function(co) {
    var stillConnectedOpponent = true;

    presences.leaves.forEach((leftOpponent) => {
      if (leftOpponent.user_id == co.user_id) {
        stillConnectedOpponent = false;
      }
    });

    return stillConnectedOpponent;
  });

  // Add all users who joined.
  connectedOpponents = connectedOpponents.concat(presences.joins);
};
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var connectedOpponents = new List<IUserPresence>(2);

socket.ReceivedMatchPresence += presenceEvent =>
{
    foreach (var presence in presenceEvent.Leaves)
    {
        connectedOpponents.Remove(presence);
    }

    connectedOpponents.AddRange(presenceEvent.Joins);

    // Remove yourself from connected opponents.
    connectedOpponents.Remove(self);
    Console.WriteLine("Connected opponents: [{0}]", string.Join(",\n  ", connectedOpponents));
};
```
{{< / code >}}

{{< code type="client" >}}
```swift
var connectedOpponents = [UserPresence]()
socket.onMatchPresence = { presenceEvent in
    connectedOpponents.removeAll { presence in presenceEvent.leaves.contains { $0.userId == presence.userId } }
    connectedOpponents.append(contentsOf: presenceEvent.joins.map { $0.toUserPresence() })
}
```
{{< / code >}}

{{< code type="client" >}}
```dart
final List<UserPresence> connectedOpponents = [];
socket.onMatchPresence.listen((event) {
  connectedOpponents.removeWhere((opponent) => event.leaves.any((leave) => leave.userId == opponent.userId));
  connectedOpponents.addAll(event.joins);
});
```
{{< / code >}}

{{< code type="client" >}}
```cpp
rtListener->setMatchPresenceCallback([](const NMatchPresenceEvent& event)
{
    for (auto& presence : event.joins)
    {
        std::cout << "Joined user: " << presence.username << std::endl;
    }

    for (auto& presence : event.leaves)
    {
        std::cout << "Left user: " << presence.username << std::endl;
    }
});
```
{{< / code >}}

{{< code type="client" >}}
```java
List<UserPresence> connectedOpponents = new ArrayList<UserPresence>();

public void onMatchPresence(final MatchPresenceEvent matchPresence) {
    connectedOpponents.addAll(matchPresence.getJoins());

    for (UserPresence leave : matchPresence.getLeaves()) {
        for (int i = 0; i < connectedOpponents.size(); i++) {
            if (connectedOpponents.get(i).getUserId().equals(leave.getUserId())) {
                connectedOpponents.remove(i);
            }
        }
    };
});
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var connected_opponents = {}

func _ready():
    # First, setup the socket as explained in the authentication section.
    socket.connect("received_match_presence", self, "_on_match_presence")

func _on_match_presence(p_presence : NakamaRTAPI.MatchPresenceEvent):
    for p in p_presence.joins:
        connected_opponents[p.user_id] = p

    for p in p_presence.leaves:
        connected_opponents.erase(p.user_id)

    print("Connected opponents: %s" % [connected_opponents])
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var connected_opponents = {}

func _ready():
    # First, setup the socket as explained in the authentication section.
    socket.received_match_presence.connect(self._on_match_presence)

func _on_match_presence(p_presence : NakamaRTAPI.MatchPresenceEvent):
    for p in p_presence.joins:
        connected_opponents[p.user_id] = p

    for p in p_presence.leaves:
        connected_opponents.erase(p.user_id)

    print("Connected opponents: %s" % [connected_opponents])
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local connected_opponents = {}
socket.on_matchpresence(function(message)
  for _,p in ipairs(message.match_presence_event.leaves) do
    connected_opponents[p.user_id] = nil
  end

  for _,p in ipairs(message.match_presence_event.joins) do
    connected_opponents[p.user_id] = p
  end
end)
```
{{< / code >}}

{{< missing type="client" lang="bash" />}}
{{< missing type="client" lang="shell" />}}

Some best practices to keep in mind for listing opponents:

* Register a client-side presence event listener _before_ joining or creating a match
* For batched events with both a join and leave for the same presence, process the leave then join for presences already in the list and process the join then leave for presences not in the list 

## Send data messages

A user in a match can send data messages which will be received by all other opponents. These messages are streamed in real-time to the destined clients and can contain any binary content. Nakama broadcasts messages **in the order received**, not necessarily in the order they are sent.

The binary content in each data message should be as **small as possible** within the maximum transmission unit (MTU) of `1500` bytes. It is common to use JSON and preferable to use a compact binary format like [Protocol Buffers](https://developers.google.com/protocol-buffers/) or [FlatBuffers](https://google.github.io/flatbuffers/). 

When further reducing the message size and/or frequency is not possible, it is best to prioritize sending **fewer messages**. For example, 1 message of `1000` bytes per second is better than 5 messages of `200` bytes per second.

To identify each message as a specific "command" it contains an [Op code](#op-codes) as well as the payload.

{{< code type="client" >}}
```javascript
var id = "<matchid>";
var opCode = 1;
var data = { "move": {"dir": "left", "steps": 4} };
socket.sendMatchState(id, opCode, data);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
// using Nakama.TinyJson;
var matchId = "<matchid>";
var opCode = 1;
var newState = new Dictionary<string, string> {{"hello", "world"}}.ToJson();
socket.SendMatchStateAsync(matchId, opCode, newState);
```
{{< / code >}}

{{< code type="client" >}}
```swift
let matchId = "<matchid>"
let opCode = 1
var newState = try JSONSerialization.data(withJSONObject: ["hello": "world"])
try await socket.sendMatchData(matchId: matchId, opCode: opCode, data: newState)
```
{{< / code >}}

{{< code type="client" >}}
```dart
socket = NakamaWebsocketClient.instance;
const matchId = '<matchid>';
const opCode = 1;
final newState = json.encode({'hello': 'world'});
socket.sendMatchData(
  matchId: matchId,
  opCode: Int64(opCode),
  data: utf8.encode(newState),
);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
string id = "<matchid>";
int64_t opCode = 1;
NBytes data = "{ \"move\": {\"dir\": \"left\", \"steps\" : 4} }";
rtClient->sendMatchData(id, opCode, data);
```
{{< / code >}}

{{< code type="client" >}}
```java
String id = "<matchid>";
int opCode = 1;
String data = "{\"message\":\"Hello world\"}";
socket.sendMatchData(id, opCode, data);
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var match_id = "<matchid>"
var op_code = 1
var new_state = {"hello": "world"}
socket.send_match_state_async(match_id, op_code, JSON.print(new_state))
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var match_id = "<matchid>"
var op_code = 1
var new_state = {"hello": "world"}
socket.send_match_state_async(match_id, op_code, JSON.stringify(new_state))
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local match_id = "<matchid>"
local op_code = 1

local data = json.encode({
  move = {
    dir = "left",
    steps = 4
  }
})

local result = socket.match_data_send(match_id, op_code, data)

if result.error then
  print(result.error.message)
  return
end
```
{{< / code >}}

{{< missing type="client" lang="bash" />}}
{{< missing type="client" lang="shell" />}}

While messages are broadcast to all other match presences by default, users can optionally specify a desired subset of the match participants (i.e. their friends, teammates, etc.) to receive the message exclusively.

{{< code type="client" >}}
```javascript
// Only send data to the first presence in the match presences array
socket.sendMatchState(id, opCode, data, [match.presences[0]]);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
// Only send data to the first presence in the match presences array
await socket.SendMatchStateAsync(matchId, opCode, newState, new [] { match.presences.First() });
```
{{< / code >}}

{{< code type="client" >}}
```swift
// Only send data to the first presence in the match presences array
try await socket.sendMatchData(matchId: matchId, opCode: opCode, data: newState, presences: [match.presences[0]])
```
{{< / code >}}

{{< code type="client" >}}
```dart
// Only send data to the first presence in the match presences array
socket.sendMatchData(
  matchId: matchId,
  opCode: opCode,
  data: utf8.encode(newState),
  presences: [matchPresences[0]],
);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
// Only send data to the first presence in the match presences array
rtClient->sendMatchData(id, opCode, data, { match.presences[0] });
```
{{< / code >}}

{{< code type="client" >}}
```java
// Only send data to the first presence in the match presences array
socket.sendMatchData(id, opCode, data, match.getPresences().get(0));
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
// Only send data to the first presence in the match presences array
socket.send_match_state_async(match_id, op_code, JSON.print(new_state), [current_match.presences[0]])
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
// Only send data to the first presence in the match presences array
socket.send_match_state_async(match_id, op_code, JSON.stringify(new_state), [current_match.presences[0]])
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
-- Only send data to the first presence in the match presences array
local result = socket.match_data_send(match_id, op_code, data, { match.presences[0] })
```
{{< / code >}}

{{< missing type="client" lang="bash" />}}
{{< missing type="client" lang="shell" />}}

### Op codes

An op code is a numeric identifier for the type of message sent. Op codes can provide insight into the purpose and content of a message before you decode it. 

They can be used to define commands within the gameplay which belong to certain user actions, such as:

* Initial state synchronization
* Ready status
* Ping / Pong
* Game state update
* Emote

See the [Fish Game tutorial](../../../tutorials/unity/fishgame/#operation-codes) for an example implementation.

## Receive data messages

The server delivers data in the order it processes data messages from clients. A client can add a callback for incoming match data messages. This should be done before they create (or join) and leave a match.

{{< code type="client" >}}
```javascript
socket.onmatchdata = (result) => {
  var content = result.data;

  switch (result.op_code) {
    case 101:
      console.log("A custom opcode.");
      break;
    default:
      console.log("User %o sent %o", result.presence.user_id, content);
  }
};
```
{{< / code >}}

{{< code type="client" >}}
```csharp
// Use whatever decoder for your message contents.
var enc = System.Text.Encoding.UTF8;
socket.ReceivedMatchState += newState =>
{
    var content = enc.GetString(newState.State);

    switch (newState.OpCode)
    {
        case 101:
            Console.WriteLine("A custom opcode.");
            break;
        default:
            Console.WriteLine("User '{0}'' sent '{1}'", newState.UserPresence.Username, content);
    }
};
```
{{< / code >}}

{{< code type="client" >}}
```swift
socket.onMatchData = { matchData in
    let content = String(data: matchData.data, encoding: .utf8) ?? ""
    switch matchData.opCode {
    case 101:
        print("A custom opcode.")
    default:
        print("User \(matchData.presence.userID) sent \(content)")
    }
}
```
{{< / code >}}

{{< code type="client" >}}
```dart
socket.onMatchData.listen((data) {
  final content = utf8.decode(data.data);
  switch (data.opCode) {
    case 101:
      print('A custom opcode.');
    default:
      print('User ${data.presence.userId} sent $content');
  }
});
```
{{< / code >}}

{{< code type="client" >}}
```cpp
rtListener->setMatchDataCallback([](const NMatchData& data)
{
    switch (data.opCode)
    {
        case 101:
            std::cout << "A custom opcode." << std::endl;
            break;
        default:
            std::cout << "User " << data.presence.userId << " sent " << data.data << std::endl;
            break;
    }
});
```
{{< / code >}}

{{< code type="client" >}}
```java
SocketListener listener = new AbstractSocketListener() {
    @Override
    public void onMatchData(final MatchData matchData) {
        System.out.format("Received match data %s with opcode %d", matchData.getData(), matchData.getOpCode());
    }
};
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
func _ready():
    # First, setup the socket as explained in the authentication section.
    socket.connect("received_match_state", self, "_on_match_state")

func _on_match_state(p_state : NakamaRTAPI.MatchData):
    print("Received match state with opcode %s, data %s" % [p_state.op_code, parse_json(p_state.data)])
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
func _ready():
    # First, setup the socket as explained in the authentication section.
    socket.received_match_state.connect(self._on_match_state)

func _on_match_state(p_state : NakamaRTAPI.MatchData):
    print("Received match state with opcode %s, data %s" % [p_state.op_code, JSON.parse_string(p_state.data)])
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
socket.on_matchdata(function(message)
  local data = json.decode(message.match_data.data)
  local op_code = tonumber(message.match_data.op_code)
end)
```
{{< / code >}}

{{< missing type="client" lang="bash" />}}
{{< missing type="client" lang="shell" />}}

## Leave a match

Users can leave a match at any point. This may occur voluntarily via client action (quitting the match) or involuntarily (e.g. network connectivity), but in either case it must be accounted for and handled appropriately in your game logic.

{{< code type="client" >}}
```javascript
var id = "<matchid>";
socket.leaveMatch(id);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var matchId = "<matchid>";
await socket.LeaveMatchAsync(matchId);
```
{{< / code >}}

{{< code type="client" >}}
```swift
let matchId = "<matchid>"
try await socket.leaveMatch(matchId: matchId)
```
{{< / code >}}

{{< code type="client" >}}
```dart
const matchId = '<matchid>';
await socket.leaveMatch(matchId);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
string matchId = "<matchid>";
rtClient->leaveMatch(matchId);
```
{{< / code >}}

{{< code type="client" >}}
```java
String matchId = "<matchid>";
socket.leaveMatch(matchId).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var match_id = "<matchid>"
var leave : NakamaAsyncResult = yield(socket.leave_match_async(match_id), "completed")
if leave.is_exception():
    print("An error occurred: %s" % leave)
    return
print("Match left")
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var match_id = "<matchid>"
var leave : NakamaAsyncResult = await socket.leave_match_async(match_id)
if leave.is_exception():
    print("An error occurred: %s" % leave)
    return
print("Match left")
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local match_id = "<matchid>"
local result = socket.match_leave(match_id)
if result.error then
  print(result.error.message)
  return
end
```
{{< / code >}}

{{< missing type="client" lang="bash" />}}
{{< missing type="client" lang="shell" />}}

A match ends when all users have left. At that time it's ID becomes invalid and cannot be re-used to join again.

## Examples

### Match host rotation

You must determine how the match host will be selected from among the connected clients. This is best implemented without requiring any "negotiation" between the match participants while still ensuring that all clients acknowledge the same host.

You can accomplish this by deterministically sorting the match presences and selecting a host based on any desired factor. In the example below, we sort the presence list and select the lowest indexed session ID as the host:

{{< code type="client" >}}
```javascript
// Declare a variable to store which presence is the host
var hostPresence;

// Upon receiving a matchmaker matched event, deterministically calculate the host by sorting the session Ids
socket.onmatchmakermatched = (matchmakerMatched) => {
  var hostSessionId = matchmakerMatched.users.map(user => user.presence.session_id).sort();
  hostPresence = matchmakerMatched.users.filter(user => user.presence.session_id == hostSessionId)[0];
};

// When receiving a match presence event, check if the host left and if so recalculate the host presence
socket.onmatchpresence = (matchPresence) => {
  if (matchPresence.leaves.find(presence => presence.user_id === hostPresence.user_id))
  {
    var hostSessionId = match.presences.map(presence => presence.session_id).sort();
    hostPresence = match.presences.filter(presence => presence.session_id === hostSessionId)[0];
  }
};
```
{{< / code >}}

{{< code type="client" >}}
```csharp
// Declare a variable to store which presence is the host
IUserPresence hostPresence;

// Upon receiving a matchmaker matched event, deterministically calculate the host by sorting the session Ids
socket.ReceivedMatchmakerMatched += matched =>
{
  hostPresence = matched.Users.OrderBy(x => x.Presence.SessionId).First().Presence;
};

// When receiving a match presence event, check if the host left and if so recalculate the host presence
socket.ReceivedMatchPresence += matchPresenceEvent =>
{
  if (matchPresenceEvent.Leaves.Any(x => x.UserId == hostPresence.UserId))
  {
    hostPresence = match.Presences.OrderBy(x => x.SessionId).First();
  }
};
```
{{< / code >}}

{{< code type="client" >}}
```swift
var hostPresence: UserPresence?

// Upon receiving a matchmaker matched event, deterministically calculate the host by sorting the session Ids
socket.onMatchmakerMatched = { matched in
    let hostSessionId = matched.users.map { $0.presence.sessionID }.sorted().first!
    hostPresence = matched.users.first { $0.presence.sessionID == hostSessionId }!.presence.toUserPresence()
}
let match = try await socket.createMatch()
// When receiving a match presence event, check if the host left and if so recalculate the host presence
socket.onMatchPresence = { matchPresence in
    if matchPresence.leaves.contains(where: { $0.userID == hostPresence!.userId }) {
        let hostSessionId = match.presences.map { $0.sessionID }.sorted().first!
        hostPresence = match.presences.first { $0.sessionID == hostSessionId }?.toUserPresence()
  }
}
```
{{< / code >}}

{{< code type="client" >}}
```dart
// Declare a variable to store which presence is the host
UserPresence hostPresence;

// Upon receiving a matchmaker matched event, deterministically calculate the host by sorting the session Ids
socket.onMatchmakerMatched.listen((matchmakerMatched) {
  List<String> hostSessionIds = matchmakerMatched.users.map((user) => user.presence.sessionId).toList()..sort();
  hostPresence = matchmakerMatched.users.firstWhere((user) => user.presence.sessionId == hostSessionIds.first).presence;
});

// When receiving a match presence event, check if the host left and if so recalculate the host presence
socket.onMatchPresence.listen((matchPresence) {
  if (matchPresence.leaves.any((presence) => presence.userId == hostPresence.userId)) {
    final List<String> hostSessionIds = match.presences.map((presence) => presence.sessionId).toList()..sort();
    hostPresence = match.presences.firstWhere((presence) => presence.sessionId == hostSessionIds.first);
  }
});
```
{{< / code >}}

{{< code type="client" >}}
```cpp
// Declare a variable to store which presence is the host
NUserPresence hostPresence;

// Upon receiving a matchmaker matched event, deterministically calculate the host by sorting the session Ids
listener.setMatchmakerMatchedCallback([&done, &hostPresence](NMatchmakerMatchedPtr matchmakerMatched) {
    std::sort(matchmakerMatched->users.begin(), matchmakerMatched->users.end(), [](const NMatchmakerUser lhs, const NMatchmakerUser rhs) {
        return lhs.presence.sessionId < rhs.presence.sessionId;
    });
    hostPresence = matchmakerMatched->users[0].presence;
});

// When receiving a match presence event, check if the host left and if so recalculate the host presence
listener.setMatchPresenceCallback([&match, &hostPresence](NMatchPresenceEvent matchPresenceEvent) {
    for (int i = 0; i < matchPresenceEvent.leaves.size(); i++) {
        if (matchPresenceEvent.leaves[i].sessionId == hostPresence.sessionId) {
            std::sort(match.presences.begin(), match.presences.end(), [](const NUserPresence lhs, const NUserPresence rhs) {
                return lhs.sessionId < rhs.sessionId;
            });
            hostPresence = match.presences[0];
        }
    }
});
```
{{< / code >}}

{{< code type="client" >}}
```java
// Declare a variable to store which presence is the host (as a final 1 length array so we can access it correctly)
final UserPresence[] hostPresence = new UserPresence[1];

SocketListener socketListener = new SocketListener() {
  // Upon receiving a matchmaker matched event, deterministically calculate the host by sorting the session Ids
  @Override
  public void onMatchmakerMatched(MatchmakerMatched matchmakerMatched) {
    List<MatchmakerUser> users = matchmakerMatched.getUsers();
    users.sort((a, b) -> String.CASE_INSENSITIVE_ORDER.compare(a.getPresence().getSessionId(), b.getPresence().getSessionId()));
    hostPresence[0] = users.get(0).getPresence();
  }

  // When receiving a match presence event, check if the host left and if so recalculate the host presence
  @Override
  public void onMatchPresence(MatchPresenceEvent e) {
    if (e.getLeaves() != null) {
      e.getLeaves().forEach(presence -> {
        if (presence.getSessionId() == hostPresence[0].getSessionId()) {
          List<UserPresence> matchPresences = match.getPresences();
          matchPresences.sort((a, b) -> String.CASE_INSENSITIVE_ORDER.compare(a.getSessionId(), b.getSessionId()));
          hostPresence[0] = matchPresences.get(0);
        }
      });
    }
  }
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
# Declare a variable to store which presence is the host
var host_presence : NakamaRTAPI.UserPresence

# Define comparer functions
func _presence_comparer(a : NakamaRTAPI.UserPresence, b : NakamaRTAPI.UserPresence):
	return a.session_id < b.session_id

func _user_comparer(a : NakamaRTAPI.MatchmakerUser, b : NakamaRTAPI.MatchmakerUser):
	return a.presence.session_id < b.presence.session_id

# Upon receiving a matchmaker matched event, deterministically calculate the host by sorting the session Ids
func _on_matchmaker_matched(matchmaker_matched : NakamaRTAPI.MatchmakerMatched):
	matchmaker_matched.users.sort_custom(self, "_user_comparer")
	host_presence = matchmaker_matched.users[0].presence
	current_match = yield(socket.join_match_async(matchmaker_matched.match_id), "completed")

# When receiving a match presence event, check if the host left and if so recalculate the host presence
func _on_match_presence(match_presence_event : NakamaRTAPI.MatchPresenceEvent):
	for presence in match_presence_event.leaves:
		if presence.session_id == host_presence.session_id:
			current_match.presences.sort_custom(self, "_presence_comparer")
			if len(current_match.presences) < 1:
				host_presence = current_match.self_user
			else:
				host_presence = current_match.presences[0]
```
{{< / code >}}

{{{< code type="client" framework="godot4" >}}
```gdscript
# Declare a variable to store which presence is the host
var host_presence : NakamaRTAPI.UserPresence

# Define comparer functions
func _presence_comparer(a : NakamaRTAPI.UserPresence, b : NakamaRTAPI.UserPresence):
	return a.session_id < b.session_id

func _user_comparer(a : NakamaRTAPI.MatchmakerUser, b : NakamaRTAPI.MatchmakerUser):
	return a.presence.session_id < b.presence.session_id

# Upon receiving a matchmaker matched event, deterministically calculate the host by sorting the session Ids
func _on_matchmaker_matched(matchmaker_matched : NakamaRTAPI.MatchmakerMatched):
	matchmaker_matched.users.sort_custom(self._user_comparer)
	host_presence = matchmaker_matched.users[0].presence
	current_match = await socket.join_match_async(matchmaker_matched.match_id)

# When receiving a match presence event, check if the host left and if so recalculate the host presence
func _on_match_presence(match_presence_event : NakamaRTAPI.MatchPresenceEvent):
	for presence in match_presence_event.leaves:
		if presence.session_id == host_presence.session_id:
			current_match.presences.sort_custom(self._presence_comparer)
			if len(current_match.presences) < 1:
				host_presence = current_match.self_user
			else:
				host_presence = current_match.presences[0]
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua

local host = nil

-- Upon receiving a matchmaker matched event, deterministically calculate the host by sorting the session Ids
socket.on_matchmaker_matched(function(matched)
    table.sort(matched.users, function(a, b) return a.presence.session_id < b.presence.session_id end)
    host = matched.users[1]
end)

-- When receiving a match presence event, check if the host left and if so recalculate the host presence
socket.on_match_presence_event(function(presence)
    for i,presence in ipairs(presence.leaves) do
        if presence.session_id == host.session_id then
            table.sort(match.presences, function(a, b) return a.session_id < b.session_id end)
            host = match.presences[1]
        end
    end
end)
```
{{< / code >}}

{{< missing type="client" lang="bash" />}}
{{< missing type="client" lang="shell" />}}
