Protocol¶
Workflows¶
General¶
Authentication¶
To authenticate, a client sends an AsyncAuthRequest to the server. The server can answer with:
AsyncConnectedRequest if the authentication is successful
AsyncConnectionErrorRequest if the authentication is not successful
When successful, the client will receive the full Player and a Session.
The client authenticates the end-user to the server with an Asmodee.net API OAuth2 access token.
Authentication with an OAuth2 access token¶
The client should authenticate beforehand through the Asmodee.net API, and request an OAuth2 access token.
Then the client will fill the name
field with the user login name, and the PartnerToken sessionToken
field with the OAuth2 access token.
Logging out¶
For the user to log out of the server, the client should send an AsyncDisconnectRequest. After this, all requests coming from the client will be rejected until the next AsyncAuthRequest.
The server keeps track of successful connections, by binding the device to the DoW account. When the player logs out, the DoW account is still linked to the device, so the server can send relevant push notifications. If the client logs in with a different DoW account, this account will be bound to the same device, along with the previous account. This means that a given device might receive notifications for a different user than the one currently logged in. Thus, the client should unlink the device when connecting with a different DoW account, by sending an AsyncUnlinkDeviceRequest.
Note
You should keep track on the device of the links between the DoW account and the Session (if known), so it is easy to check if the account changed or if the player just wants to reconnect with the same account.
Heartbeat¶
The client must send regularly PingRequest to signal the server it is still connected. The server will respond with the exact same PingRequest. This allows also the client to notice that the connection with the Scalable Server has been dropped (a TCP timeout can take a long time).
The PingRequest contains a timestamp
field where you can write the local timestamp. Since the server sends back the same request, in return you’ll get the same timestamp you sent, this allows to compute the time distance between the client and the server.
Current Games Information¶
It is possible at any time to get access to the list of games the Player is participating in. It is also possible to get information about only one game at a time.
To do so the client needs to send a WhatsNewPussycatRequest, to which the server will respond with a GameStatusReportRequest.
The returned status is a list (possibly of zero or one element) of StatusReport. These StatusReport contains all information about a given game, including (but not limited to):
Player list
Status
Game data, and/or summary game data
Active player
Player clocks
…
Invitation¶
The simplest way to create a game for a player is to create an Invitation. For a Player to create an invitation, the client sends an EngageGameWithFriendsRequest with the list of friends id (this list can’t be empty). It is possible to specify robots by adding the special id 0
to the list (a list containing only robots will result in an error).
Upon reception of that request, the server will send a GameCreatedRequest to the sender and all the invitees (if they’re not present a Push Notification will be sent). Note that the invitees have to accept or decline the invitation by sending back an AnswerInvitationRequest which is confirmed by the server with an InvitationAnsweredRequest. If any of the invitees declines the invitation the game is aborted (1), if all accept the invitation the game starts (2).
Note
It is considered best practice to send a SwitchedToGameRequest before positive acceptation in order to signal that the player is ready in the waiting room.
To quit an invitation there are two possibilities:
if the inviter wants to quit, she has to send a GameForfeitRequest and this will abort the whole invitation
invitees will have to decline the invitation by sending an InvitationAnsweredRequest with parameter
accept=false
Presence¶
To monitor presence of other players, the client needs to send their Asmodee.net player id with a RegisterPresenceRequest and subscribe to the presence stream with a SubscribePresenceServiceRequest.
Once subscribed, anytime the presence of one of the monitored player changes, an AsyncBuddyPresencePartialUpdateRequest is sent with the state change. Note that presence events are batched, so an AsyncBuddyPresencePartialUpdateRequest can contain more than one player presence information.
To be noted that while in a game, the opponents presence is automatically monitored, and their presence change is sent with PlayerPresenceUpdateRequest (there is no need to subscribe to the presence stream, but the client needs to switch to the correct game).
Switching to a Game¶
For in-game presence to work correctly, and also for synchronous games, it is necessary that the client sends a SwitchedToGameRequest whenever the client UI enters a specific game (for newly joined or created games, send it after receiving the GameCreatedRequest) or leaves completely a game. There is no response from the server, except that when switching into a game, the server will propagate this event in a PlayerPresenceUpdateRequest to the other players.
Buddy List¶
It is possible for a player to get the content of her Buddy and Ignored List, by sending an AsyncBuddyListRequest. In return the server will send back an AsyncBuddyListContentRequest containing the list of buddies.
To add a buddy, send an AsyncBuddyManagementRequest with the operation ADD
, and the Asmodee.net player id to add. On return the server will send back an AsyncBuddyAddedRequest.
To remove a buddy, follow the same workflow with the operation REMOVE
. The server will send back an AsyncBuddyRemovedRequest to confirm the operation.
Ignore List¶
As with the Buddy List, the client can manage the Player ignore list. To get access to the list, send an AsyncIgnoreListRequest, the server will return an AsyncIgnoreListContentRequest.
To manage the ignore list, send an AsyncIgnoreManagementRequest with the proper ADD
or REMOVE
operation, and the server will respond with either an AsyncIgnoreAddedRequest or an AsyncIgnoreRemovedRequest.
Server Statistics¶
It is possible to get some statistics the server keeps with AskServerStatisticsRequest, for instance to give an overview of the server state when entering the lobby: the number of games, the number of live players (connected), and the overall number of live players (connected and not connected).
The distinction between connected and not connected is subtle: we keep hosting live data for an unconnected player, waiting for her to connect back.
The server will respond a ServerStatisticsRequest with aforementioned data.
Note
The AskServerStatisticsRequest contains a subscribe
parameter: when false (or not provided) the server will respond once, if true then the client will regularly receive statistics updates (until you re-send the request with subscribe=false
).
Chat¶
Players can send chat messages from the lobby or from a game with a MulticastChatRequest.
To send a chat visible to all the players in the lobby, use MulticastChatRequest without specifying a game_id
(or with game_id = 0
).
To send a chat to all the players in a given game, use MulticastChatRequest and specify its game_id
.
If the chat message text triggers the anti-profanity filter several times in a row, the sender will be muted for a certain time, and the workflow becomes:
and no chat message is sent to the other players.
When sending chat requests you can also specify some players in the field recipient_ids
. This is useful if you want to filter who receives the message (for instance, to implement private messaging so players can share a private game’s password).
In the lobby, recipient_ids
must contain global ids. In a game, recipient_ids
must contain local ids.
If
recipient_ids
is empty, the message will be sent to all players (of the lobby, or of the game, according to the context).If
recipient_ids
is filled, only these player ids will receive the message (and the sender will always receive it back).
Chat context |
game_id |
recipients_ids |
---|---|---|
Lobby |
none or 0 |
global ids |
In game |
the game id |
local ids (in the game) |
Note
It is possible to get the history of the previous chat messages by sending a GetChatHistoryRequest (by specifying a game_id
, or with id 0 / no id for the lobby). The server will send back a ClientChatHistoryRequest with the latest previously exchanged messages. Note that the chat history will never display private messages.
Chat codes¶
The server supports message codes
, useful for implementing custom messages like pre-made quickchat messages, commands, emojis, special animation in the lobby…
Just fill the code
integer parameter and it will be relayed. Please be aware that chat codes between 0 and 255 are reserved by the server and are not free to use. Provide a higher value.
The code 0 is the “dummy code” and will behave the same way as if no code was provided.
You also have to provide a non-empty text
(matching the message in default game locale) for older clients to display the message correctly, and for debugging purpose. The text will still pass through the profanity filter as usual.
Currently implemented chat codes in the Scalable Server:
Chat code |
Description |
---|---|
1 |
Welcome message (in game) |
2 |
Inappropriate message from a player |
3 |
Advertising /report command feature |
4 |
“Thanks for your report” |
5 |
Report cooldown (players cannot spam report) |
6 |
Help (list of commands) |
7 |
Lobby statistics (command /statistics) |
8 |
In-game statistics (command /statistics) |
9 |
Welcome message (in lobby) |
10 ~ 255 |
Reserved for future use |
Chat commands¶
The Scalable Server offers a few commands, to be issued from the chat like a regular message. Currently, there are:
/help
: displays the list of all supported commands/report toto
: to report the user toto (please note that no check is done server-side regarding the username)/statistics
: displays statistics on the lobby (number of players, number of open games) or the current game (number of active players, of forfeiters…)
Lobby¶
Entering¶
To be part of the lobby one needs to enter it by sending an EnterLobbyRequest, the server will return LobbyEnteredRequest. If the player was already in the lobby, the server will return an ErrorRequest (PLAYER_ALREADY_IN_LOBBY
).
Exiting¶
To exit the lobby, send an ExitLobbyRequest, the server will return a LobbyExitedRequest. Note that upon joining a game (or creating a game), when that game starts, the player is automatically exited from the lobby.
Player List¶
When entering the lobby, the client will automatically and regularly receive LobbyPlayerListRequest. This request contains the list of the players connected in the lobby.
Note
The embedded LobbyPlayerListRequest.PlayerList is in fact stored as a binary string, result of the Zlib Deflate compression. The protobuf C++ library contains utilities to uncompress this format, other languages usually have this feature in their standard library.
The player list contains instances of SmallPlayer which are not regular Player. Those contain less information than regular Player instances. If more information is needed, please see the Player Information workflow.
Open Game List¶
Like with the Player List workflow, the client once in the lobby will regularly receive a LobbyGameListRequest, containing the list of the open games and their details.
Note
The embedded GameList is in fact stored as a binary string, result of the Zlib Deflate compression. The protobuf C++ library contains utilities to uncompress this format, other languages usually have this feature in their standard library.
Player Information¶
To get access to another player information, just send an AskPlayerInfoRequest with the Asmodee.net player id. In return the server will send a LobbyPlayerInfoRequest containing a Player instance with the information needed.
Joining an Open Game¶
To join an open game, the client must send a LobbyJoinGameRequest:
If the join is denied (see LobbyJoinDeniedRequest.JoinError for the list of possible cause), a LobbyJoinDeniedRequest is sent back to the sender, the other players of the game are not informed.
To join a Private Game, its password
needs to be provided.
Warning
It is the client responsibility to ensure the client compliance with the rules engine before joining by checking the rules_engine_version
field of GameConfiguration (for more information, see Manage online versioning).
Creating an Open Game¶
To create an open game, the client sends a LobbyCreateGameRequest, with the correct GameConfiguration and possibly initial state and summary state.
The server can answer with an ErrorRequest (TOO_MANY_OFFERS
) if the creator has too many games in progress, or with a LobbyGameCreatedRequest if it succeeded.
The Game UI should then show a waiting screen until either the creator aborts by leaving or enough players join this open game.
Leaving an Open Game¶
Until the game is started it is possible for a player to leave an open game, by sending a LobbyLeaveGameRequest:
Warning
Note that if the game creator leaves the game while it is still open, there would be no way to start the game as it is her responsibility. Thus, the open game will abort.
Starting the Game¶
A game starts when the correct number of player have joined it. In this case all players will receive a GameCreatedRequest.
The game creator can decide to start the game before the game is complete (i.e. before max_players
players have joined). For this to work, the client must set the appropriate min_players
and max_players
fields of GameConfiguration during game creation.
When the game creator (and only the game creator) thinks there are enough players she can send a LobbyStartGameRequest which will either:
start the game, the rest of the protocol is the same as when a game is complete.
return an error LobbyStartGameDeniedRequest with a dedicated
cause
(e.g.: if the game configuration minimum player is not reached). The complete error list is LobbyStartGameDeniedRequest.StartError.
Note
When the open game is complete (all player slots are full) or received an “early start” from the creator, the game will really start automatically and all players will be exited from the lobby.
Observable Game List¶
While in the lobby, it is optionally possible to subscribe to the list of observable games. This list contains games that have been started and for which you can observe what the players are doing.
Subscribing to the list update is done by sending a SubscribeToObservableGameListRequest with subscribe
to true
to the Scalable Server. The server will reply with a SubscribedToObservableGameListRequest where subscribed
is true
.
To unsubscribe, one needs to send a SubscribeToObservableGameListRequest with subscribe
to false
. The server will respond with a SubscribedToObservableGameListRequest where subscribed
is false
.
When subscribed the client will receive a stream of ObservableGameListRequest containing the list of games that can be observed. To be noted that this list doesn’t contain live data of the games. It is required to start observing a game to get the real game state.
Observing a Game¶
To observe a game, just send a StartObserveGameRequest with the game_id
of the game to observe. To stop observing send a StopObserveGameRequest with the game_id
of the game to stop observing. The server answers to these two requests with a GameObservedRequest which might either contain an error status or OK
and the full game StatusReport.
Note that it isn’t possible to observe:
a game you’re playing in
a private game
a non-observable game
an observable game restricting observers to buddies if you’re not a buddy of the creator
if you’re not in the lobby
if the game is
OVER
orNOT_STARTED
if the game doesn’t exist
All those cases generates a GameObservedRequest with the appropriate error status
.
In order for a player to watch a game outcome, just after forfeiting or timing out, the player can start observing this game by sending StartObserveGameRequest with the game id (this is irrespective of the observability properties of the game). The client GUI should list games in which the player has timed out or forfeited (see WhatsNewPussycatRequest) as observable games along with the list of games the player is active in.
In game¶
The Scalable Server doesn’t implement any specific game logic, it can only accommodate turn-by-turn games. It’s up to the client to implement the correct game workflow, the Scalable Server will just provide the communication layer and persistence for the game state.
This turn workflow has been designed so that the Game State is unique and is controlled only by one Client at a time, preventing state corruption. The server decides which client can modify the Game State, based on what the previous player told it. The Game State modifications are fully atomic, and the client must send the whole Game State.
Standard Game Turn¶
A standard game turn is a sequence of the following events:
Server sends an ActionRequiredRequest to the current player’s client with the current
state
, and some other information useful to the client (liketurn_index
, currentplayer_clock
…)Current player sends back a CommitActionRequest containing a new state in
next_state
, and the list of the next players in sequence innext_players
(it can start with the same current player to play again). It can also send anext_summary_data
if needed.Server acknowledges the commit with an ActionCommitedRequest. The client can check that the
turn_index
is correct. The server can also send an error at this stage (see below).Server decides who’s playing next based on the list of next players sent in the CommitActionRequest, and starts back to point 1.
Here’s what it gives graphically with a three players game:
Notice how in the above example Player C is playing again in the 4th turn.
Note that the next_players
is an array. This allows the server to know what would a possible turn look like, this also allows the server to select another player in case of robot hotswap.
Warning
We recommend to put all possible players in the array next_players
in order to support robot hotswap and avoid unwanted abort of the game.
Also note that if the broadcast boolean parameter is set to true, the new game state will be broadcast to other active players and observers. This avoids using the Multicast workflow.
Pausing Clock after commit¶
Warning
This feature is experimental, protocol might change in the future.
Some games display the previous players turns animations at the start of the active player turn. While those animations run on the client, the active player clock is ticking on the server. Players might complain that their time has been consumed while they weren’t able to play, which can be perceived as unfair.
In order to mitigate this feeling, it is possible to specify a short pause time for the active player clock for this specific purpose. The clock will not be decreased for this delay, and then it will resume and tick normally.
You have to fill the pause_time
member of CommitActionRequest with the duration of the pause (in seconds) for the immediate next
player. This time is given back in ActionCommitedRequest, and transmitted to the player through ActionRequiredRequest.
Clock-related messages will be sent at appropriate moments: ClockPausedRequest when the clock is effectively paused (beginning of the turn), and ClockResumedRequest (providing clocks status) when the pause time is consumed.
If for some reason, the player commits action before the pause is over, the server will cancel the resume order as it makes no sense.
If the provided pause time is zero, no pause is done and the turn follows the regular protocol of a Standard Game Turn.
Note
Please note that a maximum duration for the pause is set in configuration, giving a pause time that breaks the limit will result in an error.
Warning
The clock might resume at a moment where the player was not on the app. When the player comes back, she will notice that the clock is not paused. In fact, the “pause time” was already done server-side but the player just was not there to witness it.
Idle timer¶
Warning
This feature is experimental, protocol might change in the future.
The player clock ensures no malicious player can lock the game by not playing. Sometimes this might not be enough, as waiting for a whole player clock to expire can be quite long and cause frustration for other players.
Besides the player clock, the Scalable Server offers an optional idle timer. Regular updates of the timer progress will be broadcast with PlayerIdleProgressRequest, filled with the array of players currently idle and the percentage of total time currently exhausted (at 50%
, 75%
and 100%
).
When exhausted, the server will ask a robot to play for the currently idle players, as if they left, and the game will continue as normal (players are not kicked out of the game).
To enable the idle timer at game creation, fill the idle_time
field of GameConfiguration with a positive number of seconds.
If the provided idle_time
is zero or negative, the timer will not be used and the turn follows the regular protocol of a Standard Game Turn.
Warning
We strongly advise to scale the idle_time
duration with the total player clock, so that longer games will have a longer idle time, and shorter games will have a shorter idle time.
Note
When idle timer is enabled at game creation, one can override the timer for the next turn in order to deal with asymetrical games or non linear thinking phase through idle_time
in CommitActionRequest.
Warning
Even though we asked a robot to play, the original player still has ability to commit her action, while an other player will also try to commit action for her (as a robot). Depending on who shot first, the player or the robot handler will get an ErrorRequest with NOT_YOUR_TURN
error. The game will continue as normal, as either way someone played for this turn.
Note
When using a pause_time
at commit, the idle timer will start only when the pause ends.
Errors¶
When a client commits data to the server, the following errors can be returned:
Error Type |
Meaning |
---|---|
NOT_YOUR_TURN |
player tried to play, server thinks she’s not the current player |
UNKNOWN_PLAYER |
|
player sending this commit is not part of this game |
|
UNKNOWN_GAME |
|
YOU_FORFEITED |
this player forfeited earlier, and thus can’t play anymore |
YOU_RAN_OUT_OF_TIME |
this player exhausted her player clock, and thus can’t play anymore |
YOU_LEFT |
this player has left a synchronous game, she needs to resume the game first |
INDEX_CONFLICT |
this player provided a turn index, but it does not match the server’s own turn index |
BAD_REQUEST |
provided |
|
|
|
|
any other problem the server detected |
Simultaneous Game Turn¶
Simultaneous turns allow to require multiple independent players actions during the same turn.
To enter a simultaneous turn, one must send a CommitActionRequest with next_simultaneous
as true
, and provide players required to act in simultaneous_players
array. Once the action is validated, the game will enter in simultaneous turn and send UserDataUpdateRequiredRequest to all provided players (if present, otherwise PlayerTimeoutRequest is sent).
For players to send data, use UpdateUserDataRequest. This should also be used when playing for a robot. The Scalable Server will send back UserDataUpdatedRequest for confirmation.
Once all required players provided their UserData, the next player will be selected to merge all the actions. She receives ActionRequiredRequest with user_data
populated with previously described requests.
Warning
In case of next player is an invited robot, a PlayerTimeoutRequest is sent.
Note
merge turn is seen as an administrative turn and will not update the next array of players.
She sends a CommitActionRequest with the new state. If valid, the simultaneous turn is completed (after sending ActionCommitedRequest for confirmation).
According to commit’s next_simultaneous
member, the next turn may be simultaneous or not…
If the player never responds to the ActionRequiredRequest, we will send a PlayerTimeoutRequest following the same model as regular turns, except the Scalable Server will provide user_data
required to perform the merge as a robot.
Also note that if the broadcast
boolean parameter is set to true
, the user data will be broadcast to other active players and observers. This can be useful for other clients to acknowledge that “Player A played and is waiting for you”.
Warning
merger selection: the player which should perform the merge of all the user data is by default the first member of the current next array if present or an invited robot. If she’s not an invited robot and not present, the Scalable Server will select any other present player that was part of this simultaneous turn. To prevent waiting for the absent original merger player, the client logic must check and commit with the correct next array. If the simultaneous turn didn’t change the order of the next players, then the same next list should be used, in which case the original merger will be the next player to receive an ActionRequiredRequest.
Pausing Clock in a simultaneous turn¶
Warning
This feature is experimental, protocol might change in the future.
The same way clocks can be paused at the beginning of a standard turn, it is possible to pause the clocks at the beginning of a simultaneous turn.
Besides setting next_simultaneous
, you have to fill the pause_time
member of CommitActionRequest with the duration of the pause (in seconds) for the simultaneous players. This time is given back in ActionCommitedRequest, and transmitted to the player through UserDataUpdateRequiredRequest.
All clocks of simultaneous_players
will be paused, but only one ClockPausedRequest and ClockResumedRequest will be sent (to all players).
If for some reason, a player updates its user-data before the pause is over, other players will still be paused.
If the provided pause time is zero, no pause is done and the turn follows the regular protocol of a Simultaneous Game Turn.
Note
Please note that a maximum duration for the pause is set in configuration, giving a pause time that breaks the limit will result in an error.
Warning
When the clock resumes, player might be absent and not see that the clock was paused. However, the time was indeed saved so globally this changes nothing.
Interruptions¶
When sending a CommitActionRequest, you can specify the action as interruptible: some other players will be asked by the Scalable Server if they want to interrupt the last action.
For this, you have to specify the interruption_window_duration
parameter with the duration you want the interruption to be possible for (in milliseconds).
Players set in interruption_player
are the interrupters: they will have the given amount of time to tell the server about the interruption, by sending an InterruptActionRequest, providing interrupt_data
(please keep them small as they are transmitted to all players).
It is important to provide a correct turn_index
so the server knows exactly which action is being interrupted, in case of race conditions during consecutive interruptions. The correct value is given by ActionCommitedRequest and GameStateUpdatedRequest.
Note
It is up to the game client’s to acknowledge the game is in an interruption state. After sending the CommitActionRequest, the server will send a GameStateUpdatedRequest to all players. This request relays the interruption_window_duration
given at commit, so if it is non-zero, you should be in an interruption turn.
Once the time is exhausted, all players will receive an InterruptionOverRequest so they know the time is up. Any interruption received after the time delay will be discarded and return an InterruptActionErrorRequest.
One player can also explicitly put a stop to the interruption phase by setting no_more_interruption
.
A player will be selected to resolve the interruptions (as possibly there can be several interruptions) with an ActionRequiredRequest (or a PlayerTimeoutRequest if the merge should be performed by a robot). According to the result, the game will continue as normal.
Warning
Please note that during all interruption steps, the time will be decreased from the original committer’s clock (in the diagram, Player A). During the merge action, the time is decreased from the merger’s clock (in the diagram, Player B).
We strongly recommend to hide all this protocol under a visual animation for timing. For instance, once I played an interruptible card, the game would trigger an animation whose duration corresponds to the interruption duration, so:
other players do not have an inactive screen waiting for several seconds (there is always something happening to keep player’s attention)
players have a visual indication that an interruption is possible, and how many time is left until the interruption window closes
It is up to the game’s client to set the interruption duration, thus we advise to use quite the same as animation duration. Also, the animation should start on all devices at the same time (or the closest possible) to minimize synchronization issues:
the interrupter could receive the message a little bit late, and the animation will still play as the interruption phase is over
the interruptee could start the animation too soon, so it thinks the interruption is over but the server still accepts interruption requests for a small period
To achieve this, we recommend triggering the animation not after sending CommitActionRequest but only after receiving GameStateUpdatedRequest. It is sent to all clients at the same time so only network latency will come in play.
Warning
Simultaneous turns do not support Interruptions. If you set both next_simultaneous
and interruption_window_duration
, the server will send an ErrorRequest.
Multicast¶
The MulticastDataRequest allows to send arbitrary information to the other present players in the game, for instance to refresh the UI.
Warning
Please note that this should not be used to relay the state change after an action. The correct way is to set the broadcast parameter of CommitActionRequest to true
(or UpdateUserDataRequest in case of a simultaneous turn). The server will thus atomically send a GameStateUpdatedRequest exhibiting the new state (or user data) to other live players.
If, for whatever reason, you can’t use the broadcast parameter, you might have to use the MulticastDataRequest to send the new state data to other clients. However this is an anti-pattern and should be avoided:
the server will simply forward the information, it doesn’t know whether it’s related to state or not. It will not modify its state when receiving the request. You might inadvertently send data not compatible with the current state, resulting in a corrupted local state in the client.
MulticastDataRequest content is transient and will be lost for players leaving the game or in the rare event of a service restart, unlike the game state which is guaranteed available.
players may receive MulticastDataRequest before ActionCommitedRequest, possibly resulting in local conflicts.
worse, the next player may receive her ActionRequiredRequest before the MulticastDataRequest. The game engine would then compute the action to perform without having the last state.
Now that you know the danger of using the multicast to transmit the game state, here is how to use it to send arbitrary information.
Any present player can send a MulticastDataRequest to the Scalable Server with some binary data
and possibly a list of recipients_ids
. The server will then send back a ClientDataRequest containing the binary data
to all the recipients_ids
. If recipients_ids
is empty, all the present players will receive the request. Non-present players can also be notified by a push notification, if notification
is filled-in.
Forfeit¶
At any time a player can decide to withdraw from the game (and lose it). She does so by sending a GameForfeitRequest. In return the server will send back a GameForfeitedRequest to all the present players in the game.
If the forfeiting player was the last active (i.e. all other players have already either forfeited, left or exhausted their Player Clock), then the game will Abort.
If the forfeiting player wasn’t the last remaining active player, the Scalable Server will do the necessary to replace it with a robot as explained in Robots. The Scalable Server will also send to all the present players a PlayerReplacedRequest so that they can display the hotswap information in the UI.
Player Timeout¶
When a player exhausts her own Player Clock, she can’t play anymore and is replaced by a robot, as explained in Robots section. The Scalable Server will also send to all the present players a PlayerReplacedRequest so that they can display the hotswap information in the UI.
Robots¶
Since the Scalable Server Game instance has no knowledge of the game engine, it can’t play robots directly. As such it asks one of the connected client to impersonate another player and play for her with an IA (which is what we call a robot).
This can happen for the following reasons:
a player forfeited.
a player exhausted her Player Clock
a player left a synchronous game
a robot had been invited
In all those cases, at this player’s turn, the Scalable Server will select another client (preferentially from the ones still connected to the server) and ask it to play for this defecting player.
The game logic in the client thus must know how to play for a different player than the human using the UI.
The robot workflow works like this:
Player A commits her action by sending a CommitActionRequest as usual
server notices the next player (Player B) needs to be played by a robot
if Player A is still present, the Server will send a PlayerTimeoutRequest to its client, for it to play for Player B
Player A’s client sends a CommitActionRequest as if it was Player B
server keeps going on with Player C
This is best illustrated with this sequence diagram:
If two or more players must be played by robots, the same process happens.
If Player A is not present anymore when the server needs a robot to play, it will select one of the other connected clients of this game in order of appearance in the last next_players
array. If none are connected at this time, the server will select the next non-robot in order of appearance in the last next_players
array and send her a push notification as if it was her turn to play.
Warning
We recommend to put all possible players in the array next_players
.
Upon receiving this push notification, this client game logic (once the controlling human decides to play her turn) must check this game StatusReport, notice that turn
and active_player
are not the same (and active_player
is this client’s player) and use a robot to play for the turn
player.
Note
In a fully asynchronous game, any client can play for any player. To prevent any issue, the client that plays must check for the error NOT_YOUR_TURN
after sending the CommitActionRequest. In case of error, it means another client already played, and the client should refresh its view of the StatusReport and possibly not play. As such, it is recommended to check the active_player
value and only play when it matches the current player.
Game Over¶
At one point in the game, one of the player will trigger the end of game as implemented by the rules engine, this client will then send a GameOverRequest containing the ordering of the players in finalScores
(including robots and timed out or forfeited players). Make sure the is_ranked
is set to true
if the Scalable Server needs to update the rankings of the players.
The game status will then change to OUTCOME
, and all connected players will receive a GameOutcomeRequest containing ranking information (and the player’s scores). The clients will then display this in a final score screen.
This information can also be displayed from the StatusReport for asynchronous players.
All players will have to confirm the outcome by sending a GameOutcomeConfirmationRequest before the game can reach the OVER
status, and ultimately disappear. For asynchronous clients, the game logic can know if this player already saw the final scores by checking the StatusReport outcome_not_seen
array which contains the list of players that have not yet confirmed the outcome.
Warning
No commits or user data updates will be processed after a GameOverRequest is sent to the server, be sure to provide the final score and final state of the game (including final bonuses) in the GameOverRequest.
Note
In case of game over during a simultaneous turn, we recommend to send the GameOverRequest in place of a merge commit after receiving the ActionRequiredRequest.
Abort¶
A game can abort at any time if no more players can play (all timed out or all withdrawn) and will try to notify connected players (and observers) with a GameAbortedRequest containing the applied ranking penalties. The game status also changes to ABORTING
and then after a while to ABORTED
(to let the players see the game status with Current Games Information and then confirm with the message GameAbortedConfirmationRequest).
Game Observation¶
As explained in Observation, it is possible for multiple players to observe what happens in a given game. First the client needs to start observing an observable game (from the observable game list).
Once in the list of observers, the client will receive all events a normal player would normally receive, including all ActionRequiredRequest and PlayerTimeoutRequest.
It is up to the client to display what happens in the game from this information and the game logic.
Player Clocks Status¶
It is possible for a client to fetch all the player clocks status. It is hard to maintain a synchronized clock while displaying the UI, so from time to time, it is necessary to refresh the player clocks from the server.
To do that, send a GetClocksRequest directed to a game, the server will send back a ClocksStatusRequest.
Note
The playing player clock is always sent in the ActionRequiredRequest, so at least every start of a player turn, her local clock can be synchronized.
Push Notifications¶
Definition¶
The Scalable Server is able to send native device push notification (to Steam users, Google android users and iOS/OSX users) when specific events happen:
an invitation is sent to a player
it is a player’s turn (or a robot turn)
end of a game
Prerequisites¶
To enable push notifications the Scalable Server needs to be provisioned with the correct configuration, as explained in the following table. The developer needs to contact the Asmodee.net Administrators to enable the push notification feature.
Platform |
Material needed |
---|---|
iOS or OSX |
bundle id of the application (com.asmodee-digital.xxx) from apple developper account provision profile |
Google FCM |
sender id |
key |
|
Steam |
app id |
api key |
Client side integration¶
The Client, when authenticating a player by sending an AsyncAuthRequest, must provide a DeviceType structure containing:
the type of push system among (
IOS
,GCM
,FCM
,STEAM
,OSX
,IOS_SANDBOX
,OSX_SANDBOX
), see Devices.Typethe opaque token provided by the client operating system.
Apple allows to use a “sandbox” mode for Push Notifications, those are using their distinct codes because they use another endpoint of the APNS for developement/testing purpose (by default sandbox mode should be enabled).
For more information about receiving Push Notifications on iOS see Apple Push Notification Documentation.
For more information about receiving Push Notifications on Android see Firebase Cloud Messaging Documentation.
Note
GCM is deprecated and not supported anymore by Google. Developers should use FCM instead.
Format of a push notification¶
When an event triggering a push notification happens, the Scalable Server will notify the Push Notification platform matching the recipient device which will send to the client the following data (which models the data available in an iOS notification):
Key |
Content |
---|---|
|
id of the action |
|
id of the localized string in the application resources that will be displayed in the notification |
|
some arguments that depend on the notification type |
|
notification payload |
The payload is a protobuf serialized data structure of GameNotification that contains the game id, the event type, the player id of the recipient and the current turn index.
Apple Usage¶
On iOS, the client receives the notification as a dictionary. The above keys are used by the system to display the push notification if the app is not in foreground. For instance it will display a button whose string is searched in the app localized string whose key is the value of the action-loc-key
. The text content of the notification will be derived from the interpolation with loc-args
of a string coming from the app localized string whose key is loc-key
. See Apple Push Notification Payload Key Reference for more information.
Android Usage¶
On Android, the system is less automatic. The Scalable Server only sends Data Messages.
The client will receive a RemoteMessage
object and can get access to the Scalable Server payload through the getData
call which will return a Map
containing the keys shown above.
Once done, the client must build a notification (if in background) by looking at the loc-key
string and interpolate it with loc-args
as explained below, and display it as a notification (see NotificationCompat.Builder for this).
String interpolation¶
The interpolation of the loc-key
matched string obeys to Apple format: every $@
will be replaced by one of the value of the loc-args
array, for instance:
if ``loc-key`` give this string:
"You've been invited by $@"
and ``log-args`` contains ``CaptnHook``, the result will be:
"You've been invited by CaptnHook"
Type of notifications¶
Type |
Key |
Value |
---|---|---|
|
Sent to the current player in a game |
|
action_key |
|
|
localized_key |
|
|
localized_args |
none |
|
|
Sent to the current player of an invitation game |
|
action_key |
|
|
localized_key |
|
|
localized_args |
name of the inviter |
|
|
Sent to the player selected to play a robot for another player |
|
action_key |
|
|
localized_key |
|
|
localized_args |
none |
|
|
Sent to a player that has been invited to a game so that she can confirm or deny the invitation |
|
action_key |
|
|
localized_key |
|
|
localized_args |
name of the inviter |
|
|
Sent to a player to inform her that an invitee confirmed the invitation |
|
action_key |
|
|
localized_key |
|
|
localized_args |
name of the invitee who accepted |
|
|
Sent to a player to inform her that an invitee denied the invitation |
|
action_key |
|
|
localized_key |
|
|
localized_args |
name of the invitee who declined |
|
|
Sent to a player to inform her that the game ended. When all players saw the outcome, the game can be deleted |
|
action_key |
|
|
localized_key |
|
|
localized_args |
none |