Using the Scalable Server

Scalable Server documentation

Please refer to the documentation available at http://ssdoc.asmodee.net/

Dependencies

Protobuf C#

Google provides Protobuf via GitHub: https://github.com/protocolbuffers/protobuf.

We developed SDK 5.0 using Protobuf v3.6.1:

Protobuf Unity Compiler

Protobuf lets you describe the format of your data in .proto files, then you generate the corresponding C# classes by calling protoc. ProtobufUnityCompiler available at https://github.com/5argon/protobuf-unity/blob/master/Editor/ProtobufUnityCompiler.cs eases this process by automatically calling protoc every time you modify a .proto file.

Setup

NetworkParameters

Verify your connection parameters in Network Parameters under Scalable Server section.

CoreApplication

In your CoreApplication component, you should set the Use Scalable Server parameter to true.

Usage

ScalableServerManager

ScalableServerManager is the entry point for all the services linked with the Scalable Server. You can access it through the CoreApplication :

var scalableServerManager = CoreApplication.Instance.ScalableServerManager;

The ScalableServerManager contains a ServerConnection wich is the low level API to send and receive requests. It also provides workflows following the Scalable Server Documentation.

CommunityHub

A simple way to connect to the Scalable Server is to require an Online Status with:

  • InternetConnectionWithScalableServerConnectionOptionalStatus
  • InternetConnectionWithScalableServerConnectionRequiredStatus will prompt the SSO

ServerConnection

ServerConnection is the base layer where everything happen. Your main entry point is Connect allowing you to establish a connection with the Scalable Server and triggering the authentication of the player. When leaving an online section of the game, you may want to Disconnect or DisconnectAfterDelay.

When connected to the Scalable Server, the client sends and receives requests. The complete Request Reference is available as C# helper methods (C -> S) and events (S -> C).

public enum ConnectionState { FailedToConnect, NotConnected, Connecting, Connected }
public ConnectionState ConnectionState;

public void Connect();
public void Reconnect();
public void Disconnect(bool disconnectedOnNetworkLoss = false);
public void DisconnectAfterDelay();
public void PreventFromDelayedDisconnection();

// Server -> Client
public event Action<GameStatusReportRequest, long> GameStatusReportRequestReceived;
...
...

// Client -> Server
public long SendWhatsNewPussycatRequest(long? gameId = null, bool summaryDataOnly = false) {...}
...
...

Authentication Workflow

Reference: Scalable Server Documentation.

Authenticating the user is done automatically when a connection is established. You can subscribe to AuthenticationState modifications, and access data about the AuthenticatedPlayer.

public enum AuthenticationState { NotAuthenticated, Authenticating, Authenticated }
public AuthenticationState AuthenticationState;

public event Action Authenticated;
public event Action DeAuthenticated;
public event Action FailedToAuthenticate;

public Player AuthenticatedPlayer { get; private set; }

Chat Workflow

References: in lobby chat workflow documentation, in game chat workflow documentation

The API is simple, but we also provide a Chat Community Hub Content for a simpler integration.

public event Action<ChatEntryRaw> ChatReceived;
public event Action<List<ChatEntryRaw>> ChatHistoryReceived;

public void RefreshHistory(long? gameID = null) {...}
public void SendMessage(string chat, long? gameId, List<int> recipientIds = null, int code = 0) {...}

Game Workflow

Reference: Scalable Server Documentation.

Receiving game events:

public delegate void GameCreatedListener(int invitedBy, int localPlayerId, GameDetails onComplete);
public event GameCreatedListener gameCreatedListener;

// Standard Game Turn
public delegate void ActionRequiredListener(long gameId, byte[] state, List<Player> players, List<int> nextPlayerIds, int turnIndex, int invitedBy, GameClock playerClock, List<PlayerPregameData> userData);
public event ActionRequiredListener actionRequiredListener;
//  - Broadcast
public delegate void GameStateUpdatedListener(long gameId, int playerId, int turnIndex, byte[] state, List<StatusReport.Types.PlayerClock> clock);
public event GameStateUpdatedListener gameStateUpdatedListener;

// Robots
public delegate void PlayerTimeoutListener(long gameId, byte[] state, int offenderId, List<Player> players, List<int> nextPlayerIds, PlayerTimeoutRequest.Types.PlayerStatus status, int turnIndex, GameStatus gameStatus, List<PlayerPregameData> userData);
public event PlayerTimeoutListener playerTimeoutListener;

public delegate void PlayerReplacedListener(long gameId, int playerId, bool becomeRobot, PlayerTimeoutRequest.Types.PlayerStatus status);
public event PlayerReplacedListener playerReplacedListener;

// Simultaneous Game Turn
public delegate void UserDataUpdateRequiredListener(long gameId, int turnIndex, List<int> participant, byte[] state);
public event UserDataUpdateRequiredListener userDataUpdateRequiredListener;
//  - Broadcast
public delegate void GameUserDataUpdatedListener(long gameId, int playerId, int turnIndex, List<PlayerPregameData> userData);
public event GameUserDataUpdatedListener gameUserDataUpdatedListener;

// Multicast
public delegate void ClientDataListener(long gameId, byte[] data, Player sender);
public event ClientDataListener clientDataListener;

// Abort
public delegate void GameAbortedListener(long gameId, List<int> abortingPlayerIds, List<PlayerRankingUpdate> rankingUpdate, List<KarmaUpdate> karmaUpdate, byte[] data);
public event GameAbortedListener gameAbortedListener;

public void PlayerClocksStatus(Action<ErrorCode?, List<StatusReport.Types.PlayerClock>> completion, long gameId) { ... }

Sending game data:

public void CommitAction(Action<ErrorCode?> completion, long gameId, int localPlayerId, byte[] nextState, List<int> nextPlayerIds, byte[] nextSummaryData = null, int turnIndex = 0, bool nextSimultaneous = false, List<int> simultaneousPlayers = null, bool broadcast = false) { ... }
public void UpdateUserData(Action<ErrorCode?> completion, long gameId, byte[] userData, int localPlayerId, bool expectMoreData, bool broadcast) { ... }
public void MulticastData(Action<ErrorCode?> completion, long gameId, byte[] data, List<int> recipientIds, bool presentPlayersOnly) { ... }
public void GameForfeit(Action<ErrorCode?, bool?> completion, long gameId) { ... }
public void GameOver(Action<ErrorCode?, List<PlayerRankingUpdate>, List<KarmaUpdate>> completion, long gameId, List<FinalScore> finalScore, List<PlayerAchievementUpdate> achievementsAwarded = null, byte[] data = null) { ... }
public void GameOutcomeConfirmation(long gameId, int localPlayerId) { ... }
public void GameAbortedConfirmation(long gameId, int localPlayerId) { ... }

In Game Presence:

public enum Presence { unknown, absent, present, inGame }

public event Action<Dictionary<int, Presence>> PresenceDidChange;

public Presence PlayerInGamePresence(int playerId) { ... }
public void SwitchToGame(long gameId) { ... }
public void LeaveGame(long gameId) { ... }

General Workflow

Reference: Scalable Server Documentation.

All non-specific services are provided through the General Workflow.

public void GetGameInformation(Action<ErrorCode?, StatusReport> completion, long gameId, bool summaryDataOnly = false) { ... }
public void GetCurrentGamesInformation(Action<ErrorCode?, List<StatusReport>> completion, bool summaryDataOnly = false) { ... }

// Buddy List / Ignore List
public void GetBuddyList(Action<ErrorCode?, List<Buddy>> completion, List<string> webCardFilter = null) { ... }
public void AddBuddy(Action<ErrorCode?> completion, int playerId) { ... }
public void RemoveBuddy(Action<ErrorCode?> completion, int playerId) { ... }
public void GetIgnoreList(Action<ErrorCode?, List<Player>> completion, List<string> webCardFiter = null) { ... }
public void AddIgnore(Action<ErrorCode?> completion, int playerId) { ... }
public void RemoveIgnore(Action<ErrorCode?> completion, int playerId) { ... }

// Server Statistics
public ServerStatistics? LatestServerStatistics;
public event Action<ServerStatistics?> ServerStatisticsDidChange;
public void AskServerStatistics(bool subscribe) { ... }

// Presence
public enum Presence { unknown, absent, present }
public event Action<IDictionary<int, Presence>> PresenceDidChange;

public Presence PlayerPresence(int playerId) { ... }
public void RegisterPresence(List<int> playerIds) { ... }
public void UnregisterPresence(List<int> playerIds) { ... }
public void SubscribePresenceService() { ... }
public void UnsubscribePresenceService() { ... }

// Invitation
public delegate void InvitationAnsweredListener(long gameId, Player invitee, bool accept);
public event InvitationAnsweredListener invitationAnsweredListener;

public void EngageGameWithFriends(Action<ErrorCode?, GameDetails> completion, GameConfiguration gameConfiguration, List<int> friends, byte[] userData = null, byte[] initialState = null, byte[] initialStateSummary = null) { ... }

public void AnswerInvitation(Action<ErrorCode?> completion, long gameId, bool accept, byte[] userData = null) { ... }

Heartbeat Workflow

Reference: Scalable Server Documentation.

In order to keep a connection live, the Scalable Server requires to send requests regularly. The role of HeartbeatWorkflow is to send the Ping request and to alert if it takes too long to receive a response.

public enum HeartbeatState { Failure, Disabled, Unstable, Stable }
public long RequestRoundTripTime { get; private set; }

public event Action<HeartbeatState> HeartbeatStateDidChange;
public HeartbeatState HeartbeatState;

Lobby Workflow

Lobby State:

public enum LobbyState { NotInLobby, EnteringLobby, ExitingLobby, InLobby }
public LobbyState LobbyState;

public event Action EnteredLobby;
public event Action ExitedLobby;

public bool AutomaticallyEnterLobbyAgainAfterReAuthentication { get; set; } = true;

public void EnterLobby(Action<bool> completion = null, bool forceSendingRequest = false) { ... }
public void ExitLobby(Action<bool> completion = null, bool forceSendingRequest = false) { ... }

Lobby Players:

public event Action<IList<Player>> LobbyPlayersUpdated;
public IList<Player> LobbyPlayers;

public void GetPlayerInformation(Action<ErrorCode?, Player> completion, int playerId) { ... }

Open Games:

public event Action<IList<GameDetails>> LobbyGamesUpdated;
public event Action<IList<long>> LobbyJoinedGameIdsUpdated;
public event Action<GameDetails, int> NewPlayer;
public event Action<GameDetails, Player> PlayerLeftGame;

public IList<GameDetails> LobbyGames;
public IList<long> LobbyJoinedGameIds;

public void CreateGame(Action<ErrorCode?, GameDetails> completion, GameConfiguration gameConfiguration, string password = "", byte[] userData = null, byte[] initialState = null, byte[] initialStateSummary = null) { ... }
public void JoinGame(Action<ErrorCode?, LobbyJoinDeniedRequest.Types.JoinError?, GameDetails> completion, long gameId, string password = "", byte[] userData = null) { ... }
public void LeaveGame(Action<ErrorCode?, GameDetails> completion, long gameId) { ... }
public void StartGame(Action<ErrorCode?, LobbyStartGameDeniedRequest.Types.StartError?, GameDetails> completion, long gameId, bool fillWithRobots = false, byte[] data = null) { ... }

Observable Games:

public event Action<IList<GameDetails>> ObservableGamesUpdated;
public IList<GameDetails> ObservableGames;

public void SubscribeToObservableGames(Action<ErrorCode?, bool> completion, bool subscribe = true) { ... }
public void UnSubscribeToObservableGames(Action<ErrorCode?, bool> completion) { ... }

public void StartObserveGame(Action<ErrorCode?, GameObservedRequest.Types.Status?, StatusReport> completion, long gameId, bool observe = true) { ... }
public void StopObserveGame(Action<ErrorCode?, GameObservedRequest.Types.Status?> completion, long gameId) { ... }

LobbyPresenceManager

After having entered the Lobby, we can monitor the presence of specific players. This is useful to present the availability of the player’s buddies.

public interface IPresenceSubscriber
{
        void UpdatePresence(int playerWWWId, PresenceStatus newStatus);
}

public void Subscribe(int playerWWWId, IPresenceSubscriber subscriber) { ... }
public void Subscribe(List<int> playerWWWIds, IPresenceSubscriber subscriber) { ... }
public void Unsubscribe(int playerWWWId, IPresenceSubscriber subscriber) { ... }
public void Unsubscribe(List<int> playerWWWIds, IPresenceSubscriber subscriber) { ... }

public enum PresenceStatus { Unknown, Absent, Away, Present }
public PresenceStatus GetPresenceStatus(int playerWWWId) { ... }