// ---------------------------------------------------------------------------- // // Loadbalancing Framework for Photon - Copyright (C) 2018 Exit Games GmbH // // // Provides operations to use the LoadBalancing and Cloud photon servers. // No logic is implemented here. // // developer@photonengine.com // ---------------------------------------------------------------------------- #if UNITY_4_7 || UNITY_5 || UNITY_5_3_OR_NEWER #define SUPPORTED_UNITY #endif namespace Photon.Realtime { using System; using System.Collections; using System.Collections.Generic; using ExitGames.Client.Photon; #if SUPPORTED_UNITY using UnityEngine; using Debug = UnityEngine.Debug; #endif #if SUPPORTED_UNITY || NETFX_CORE using Hashtable = ExitGames.Client.Photon.Hashtable; using SupportClass = ExitGames.Client.Photon.SupportClass; #endif /// /// A LoadBalancingPeer provides the operations and enum definitions needed to use the LoadBalancing server application which is also used in Photon Cloud. /// /// /// This class is internally used. /// The LoadBalancingPeer does not keep a state, instead this is done by a LoadBalancingClient. /// public class LoadBalancingPeer : PhotonPeer { /// Obsolete accessor to the RegionHandler.PingImplementation. [Obsolete("Use RegionHandler.PingImplementation directly.")] protected internal static Type PingImplementation { get { return RegionHandler.PingImplementation; } set { RegionHandler.PingImplementation = value; } } private readonly Pool paramDictionaryPool = new Pool( () => new ParameterDictionary(), x => x.Clear(), 1); // used in OpRaiseEvent() (avoids lots of new Dictionary() calls) /// /// Creates a Peer with specified connection protocol. You need to set the Listener before using the peer. /// /// Each connection protocol has it's own default networking ports for Photon. /// The preferred option is UDP. public LoadBalancingPeer(ConnectionProtocol protocolType) : base(protocolType) { // this does not require a Listener, so: // make sure to set this.Listener before using a peer! this.ConfigUnitySockets(); } /// /// Creates a Peer with specified connection protocol and a Listener for callbacks. /// public LoadBalancingPeer(IPhotonPeerListener listener, ConnectionProtocol protocolType) : this(protocolType) { this.Listener = listener; } // Sets up the socket implementations to use, depending on platform [System.Diagnostics.Conditional("SUPPORTED_UNITY")] private void ConfigUnitySockets() { Type websocketType = null; #if (UNITY_XBOXONE || UNITY_GAMECORE) && !UNITY_EDITOR websocketType = Type.GetType("ExitGames.Client.Photon.SocketNativeSource, Assembly-CSharp", false); if (websocketType == null) { websocketType = Type.GetType("ExitGames.Client.Photon.SocketNativeSource, Assembly-CSharp-firstpass", false); } if (websocketType == null) { websocketType = Type.GetType("ExitGames.Client.Photon.SocketNativeSource, PhotonRealtime", false); } if (websocketType != null) { this.SocketImplementationConfig[ConnectionProtocol.Udp] = websocketType; // on Xbox, the native socket plugin supports UDP as well } #else // to support WebGL export in Unity, we find and assign the SocketWebTcp class (if it's in the project). // alternatively class SocketWebTcp might be in the Photon3Unity3D.dll websocketType = Type.GetType("ExitGames.Client.Photon.SocketWebTcp, PhotonWebSocket", false); if (websocketType == null) { websocketType = Type.GetType("ExitGames.Client.Photon.SocketWebTcp, Assembly-CSharp-firstpass", false); } if (websocketType == null) { websocketType = Type.GetType("ExitGames.Client.Photon.SocketWebTcp, Assembly-CSharp", false); } #endif if (websocketType != null) { this.SocketImplementationConfig[ConnectionProtocol.WebSocket] = websocketType; this.SocketImplementationConfig[ConnectionProtocol.WebSocketSecure] = websocketType; } #if NET_4_6 && (UNITY_EDITOR || !ENABLE_IL2CPP) && !NETFX_CORE this.SocketImplementationConfig[ConnectionProtocol.Udp] = typeof(SocketUdpAsync); this.SocketImplementationConfig[ConnectionProtocol.Tcp] = typeof(SocketTcpAsync); #endif } public virtual bool OpGetRegions(string appId) { Dictionary parameters = new Dictionary(1); parameters[(byte)ParameterCode.ApplicationId] = appId; return this.SendOperation(OperationCode.GetRegions, parameters, new SendOptions() { Reliability = true, Encrypt = true }); } /// /// Joins the lobby on the Master Server, where you get a list of RoomInfos of currently open rooms. /// This is an async request which triggers a OnOperationResponse() call. /// /// The lobby join to. /// If the operation could be sent (has to be connected). public virtual bool OpJoinLobby(TypedLobby lobby = null) { if (this.DebugOut >= DebugLevel.INFO) { this.Listener.DebugReturn(DebugLevel.INFO, "OpJoinLobby()"); } Dictionary parameters = null; if (lobby != null && !lobby.IsDefault) { parameters = new Dictionary(); parameters[(byte)ParameterCode.LobbyName] = lobby.Name; parameters[(byte)ParameterCode.LobbyType] = (byte)lobby.Type; } return this.SendOperation(OperationCode.JoinLobby, parameters, SendOptions.SendReliable); } /// /// Leaves the lobby on the Master Server. /// This is an async request which triggers a OnOperationResponse() call. /// /// If the operation could be sent (requires connection). public virtual bool OpLeaveLobby() { if (this.DebugOut >= DebugLevel.INFO) { this.Listener.DebugReturn(DebugLevel.INFO, "OpLeaveLobby()"); } return this.SendOperation(OperationCode.LeaveLobby, (Dictionary)null, SendOptions.SendReliable); } /// Used by OpJoinRoom and by OpCreateRoom alike. private void RoomOptionsToOpParameters(Dictionary op, RoomOptions roomOptions, bool usePropertiesKey = false) { if (roomOptions == null) { roomOptions = new RoomOptions(); } Hashtable gameProperties = new Hashtable(); gameProperties[GamePropertyKey.IsOpen] = roomOptions.IsOpen; gameProperties[GamePropertyKey.IsVisible] = roomOptions.IsVisible; gameProperties[GamePropertyKey.PropsListedInLobby] = (roomOptions.CustomRoomPropertiesForLobby == null) ? new string[0] : roomOptions.CustomRoomPropertiesForLobby; gameProperties.MergeStringKeys(roomOptions.CustomRoomProperties); if (roomOptions.MaxPlayers > 0) { gameProperties[GamePropertyKey.MaxPlayers] = roomOptions.MaxPlayers; } if (!usePropertiesKey) { op[ParameterCode.GameProperties] = gameProperties; // typically, the key for game props is 248 } else { op[ParameterCode.Properties] = gameProperties; // when an op uses 248 as filter, the "create room" props can be set as 251 } int flags = 0; // a new way to send the room options as bitwise-flags if (roomOptions.CleanupCacheOnLeave) { op[ParameterCode.CleanupCacheOnLeave] = true; // this defines the server's room settings and logic flags = flags | (int)RoomOptionBit.DeleteCacheOnLeave; // this defines the server's room settings and logic (for servers that support flags) } else { op[ParameterCode.CleanupCacheOnLeave] = false; // this defines the server's room settings and logic gameProperties[GamePropertyKey.CleanupCacheOnLeave] = false; // this is only informational for the clients which join } #if SERVERSDK op[ParameterCode.CheckUserOnJoin] = roomOptions.CheckUserOnJoin; if (roomOptions.CheckUserOnJoin) { flags = flags | (int) RoomOptionBit.CheckUserOnJoin; } #else // in PUN v1.88 and PUN 2, CheckUserOnJoin is set by default: flags = flags | (int) RoomOptionBit.CheckUserOnJoin; op[ParameterCode.CheckUserOnJoin] = true; #endif if (roomOptions.PlayerTtl > 0 || roomOptions.PlayerTtl == -1) { op[ParameterCode.PlayerTTL] = roomOptions.PlayerTtl; // TURNBASED } if (roomOptions.EmptyRoomTtl > 0) { op[ParameterCode.EmptyRoomTTL] = roomOptions.EmptyRoomTtl; //TURNBASED } if (roomOptions.SuppressRoomEvents) { flags = flags | (int)RoomOptionBit.SuppressRoomEvents; op[ParameterCode.SuppressRoomEvents] = true; } if (roomOptions.SuppressPlayerInfo) { flags = flags | (int)RoomOptionBit.SuppressPlayerInfo; } if (roomOptions.Plugins != null) { op[ParameterCode.Plugins] = roomOptions.Plugins; } if (roomOptions.PublishUserId) { flags = flags | (int)RoomOptionBit.PublishUserId; op[ParameterCode.PublishUserId] = true; } if (roomOptions.DeleteNullProperties) { flags = flags | (int)RoomOptionBit.DeleteNullProps; // this is only settable as flag } if (roomOptions.BroadcastPropsChangeToAll) { flags = flags | (int)RoomOptionBit.BroadcastPropsChangeToAll; // this is only settable as flag } op[ParameterCode.RoomOptionFlags] = flags; } /// /// Creates a room (on either Master or Game Server). /// The OperationResponse depends on the server the peer is connected to: /// Master will return a Game Server to connect to. /// Game Server will return the joined Room's data. /// This is an async request which triggers a OnOperationResponse() call. /// /// /// If the room is already existing, the OperationResponse will have a returnCode of ErrorCode.GameAlreadyExists. /// public virtual bool OpCreateRoom(EnterRoomParams opParams) { if (this.DebugOut >= DebugLevel.INFO) { this.Listener.DebugReturn(DebugLevel.INFO, "OpCreateRoom()"); } Dictionary op = new Dictionary(); if (!string.IsNullOrEmpty(opParams.RoomName)) { op[ParameterCode.RoomName] = opParams.RoomName; } if (opParams.Lobby != null && !opParams.Lobby.IsDefault) { op[ParameterCode.LobbyName] = opParams.Lobby.Name; op[ParameterCode.LobbyType] = (byte)opParams.Lobby.Type; } if (opParams.ExpectedUsers != null && opParams.ExpectedUsers.Length > 0) { op[ParameterCode.Add] = opParams.ExpectedUsers; } if (opParams.OnGameServer) { if (opParams.PlayerProperties != null && opParams.PlayerProperties.Count > 0) { op[ParameterCode.PlayerProperties] = opParams.PlayerProperties; } op[ParameterCode.Broadcast] = true; // broadcast actor properties this.RoomOptionsToOpParameters(op, opParams.RoomOptions); } //this.Listener.DebugReturn(DebugLevel.INFO, "OpCreateRoom: " + SupportClass.DictionaryToString(op)); return this.SendOperation(OperationCode.CreateGame, op, SendOptions.SendReliable); } /// /// Joins a room by name or creates new room if room with given name not exists. /// The OperationResponse depends on the server the peer is connected to: /// Master will return a Game Server to connect to. /// Game Server will return the joined Room's data. /// This is an async request which triggers a OnOperationResponse() call. /// /// /// If the room is not existing (anymore), the OperationResponse will have a returnCode of ErrorCode.GameDoesNotExist. /// Other possible ErrorCodes are: GameClosed, GameFull. /// /// If the operation could be sent (requires connection). public virtual bool OpJoinRoom(EnterRoomParams opParams) { if (this.DebugOut >= DebugLevel.INFO) { this.Listener.DebugReturn(DebugLevel.INFO, "OpJoinRoom()"); } Dictionary op = new Dictionary(); if (!string.IsNullOrEmpty(opParams.RoomName)) { op[ParameterCode.RoomName] = opParams.RoomName; } if (opParams.JoinMode == JoinMode.CreateIfNotExists) { op[ParameterCode.JoinMode] = (byte)JoinMode.CreateIfNotExists; if (opParams.Lobby != null && !opParams.Lobby.IsDefault) { op[ParameterCode.LobbyName] = opParams.Lobby.Name; op[ParameterCode.LobbyType] = (byte)opParams.Lobby.Type; } } else if (opParams.JoinMode == JoinMode.RejoinOnly) { op[ParameterCode.JoinMode] = (byte)JoinMode.RejoinOnly; // changed from JoinMode.JoinOrRejoin } if (opParams.ExpectedUsers != null && opParams.ExpectedUsers.Length > 0) { op[ParameterCode.Add] = opParams.ExpectedUsers; } if (opParams.OnGameServer) { if (opParams.PlayerProperties != null && opParams.PlayerProperties.Count > 0) { op[ParameterCode.PlayerProperties] = opParams.PlayerProperties; } op[ParameterCode.Broadcast] = true; // broadcast actor properties } if (opParams.OnGameServer || opParams.JoinMode == JoinMode.CreateIfNotExists) { this.RoomOptionsToOpParameters(op, opParams.RoomOptions); } //this.Listener.DebugReturn(DebugLevel.INFO, "OpJoinRoom: " + SupportClass.DictionaryToString(op)); return this.SendOperation(OperationCode.JoinGame, op, SendOptions.SendReliable); } /// /// Operation to join a random, available room. Overloads take additional player properties. /// This is an async request which triggers a OnOperationResponse() call. /// If all rooms are closed or full, the OperationResponse will have a returnCode of ErrorCode.NoRandomMatchFound. /// If successful, the OperationResponse contains a gameserver address and the name of some room. /// /// If the operation could be sent currently (requires connection). public virtual bool OpJoinRandomRoom(OpJoinRandomRoomParams opJoinRandomRoomParams) { if (this.DebugOut >= DebugLevel.INFO) { this.Listener.DebugReturn(DebugLevel.INFO, "OpJoinRandomRoom()"); } Hashtable expectedRoomProperties = new Hashtable(); expectedRoomProperties.MergeStringKeys(opJoinRandomRoomParams.ExpectedCustomRoomProperties); if (opJoinRandomRoomParams.ExpectedMaxPlayers > 0) { expectedRoomProperties[GamePropertyKey.MaxPlayers] = opJoinRandomRoomParams.ExpectedMaxPlayers; } Dictionary opParameters = new Dictionary(); if (expectedRoomProperties.Count > 0) { opParameters[ParameterCode.GameProperties] = expectedRoomProperties; } if (opJoinRandomRoomParams.MatchingType != MatchmakingMode.FillRoom) { opParameters[ParameterCode.MatchMakingType] = (byte)opJoinRandomRoomParams.MatchingType; } if (opJoinRandomRoomParams.TypedLobby != null && !opJoinRandomRoomParams.TypedLobby.IsDefault) { opParameters[ParameterCode.LobbyName] = opJoinRandomRoomParams.TypedLobby.Name; opParameters[ParameterCode.LobbyType] = (byte)opJoinRandomRoomParams.TypedLobby.Type; } if (!string.IsNullOrEmpty(opJoinRandomRoomParams.SqlLobbyFilter)) { opParameters[ParameterCode.Data] = opJoinRandomRoomParams.SqlLobbyFilter; } if (opJoinRandomRoomParams.ExpectedUsers != null && opJoinRandomRoomParams.ExpectedUsers.Length > 0) { opParameters[ParameterCode.Add] = opJoinRandomRoomParams.ExpectedUsers; } //this.Listener.DebugReturn(DebugLevel.INFO, "OpJoinRandomRoom: " + SupportClass.DictionaryToString(opParameters)); return this.SendOperation(OperationCode.JoinRandomGame, opParameters, SendOptions.SendReliable); } /// /// Only used on the Master Server. It will assign a game server and room to join-or-create. /// On the Game Server, the OpJoin is used with option "create if not exists". /// public virtual bool OpJoinRandomOrCreateRoom(OpJoinRandomRoomParams opJoinRandomRoomParams, EnterRoomParams createRoomParams) { if (this.DebugOut >= DebugLevel.INFO) { this.Listener.DebugReturn(DebugLevel.INFO, "OpJoinRandomOrCreateRoom()"); } // join random room parameters: Hashtable expectedRoomProperties = new Hashtable(); expectedRoomProperties.MergeStringKeys(opJoinRandomRoomParams.ExpectedCustomRoomProperties); if (opJoinRandomRoomParams.ExpectedMaxPlayers > 0) { expectedRoomProperties[GamePropertyKey.MaxPlayers] = opJoinRandomRoomParams.ExpectedMaxPlayers; } Dictionary opParameters = new Dictionary(); if (expectedRoomProperties.Count > 0) { opParameters[ParameterCode.GameProperties] = expectedRoomProperties; // used as filter. below, RoomOptionsToOpParameters has usePropertiesKey = true } if (opJoinRandomRoomParams.MatchingType != MatchmakingMode.FillRoom) { opParameters[ParameterCode.MatchMakingType] = (byte)opJoinRandomRoomParams.MatchingType; } if (opJoinRandomRoomParams.TypedLobby != null && !opJoinRandomRoomParams.TypedLobby.IsDefault) { opParameters[ParameterCode.LobbyName] = opJoinRandomRoomParams.TypedLobby.Name; opParameters[ParameterCode.LobbyType] = (byte)opJoinRandomRoomParams.TypedLobby.Type; } if (!string.IsNullOrEmpty(opJoinRandomRoomParams.SqlLobbyFilter)) { opParameters[ParameterCode.Data] = opJoinRandomRoomParams.SqlLobbyFilter; } if (opJoinRandomRoomParams.ExpectedUsers != null && opJoinRandomRoomParams.ExpectedUsers.Length > 0) { opParameters[ParameterCode.Add] = opJoinRandomRoomParams.ExpectedUsers; } // parameters for creating a room if needed ("or create" part of the operation) // partial copy of OpCreateRoom opParameters[ParameterCode.JoinMode] = (byte)JoinMode.CreateIfNotExists; if (createRoomParams != null) { if (!string.IsNullOrEmpty(createRoomParams.RoomName)) { opParameters[ParameterCode.RoomName] = createRoomParams.RoomName; } this.RoomOptionsToOpParameters(opParameters, createRoomParams.RoomOptions, true); } //this.Listener.DebugReturn(DebugLevel.INFO, "OpJoinRandomOrCreateRoom: " + SupportClass.DictionaryToString(opParameters, false)); return this.SendOperation(OperationCode.JoinRandomGame, opParameters, SendOptions.SendReliable); } /// /// Leaves a room with option to come back later or "for good". /// /// Async games can be re-joined (loaded) later on. Set to false, if you want to abandon a game entirely. /// WebFlag: Securely transmit the encrypted object AuthCookie to the web service in PathLeave webhook when available /// If the opteration can be send currently. public virtual bool OpLeaveRoom(bool becomeInactive, bool sendAuthCookie = false) { Dictionary opParameters = new Dictionary(); if (becomeInactive) { opParameters[ParameterCode.IsInactive] = true; } if (sendAuthCookie) { opParameters[ParameterCode.EventForward] = WebFlags.SendAuthCookieConst; } return this.SendOperation(OperationCode.Leave, opParameters, SendOptions.SendReliable); } /// Gets a list of games matching a SQL-like where clause. /// /// Operation is only available in lobbies of type SqlLobby. /// This is an async request which triggers a OnOperationResponse() call. /// Returned game list is stored in RoomInfoList. /// /// /// The lobby to query. Has to be of type SqlLobby. /// The sql query statement. /// If the operation could be sent (has to be connected). public virtual bool OpGetGameList(TypedLobby lobby, string queryData) { if (this.DebugOut >= DebugLevel.INFO) { this.Listener.DebugReturn(DebugLevel.INFO, "OpGetGameList()"); } if (lobby == null) { if (this.DebugOut >= DebugLevel.INFO) { this.Listener.DebugReturn(DebugLevel.INFO, "OpGetGameList not sent. Lobby cannot be null."); } return false; } if (lobby.Type != LobbyType.SqlLobby) { if (this.DebugOut >= DebugLevel.INFO) { this.Listener.DebugReturn(DebugLevel.INFO, "OpGetGameList not sent. LobbyType must be SqlLobby."); } return false; } if (lobby.IsDefault) { if (this.DebugOut >= DebugLevel.INFO) { this.Listener.DebugReturn(DebugLevel.INFO, "OpGetGameList not sent. LobbyName must be not null and not empty."); } return false; } if (string.IsNullOrEmpty(queryData)) { if (this.DebugOut >= DebugLevel.INFO) { this.Listener.DebugReturn(DebugLevel.INFO, "OpGetGameList not sent. queryData must be not null and not empty."); } return false; } Dictionary opParameters = new Dictionary(); opParameters[(byte)ParameterCode.LobbyName] = lobby.Name; opParameters[(byte)ParameterCode.LobbyType] = (byte)lobby.Type; opParameters[(byte)ParameterCode.Data] = queryData; return this.SendOperation(OperationCode.GetGameList, opParameters, SendOptions.SendReliable); } /// /// Request the rooms and online status for a list of friends (each client must set a unique username via OpAuthenticate). /// /// /// Used on Master Server to find the rooms played by a selected list of users. /// Users identify themselves by using OpAuthenticate with a unique user ID. /// The list of user IDs must be fetched from some other source (not provided by Photon). /// /// The server response includes 2 arrays of info (each index matching a friend from the request):
/// ParameterCode.FindFriendsResponseOnlineList = bool[] of online states
/// ParameterCode.FindFriendsResponseRoomIdList = string[] of room names (empty string if not in a room)
///
/// The options may be used to define which state a room must match to be returned. ///
/// Array of friend's names (make sure they are unique). /// Options that affect the result of the FindFriends operation. /// If the operation could be sent (requires connection). public virtual bool OpFindFriends(string[] friendsToFind, FindFriendsOptions options = null) { Dictionary opParameters = new Dictionary(); if (friendsToFind != null && friendsToFind.Length > 0) { opParameters[ParameterCode.FindFriendsRequestList] = friendsToFind; } if (options != null) { opParameters[ParameterCode.FindFriendsOptions] = options.ToIntFlags(); } return this.SendOperation(OperationCode.FindFriends, opParameters, SendOptions.SendReliable); } public bool OpSetCustomPropertiesOfActor(int actorNr, Hashtable actorProperties) { return this.OpSetPropertiesOfActor(actorNr, actorProperties.StripToStringKeys(), null); } /// /// Sets properties of a player / actor. /// Internally this uses OpSetProperties, which can be used to either set room or player properties. /// /// The payer ID (a.k.a. actorNumber) of the player to attach these properties to. /// The properties to add or update. /// If set, these must be in the current properties-set (on the server) to set actorProperties: CAS. /// Set these to forward the properties to a WebHook as defined for this app (in Dashboard). /// If the operation could be sent (requires connection). protected internal bool OpSetPropertiesOfActor(int actorNr, Hashtable actorProperties, Hashtable expectedProperties = null, WebFlags webflags = null) { if (this.DebugOut >= DebugLevel.INFO) { this.Listener.DebugReturn(DebugLevel.INFO, "OpSetPropertiesOfActor()"); } if (actorNr <= 0 || actorProperties == null || actorProperties.Count == 0) { if (this.DebugOut >= DebugLevel.INFO) { this.Listener.DebugReturn(DebugLevel.INFO, "OpSetPropertiesOfActor not sent. ActorNr must be > 0 and actorProperties must be not null nor empty."); } return false; } Dictionary opParameters = new Dictionary(); opParameters.Add(ParameterCode.Properties, actorProperties); opParameters.Add(ParameterCode.ActorNr, actorNr); opParameters.Add(ParameterCode.Broadcast, true); if (expectedProperties != null && expectedProperties.Count != 0) { opParameters.Add(ParameterCode.ExpectedValues, expectedProperties); } if (webflags != null && webflags.HttpForward) { opParameters[ParameterCode.EventForward] = webflags.WebhookFlags; } return this.SendOperation(OperationCode.SetProperties, opParameters, SendOptions.SendReliable); } protected bool OpSetPropertyOfRoom(byte propCode, object value) { Hashtable properties = new Hashtable(); properties[propCode] = value; return this.OpSetPropertiesOfRoom(properties); } public bool OpSetCustomPropertiesOfRoom(Hashtable gameProperties) { return this.OpSetPropertiesOfRoom(gameProperties.StripToStringKeys()); } /// /// Sets properties of a room. /// Internally this uses OpSetProperties, which can be used to either set room or player properties. /// /// The properties to add or update. /// The properties expected when update occurs. (CAS : "Check And Swap") /// WebFlag to indicate if request should be forwarded as "PathProperties" webhook or not. /// If the operation could be sent (has to be connected). protected internal bool OpSetPropertiesOfRoom(Hashtable gameProperties, Hashtable expectedProperties = null, WebFlags webflags = null) { if (this.DebugOut >= DebugLevel.INFO) { this.Listener.DebugReturn(DebugLevel.INFO, "OpSetPropertiesOfRoom()"); } if (gameProperties == null || gameProperties.Count == 0) { if (this.DebugOut >= DebugLevel.INFO) { this.Listener.DebugReturn(DebugLevel.INFO, "OpSetPropertiesOfRoom not sent. gameProperties must be not null nor empty."); } return false; } Dictionary opParameters = new Dictionary(); opParameters.Add(ParameterCode.Properties, gameProperties); opParameters.Add(ParameterCode.Broadcast, true); if (expectedProperties != null && expectedProperties.Count != 0) { opParameters.Add(ParameterCode.ExpectedValues, expectedProperties); } if (webflags!=null && webflags.HttpForward) { opParameters[ParameterCode.EventForward] = webflags.WebhookFlags; } return this.SendOperation(OperationCode.SetProperties, opParameters, SendOptions.SendReliable); } /// /// Sends this app's appId and appVersion to identify this application server side. /// This is an async request which triggers a OnOperationResponse() call. /// /// /// This operation makes use of encryption, if that is established before. /// See: EstablishEncryption(). Check encryption with IsEncryptionAvailable. /// This operation is allowed only once per connection (multiple calls will have ErrorCode != Ok). /// /// Your application's name or ID to authenticate. This is assigned by Photon Cloud (webpage). /// The client's version (clients with differing client appVersions are separated and players don't meet). /// Contains all values relevant for authentication. Even without account system (external Custom Auth), the clients are allowed to identify themselves. /// Optional region code, if the client should connect to a specific Photon Cloud Region. /// Set to true on Master Server to receive "Lobby Statistics" events. /// If the operation could be sent (has to be connected). public virtual bool OpAuthenticate(string appId, string appVersion, AuthenticationValues authValues, string regionCode, bool getLobbyStatistics) { if (this.DebugOut >= DebugLevel.INFO) { this.Listener.DebugReturn(DebugLevel.INFO, "OpAuthenticate()"); } Dictionary opParameters = new Dictionary(); if (getLobbyStatistics) { // must be sent in operation, even if a Token is available opParameters[ParameterCode.LobbyStats] = true; } // shortcut, if we have a Token if (authValues != null && authValues.Token != null) { opParameters[ParameterCode.Token] = authValues.Token; return this.SendOperation(OperationCode.Authenticate, opParameters, SendOptions.SendReliable); // we don't have to encrypt, when we have a token (which is encrypted) } // without a token, we send a complete op auth opParameters[ParameterCode.AppVersion] = appVersion; opParameters[ParameterCode.ApplicationId] = appId; if (!string.IsNullOrEmpty(regionCode)) { opParameters[ParameterCode.Region] = regionCode; } if (authValues != null) { if (!string.IsNullOrEmpty(authValues.UserId)) { opParameters[ParameterCode.UserId] = authValues.UserId; } if (authValues.AuthType != CustomAuthenticationType.None) { opParameters[ParameterCode.ClientAuthenticationType] = (byte)authValues.AuthType; // if we had a token, the code above would use it. here, we send parameters: if (!string.IsNullOrEmpty(authValues.AuthGetParameters)) { opParameters[ParameterCode.ClientAuthenticationParams] = authValues.AuthGetParameters; } if (authValues.AuthPostData != null) { opParameters[ParameterCode.ClientAuthenticationData] = authValues.AuthPostData; } } } return this.SendOperation(OperationCode.Authenticate, opParameters, new SendOptions() { Reliability = true, Encrypt = true }); } /// /// Sends this app's appId and appVersion to identify this application server side. /// This is an async request which triggers a OnOperationResponse() call. /// /// /// This operation makes use of encryption, if that is established before. /// See: EstablishEncryption(). Check encryption with IsEncryptionAvailable. /// This operation is allowed only once per connection (multiple calls will have ErrorCode != Ok). /// /// Your application's name or ID to authenticate. This is assigned by Photon Cloud (webpage). /// The client's version (clients with differing client appVersions are separated and players don't meet). /// Optional authentication values. The client can set no values or a UserId or some parameters for Custom Authentication by a server. /// Optional region code, if the client should connect to a specific Photon Cloud Region. /// /// /// If the operation could be sent (has to be connected). public virtual bool OpAuthenticateOnce(string appId, string appVersion, AuthenticationValues authValues, string regionCode, EncryptionMode encryptionMode, ConnectionProtocol expectedProtocol) { if (this.DebugOut >= DebugLevel.INFO) { this.Listener.DebugReturn(DebugLevel.INFO, "OpAuthenticateOnce(): authValues = " + authValues + ", region = " + regionCode + ", encryption = " + encryptionMode); } var opParameters = new Dictionary(); // shortcut, if we have a Token if (authValues != null && authValues.Token != null) { opParameters[ParameterCode.Token] = authValues.Token; return this.SendOperation(OperationCode.AuthenticateOnce, opParameters, SendOptions.SendReliable); // we don't have to encrypt, when we have a token (which is encrypted) } if (encryptionMode == EncryptionMode.DatagramEncryption && expectedProtocol != ConnectionProtocol.Udp) { // TODO disconnect?! throw new NotSupportedException("Expected protocol set to UDP, due to encryption mode DatagramEncryption."); // TODO use some other form of callback?! } opParameters[ParameterCode.ExpectedProtocol] = (byte)expectedProtocol; opParameters[ParameterCode.EncryptionMode] = (byte)encryptionMode; opParameters[ParameterCode.AppVersion] = appVersion; opParameters[ParameterCode.ApplicationId] = appId; if (!string.IsNullOrEmpty(regionCode)) { opParameters[ParameterCode.Region] = regionCode; } if (authValues != null) { if (!string.IsNullOrEmpty(authValues.UserId)) { opParameters[ParameterCode.UserId] = authValues.UserId; } if (authValues.AuthType != CustomAuthenticationType.None) { opParameters[ParameterCode.ClientAuthenticationType] = (byte)authValues.AuthType; if (authValues.Token != null) { opParameters[ParameterCode.Token] = authValues.Token; } else { if (!string.IsNullOrEmpty(authValues.AuthGetParameters)) { opParameters[ParameterCode.ClientAuthenticationParams] = authValues.AuthGetParameters; } if (authValues.AuthPostData != null) { opParameters[ParameterCode.ClientAuthenticationData] = authValues.AuthPostData; } } } } return this.SendOperation(OperationCode.AuthenticateOnce, opParameters, new SendOptions() { Reliability = true, Encrypt = true }); } /// /// Operation to handle this client's interest groups (for events in room). /// /// /// Note the difference between passing null and byte[0]: /// null won't add/remove any groups. /// byte[0] will add/remove all (existing) groups. /// First, removing groups is executed. This way, you could leave all groups and join only the ones provided. /// /// Changes become active not immediately but when the server executes this operation (approximately RTT/2). /// /// Groups to remove from interest. Null will not remove any. A byte[0] will remove all. /// Groups to add to interest. Null will not add any. A byte[0] will add all current. /// If operation could be enqueued for sending. Sent when calling: Service or SendOutgoingCommands. public virtual bool OpChangeGroups(byte[] groupsToRemove, byte[] groupsToAdd) { if (this.DebugOut >= DebugLevel.ALL) { this.Listener.DebugReturn(DebugLevel.ALL, "OpChangeGroups()"); } Dictionary opParameters = new Dictionary(); if (groupsToRemove != null) { opParameters[(byte)ParameterCode.Remove] = groupsToRemove; } if (groupsToAdd != null) { opParameters[(byte)ParameterCode.Add] = groupsToAdd; } return this.SendOperation(OperationCode.ChangeGroups, opParameters, SendOptions.SendReliable); } /// /// Send an event with custom code/type and any content to the other players in the same room. /// /// This override explicitly uses another parameter order to not mix it up with the implementation for Hashtable only. /// Identifies this type of event (and the content). Your game's event codes can start with 0. /// Any serializable datatype (including Hashtable like the other OpRaiseEvent overloads). /// Contains (slightly) less often used options. If you pass null, the default options will be used. /// Send options for reliable, encryption etc /// If operation could be enqueued for sending. Sent when calling: Service or SendOutgoingCommands. public virtual bool OpRaiseEvent(byte eventCode, object customEventContent, RaiseEventOptions raiseEventOptions, SendOptions sendOptions) { var paramDict = this.paramDictionaryPool.Acquire(); try { if (raiseEventOptions != null) { if (raiseEventOptions.CachingOption != EventCaching.DoNotCache) { paramDict.Add(ParameterCode.Cache, (byte)raiseEventOptions.CachingOption); } switch (raiseEventOptions.CachingOption) { case EventCaching.SliceSetIndex: case EventCaching.SlicePurgeIndex: case EventCaching.SlicePurgeUpToIndex: //this.opParameters[(byte) ParameterCode.CacheSliceIndex] = // (byte) raiseEventOptions.CacheSliceIndex; return this.SendOperation(OperationCode.RaiseEvent, paramDict, sendOptions); case EventCaching.SliceIncreaseIndex: case EventCaching.RemoveFromRoomCacheForActorsLeft: return this.SendOperation(OperationCode.RaiseEvent, paramDict, sendOptions); case EventCaching.RemoveFromRoomCache: if (raiseEventOptions.TargetActors != null) { paramDict.Add(ParameterCode.ActorList, raiseEventOptions.TargetActors); } break; default: if (raiseEventOptions.TargetActors != null) { paramDict.Add(ParameterCode.ActorList, raiseEventOptions.TargetActors); } else if (raiseEventOptions.InterestGroup != 0) { paramDict.Add(ParameterCode.Group, (byte)raiseEventOptions.InterestGroup); } else if (raiseEventOptions.Receivers != ReceiverGroup.Others) { paramDict.Add(ParameterCode.ReceiverGroup, (byte)raiseEventOptions.Receivers); } if (raiseEventOptions.Flags.HttpForward) { paramDict.Add(ParameterCode.EventForward, (byte)raiseEventOptions.Flags.WebhookFlags); } break; } } paramDict.Add(ParameterCode.Code, (byte)eventCode); if (customEventContent != null) { paramDict.Add(ParameterCode.Data, (object)customEventContent); } return this.SendOperation(OperationCode.RaiseEvent, paramDict, sendOptions); } finally { this.paramDictionaryPool.Release(paramDict); } } /// /// Internally used operation to set some "per server" settings. This is for the Master Server. /// /// Set to true, to get Lobby Statistics (lists of existing lobbies). /// False if the operation could not be sent. public virtual bool OpSettings(bool receiveLobbyStats) { if (this.DebugOut >= DebugLevel.ALL) { this.Listener.DebugReturn(DebugLevel.ALL, "OpSettings()"); } Dictionary opParameters = new Dictionary(); // implementation for Master Server: if (receiveLobbyStats) { opParameters[(byte)0] = receiveLobbyStats; } if (opParameters.Count == 0) { // no need to send op in case we set the default values return true; } return this.SendOperation(OperationCode.ServerSettings, opParameters, SendOptions.SendReliable); } } /// Used in the RoomOptionFlags parameter, this bitmask toggles options in the room. internal enum RoomOptionBit : int { CheckUserOnJoin = 0x01, // toggles a check of the UserId when joining (enabling returning to a game) DeleteCacheOnLeave = 0x02, // deletes cache on leave SuppressRoomEvents = 0x04, // suppresses all room events PublishUserId = 0x08, // signals that we should publish userId DeleteNullProps = 0x10, // signals that we should remove property if its value was set to null. see RoomOption to Delete Null Properties BroadcastPropsChangeToAll = 0x20, // signals that we should send PropertyChanged event to all room players including initiator SuppressPlayerInfo = 0x40, // disables events join and leave from the server as well as property broadcasts in a room (to minimize traffic) } /// /// Options for OpFindFriends can be combined to filter which rooms of friends are returned. /// public class FindFriendsOptions { /// Include a friend's room only if it is created and confirmed by the game server. public bool CreatedOnGs = false; //flag: 0x01 /// Include a friend's room only if it is visible (using Room.IsVisible). public bool Visible = false; //flag: 0x02 /// Include a friend's room only if it is open (using Room.IsOpen). public bool Open = false; //flag: 0x04 /// Turns the bool options into an integer, which is sent as option flags for Op FindFriends. /// The options applied to bits of an integer. internal int ToIntFlags() { int optionFlags = 0; if (this.CreatedOnGs) { optionFlags = optionFlags | 0x1; } if (this.Visible) { optionFlags = optionFlags | 0x2; } if (this.Open) { optionFlags = optionFlags | 0x4; } return optionFlags; } } /// /// Parameters for the matchmaking of JoinRandomRoom and JoinRandomOrCreateRoom. /// /// /// More about matchmaking: . /// public class OpJoinRandomRoomParams { /// The custom room properties a room must have to fit. All key-values must be present to match. In SQL Lobby, use SqlLobbyFilter instead. public Hashtable ExpectedCustomRoomProperties; /// Filters by the MaxPlayers value of rooms. public byte ExpectedMaxPlayers; /// The MatchmakingMode affects how rooms get filled. By default, the server fills rooms. public MatchmakingMode MatchingType; /// The lobby in which to match. The type affects how filters are applied. public TypedLobby TypedLobby; /// SQL query to filter room matches. For default-typed lobbies, use ExpectedCustomRoomProperties instead. public string SqlLobbyFilter; /// The expected users list blocks player slots for your friends or team mates to join the room, too. /// See: https://doc.photonengine.com/en-us/pun/v2/lobby-and-matchmaking/matchmaking-and-lobby#matchmaking_slot_reservation public string[] ExpectedUsers; } /// Parameters for creating rooms. public class EnterRoomParams { /// The name of the room to create. If null, the server generates a unique name. If not null, it must be unique and new or will cause an error. public string RoomName; /// The RoomOptions define the optional behaviour of rooms. public RoomOptions RoomOptions; /// A lobby to attach the new room to. If set, this overrides a joined lobby (if any). public TypedLobby Lobby; /// The custom player properties that describe this client / user. Keys must be strings. public Hashtable PlayerProperties; /// Internally used value to skip some values when the operation is sent to the Master Server. protected internal bool OnGameServer = true; // defaults to true! better send more parameter than too few (GS needs all) /// Internally used value to check which join mode we should call. protected internal JoinMode JoinMode; /// A list of users who are expected to join the room along with this client. Reserves slots for rooms with MaxPlayers value. public string[] ExpectedUsers; } /// /// ErrorCode defines the default codes associated with Photon client/server communication. /// public class ErrorCode { /// (0) is always "OK", anything else an error or specific situation. public const int Ok = 0; // server - Photon low(er) level: <= 0 /// /// (-3) Operation can't be executed yet (e.g. OpJoin can't be called before being authenticated, RaiseEvent cant be used before getting into a room). /// /// /// Before you call any operations on the Cloud servers, the automated client workflow must complete its authorization. /// Wait until State is: JoinedLobby or ConnectedToMasterServer /// public const int OperationNotAllowedInCurrentState = -3; /// (-2) The operation you called is not implemented on the server (application) you connect to. Make sure you run the fitting applications. [Obsolete("Use InvalidOperation.")] public const int InvalidOperationCode = -2; /// (-2) The operation you called could not be executed on the server. /// /// Make sure you are connected to the server you expect. /// /// This code is used in several cases: /// The arguments/parameters of the operation might be out of range, missing entirely or conflicting. /// The operation you called is not implemented on the server (application). Server-side plugins affect the available operations. /// public const int InvalidOperation = -2; /// (-1) Something went wrong in the server. Try to reproduce and contact Exit Games. public const int InternalServerError = -1; // server - client: 0x7FFF and down // logic-level error codes start with short.max /// (32767) Authentication failed. Possible cause: AppId is unknown to Photon (in cloud service). public const int InvalidAuthentication = 0x7FFF; /// (32766) GameId (name) already in use (can't create another). Change name. public const int GameIdAlreadyExists = 0x7FFF - 1; /// (32765) Game is full. This rarely happens when some player joined the room before your join completed. public const int GameFull = 0x7FFF - 2; /// (32764) Game is closed and can't be joined. Join another game. public const int GameClosed = 0x7FFF - 3; [Obsolete("No longer used, cause random matchmaking is no longer a process.")] public const int AlreadyMatched = 0x7FFF - 4; /// (32762) All servers are busy. This is a temporary issue and the game logic should try again after a brief wait time. /// /// This error may happen for all operations that create rooms. The operation response will contain this error code. /// /// This error is very unlikely to happen as we monitor load on all servers and add them on demand. /// However, it's good to be prepared for a shortage of machines or surge in CCUs. /// public const int ServerFull = 0x7FFF - 5; /// (32761) Not in use currently. public const int UserBlocked = 0x7FFF - 6; /// (32760) Random matchmaking only succeeds if a room exists thats neither closed nor full. Repeat in a few seconds or create a new room. public const int NoRandomMatchFound = 0x7FFF - 7; /// (32758) Join can fail if the room (name) is not existing (anymore). This can happen when players leave while you join. public const int GameDoesNotExist = 0x7FFF - 9; /// (32757) Authorization on the Photon Cloud failed becaus the concurrent users (CCU) limit of the app's subscription is reached. /// /// Unless you have a plan with "CCU Burst", clients might fail the authentication step during connect. /// Affected client are unable to call operations. Please note that players who end a game and return /// to the master server will disconnect and re-connect, which means that they just played and are rejected /// in the next minute / re-connect. /// This is a temporary measure. Once the CCU is below the limit, players will be able to connect an play again. /// /// OpAuthorize is part of connection workflow but only on the Photon Cloud, this error can happen. /// Self-hosted Photon servers with a CCU limited license won't let a client connect at all. /// public const int MaxCcuReached = 0x7FFF - 10; /// (32756) Authorization on the Photon Cloud failed because the app's subscription does not allow to use a particular region's server. /// /// Some subscription plans for the Photon Cloud are region-bound. Servers of other regions can't be used then. /// Check your master server address and compare it with your Photon Cloud Dashboard's info. /// https://dashboard.photonengine.com /// /// OpAuthorize is part of connection workflow but only on the Photon Cloud, this error can happen. /// Self-hosted Photon servers with a CCU limited license won't let a client connect at all. /// public const int InvalidRegion = 0x7FFF - 11; /// /// (32755) Custom Authentication of the user failed due to setup reasons (see Cloud Dashboard) or the provided user data (like username or token). Check error message for details. /// public const int CustomAuthenticationFailed = 0x7FFF - 12; /// (32753) The Authentication ticket expired. Usually, this is refreshed behind the scenes. Connect (and authorize) again. public const int AuthenticationTicketExpired = 0x7FF1; /// /// (32752) A server-side plugin (or webhook) failed to execute and reported an error. Check the OperationResponse.DebugMessage. /// public const int PluginReportedError = 0x7FFF - 15; /// /// (32751) CreateGame/JoinGame/Join operation fails if expected plugin does not correspond to loaded one. /// public const int PluginMismatch = 0x7FFF - 16; /// /// (32750) for join requests. Indicates the current peer already called join and is joined to the room. /// public const int JoinFailedPeerAlreadyJoined = 32750; // 0x7FFF - 17, /// /// (32749) for join requests. Indicates the list of InactiveActors already contains an actor with the requested ActorNr or UserId. /// public const int JoinFailedFoundInactiveJoiner = 32749; // 0x7FFF - 18, /// /// (32748) for join requests. Indicates the list of Actors (active and inactive) did not contain an actor with the requested ActorNr or UserId. /// public const int JoinFailedWithRejoinerNotFound = 32748; // 0x7FFF - 19, /// /// (32747) for join requests. Note: for future use - Indicates the requested UserId was found in the ExcludedList. /// public const int JoinFailedFoundExcludedUserId = 32747; // 0x7FFF - 20, /// /// (32746) for join requests. Indicates the list of ActiveActors already contains an actor with the requested ActorNr or UserId. /// public const int JoinFailedFoundActiveJoiner = 32746; // 0x7FFF - 21, /// /// (32745) for SetProerties and Raisevent (if flag HttpForward is true) requests. Indicates the maximum allowd http requests per minute was reached. /// public const int HttpLimitReached = 32745; // 0x7FFF - 22, /// /// (32744) for WebRpc requests. Indicates the the call to the external service failed. /// public const int ExternalHttpCallFailed = 32744; // 0x7FFF - 23, /// /// (32743) for operations with defined limits (as in calls per second, content count or size). /// public const int OperationLimitReached = 32743; // 0x7FFF - 24, /// /// (32742) Server error during matchmaking with slot reservation. E.g. the reserved slots can not exceed MaxPlayers. /// public const int SlotError = 32742; // 0x7FFF - 25, /// /// (32741) Server will react with this error if invalid encryption parameters provided by token /// public const int InvalidEncryptionParameters = 32741; // 0x7FFF - 24, } /// /// Class for constants. These (byte) values define "well known" properties for an Actor / Player. /// /// /// These constants are used internally. /// "Custom properties" have to use a string-type as key. They can be assigned at will. /// public class ActorProperties { /// (255) Name of a player/actor. public const byte PlayerName = 255; // was: 1 /// (254) Tells you if the player is currently in this game (getting events live). /// A server-set value for async games, where players can leave the game and return later. public const byte IsInactive = 254; /// (253) UserId of the player. Sent when room gets created with RoomOptions.PublishUserId = true. public const byte UserId = 253; } /// /// Class for constants. These (byte) values are for "well known" room/game properties used in Photon LoadBalancing. /// /// /// These constants are used internally. /// "Custom properties" have to use a string-type as key. They can be assigned at will. /// public class GamePropertyKey { /// (255) Max number of players that "fit" into this room. 0 is for "unlimited". public const byte MaxPlayers = 255; /// (254) Makes this room listed or not in the lobby on master. public const byte IsVisible = 254; /// (253) Allows more players to join a room (or not). public const byte IsOpen = 253; /// (252) Current count of players in the room. Used only in the lobby on master. public const byte PlayerCount = 252; /// (251) True if the room is to be removed from room listing (used in update to room list in lobby on master) public const byte Removed = 251; /// (250) A list of the room properties to pass to the RoomInfo list in a lobby. This is used in CreateRoom, which defines this list once per room. public const byte PropsListedInLobby = 250; /// (249) Equivalent of Operation Join parameter CleanupCacheOnLeave. public const byte CleanupCacheOnLeave = 249; /// (248) Code for MasterClientId, which is synced by server. When sent as op-parameter this is (byte)203. As room property this is (byte)248. /// Tightly related to ParameterCode.MasterClientId. public const byte MasterClientId = (byte)248; /// (247) Code for ExpectedUsers in a room. Matchmaking keeps a slot open for the players with these userIDs. public const byte ExpectedUsers = (byte)247; /// (246) Player Time To Live. How long any player can be inactive (due to disconnect or leave) before the user gets removed from the playerlist (freeing a slot). public const byte PlayerTtl = (byte)246; /// (245) Room Time To Live. How long a room stays available (and in server-memory), after the last player becomes inactive. After this time, the room gets persisted or destroyed. public const byte EmptyRoomTtl = (byte)245; } /// /// Class for constants. These values are for events defined by Photon LoadBalancing. /// /// They start at 255 and go DOWN. Your own in-game events can start at 0. These constants are used internally. public class EventCode { /// (230) Initial list of RoomInfos (in lobby on Master) public const byte GameList = 230; /// (229) Update of RoomInfos to be merged into "initial" list (in lobby on Master) public const byte GameListUpdate = 229; /// (228) Currently not used. State of queueing in case of server-full public const byte QueueState = 228; /// (227) Currently not used. Event for matchmaking public const byte Match = 227; /// (226) Event with stats about this application (players, rooms, etc) public const byte AppStats = 226; /// (224) This event provides a list of lobbies with their player and game counts. public const byte LobbyStats = 224; /// (210) Internally used in case of hosting by Azure [Obsolete("TCP routing was removed after becoming obsolete.")] public const byte AzureNodeInfo = 210; /// (255) Event Join: someone joined the game. The new actorNumber is provided as well as the properties of that actor (if set in OpJoin). public const byte Join = (byte)255; /// (254) Event Leave: The player who left the game can be identified by the actorNumber. public const byte Leave = (byte)254; /// (253) When you call OpSetProperties with the broadcast option "on", this event is fired. It contains the properties being set. public const byte PropertiesChanged = (byte)253; /// (253) When you call OpSetProperties with the broadcast option "on", this event is fired. It contains the properties being set. [Obsolete("Use PropertiesChanged now.")] public const byte SetProperties = (byte)253; /// (252) When player left game unexpected and the room has a playerTtl != 0, this event is fired to let everyone know about the timeout. /// Obsolete. Replaced by Leave. public const byte Disconnect = LiteEventCode.Disconnect; /// (251) Sent by Photon Cloud when a plugin-call or webhook-call failed or events cache limit exceeded. Usually, the execution on the server continues, despite the issue. Contains: ParameterCode.Info. /// public const byte ErrorInfo = 251; /// (250) Sent by Photon whent he event cache slice was changed. Done by OpRaiseEvent. public const byte CacheSliceChanged = 250; /// (223) Sent by Photon to update a token before it times out. public const byte AuthEvent = 223; } /// Class for constants. Codes for parameters of Operations and Events. /// These constants are used internally. public class ParameterCode { /// (237) A bool parameter for creating games. If set to true, no room events are sent to the clients on join and leave. Default: false (and not sent). public const byte SuppressRoomEvents = 237; /// (236) Time To Live (TTL) for a room when the last player leaves. Keeps room in memory for case a player re-joins soon. In milliseconds. public const byte EmptyRoomTTL = 236; /// (235) Time To Live (TTL) for an 'actor' in a room. If a client disconnects, this actor is inactive first and removed after this timeout. In milliseconds. public const byte PlayerTTL = 235; /// (234) Optional parameter of OpRaiseEvent and OpSetCustomProperties to forward the event/operation to a web-service. public const byte EventForward = 234; /// (233) Optional parameter of OpLeave in async games. If false, the player does abandons the game (forever). By default players become inactive and can re-join. [Obsolete("Use: IsInactive")] public const byte IsComingBack = (byte)233; /// (233) Used in EvLeave to describe if a user is inactive (and might come back) or not. In rooms with PlayerTTL, becoming inactive is the default case. public const byte IsInactive = (byte)233; /// (232) Used when creating rooms to define if any userid can join the room only once. public const byte CheckUserOnJoin = (byte)232; /// (231) Code for "Check And Swap" (CAS) when changing properties. public const byte ExpectedValues = (byte)231; /// (230) Address of a (game) server to use. public const byte Address = 230; /// (229) Count of players in this application in a rooms (used in stats event) public const byte PeerCount = 229; /// (228) Count of games in this application (used in stats event) public const byte GameCount = 228; /// (227) Count of players on the master server (in this app, looking for rooms) public const byte MasterPeerCount = 227; /// (225) User's ID public const byte UserId = 225; /// (224) Your application's ID: a name on your own Photon or a GUID on the Photon Cloud public const byte ApplicationId = 224; /// (223) Not used currently (as "Position"). If you get queued before connect, this is your position public const byte Position = 223; /// (223) Modifies the matchmaking algorithm used for OpJoinRandom. Allowed parameter values are defined in enum MatchmakingMode. public const byte MatchMakingType = 223; /// (222) List of RoomInfos about open / listed rooms public const byte GameList = 222; /// (221) Internally used to establish encryption public const byte Token = 221; /// (220) Version of your application public const byte AppVersion = 220; /// (210) Internally used in case of hosting by Azure [Obsolete("TCP routing was removed after becoming obsolete.")] public const byte AzureNodeInfo = 210; // only used within events, so use: EventCode.AzureNodeInfo /// (209) Internally used in case of hosting by Azure [Obsolete("TCP routing was removed after becoming obsolete.")] public const byte AzureLocalNodeId = 209; /// (208) Internally used in case of hosting by Azure [Obsolete("TCP routing was removed after becoming obsolete.")] public const byte AzureMasterNodeId = 208; /// (255) Code for the gameId/roomName (a unique name per room). Used in OpJoin and similar. public const byte RoomName = (byte)255; /// (250) Code for broadcast parameter of OpSetProperties method. public const byte Broadcast = (byte)250; /// (252) Code for list of players in a room. public const byte ActorList = (byte)252; /// (254) Code of the Actor of an operation. Used for property get and set. public const byte ActorNr = (byte)254; /// (249) Code for property set (Hashtable). public const byte PlayerProperties = (byte)249; /// (245) Code of data/custom content of an event. Used in OpRaiseEvent. public const byte CustomEventContent = (byte)245; /// (245) Code of data of an event. Used in OpRaiseEvent. public const byte Data = (byte)245; /// (244) Code used when sending some code-related parameter, like OpRaiseEvent's event-code. /// This is not the same as the Operation's code, which is no longer sent as part of the parameter Dictionary in Photon 3. public const byte Code = (byte)244; /// (248) Code for property set (Hashtable). public const byte GameProperties = (byte)248; /// /// (251) Code for property-set (Hashtable). This key is used when sending only one set of properties. /// If either ActorProperties or GameProperties are used (or both), check those keys. /// public const byte Properties = (byte)251; /// (253) Code of the target Actor of an operation. Used for property set. Is 0 for game public const byte TargetActorNr = (byte)253; /// (246) Code to select the receivers of events (used in Lite, Operation RaiseEvent). public const byte ReceiverGroup = (byte)246; /// (247) Code for caching events while raising them. public const byte Cache = (byte)247; /// (241) Bool parameter of CreateGame Operation. If true, server cleans up roomcache of leaving players (their cached events get removed). public const byte CleanupCacheOnLeave = (byte)241; /// (240) Code for "group" operation-parameter (as used in Op RaiseEvent). public const byte Group = 240; /// (239) The "Remove" operation-parameter can be used to remove something from a list. E.g. remove groups from player's interest groups. public const byte Remove = 239; /// (239) Used in Op Join to define if UserIds of the players are broadcast in the room. Useful for FindFriends and reserving slots for expected users. public const byte PublishUserId = 239; /// (238) The "Add" operation-parameter can be used to add something to some list or set. E.g. add groups to player's interest groups. public const byte Add = 238; /// (218) Content for EventCode.ErrorInfo and internal debug operations. public const byte Info = 218; /// (217) This key's (byte) value defines the target custom authentication type/service the client connects with. Used in OpAuthenticate public const byte ClientAuthenticationType = 217; /// (216) This key's (string) value provides parameters sent to the custom authentication type/service the client connects with. Used in OpAuthenticate public const byte ClientAuthenticationParams = 216; /// (215) Makes the server create a room if it doesn't exist. OpJoin uses this to always enter a room, unless it exists and is full/closed. // public const byte CreateIfNotExists = 215; /// (215) The JoinMode enum defines which variant of joining a room will be executed: Join only if available, create if not exists or re-join. /// Replaces CreateIfNotExists which was only a bool-value. public const byte JoinMode = 215; /// (214) This key's (string or byte[]) value provides parameters sent to the custom authentication service setup in Photon Dashboard. Used in OpAuthenticate public const byte ClientAuthenticationData = 214; /// (203) Code for MasterClientId, which is synced by server. When sent as op-parameter this is code 203. /// Tightly related to GamePropertyKey.MasterClientId. public const byte MasterClientId = (byte)203; /// (1) Used in Op FindFriends request. Value must be string[] of friends to look up. public const byte FindFriendsRequestList = (byte)1; /// (2) Used in Op FindFriends request. An integer containing option-flags to filter the results. public const byte FindFriendsOptions = (byte)2; /// (1) Used in Op FindFriends response. Contains bool[] list of online states (false if not online). public const byte FindFriendsResponseOnlineList = (byte)1; /// (2) Used in Op FindFriends response. Contains string[] of room names ("" where not known or no room joined). public const byte FindFriendsResponseRoomIdList = (byte)2; /// (213) Used in matchmaking-related methods and when creating a room to name a lobby (to join or to attach a room to). public const byte LobbyName = (byte)213; /// (212) Used in matchmaking-related methods and when creating a room to define the type of a lobby. Combined with the lobby name this identifies the lobby. public const byte LobbyType = (byte)212; /// (211) This (optional) parameter can be sent in Op Authenticate to turn on Lobby Stats (info about lobby names and their user- and game-counts). public const byte LobbyStats = (byte)211; /// (210) Used for region values in OpAuth and OpGetRegions. public const byte Region = (byte)210; /// (209) Path of the WebRPC that got called. Also known as "WebRpc Name". Type: string. public const byte UriPath = 209; /// (208) Parameters for a WebRPC as: Dictionary<string, object>. This will get serialized to JSon. public const byte WebRpcParameters = 208; /// (207) ReturnCode for the WebRPC, as sent by the web service (not by Photon, which uses ErrorCode). Type: byte. public const byte WebRpcReturnCode = 207; /// (206) Message returned by WebRPC server. Analog to Photon's debug message. Type: string. public const byte WebRpcReturnMessage = 206; /// (205) Used to define a "slice" for cached events. Slices can easily be removed from cache. Type: int. public const byte CacheSliceIndex = 205; /// (204) Informs the server of the expected plugin setup. /// /// The operation will fail in case of a plugin mismatch returning error code PluginMismatch 32751(0x7FFF - 16). /// Setting string[]{} means the client expects no plugin to be setup. /// Note: for backwards compatibility null omits any check. /// public const byte Plugins = 204; /// (202) Used by the server in Operation Responses, when it sends the nickname of the client (the user's nickname). public const byte NickName = 202; /// (201) Informs user about name of plugin load to game public const byte PluginName = 201; /// (200) Informs user about version of plugin load to game public const byte PluginVersion = 200; /// (196) Cluster info provided in OpAuthenticate/OpAuthenticateOnce responses. public const byte Cluster = 196; /// (195) Protocol which will be used by client to connect master/game servers. Used for nameserver. public const byte ExpectedProtocol = 195; /// (194) Set of custom parameters which are sent in auth request. public const byte CustomInitData = 194; /// (193) How are we going to encrypt data. public const byte EncryptionMode = 193; /// (192) Parameter of Authentication, which contains encryption keys (depends on AuthMode and EncryptionMode). public const byte EncryptionData = 192; /// (191) An int parameter summarizing several boolean room-options with bit-flags. public const byte RoomOptionFlags = 191; } /// /// Class for constants. Contains operation codes. /// /// These constants are used internally. public class OperationCode { [Obsolete("Exchanging encrpytion keys is done internally in the lib now. Don't expect this operation-result.")] public const byte ExchangeKeysForEncryption = 250; /// (255) Code for OpJoin, to get into a room. [Obsolete] public const byte Join = 255; /// (231) Authenticates this peer and connects to a virtual application public const byte AuthenticateOnce = 231; /// (230) Authenticates this peer and connects to a virtual application public const byte Authenticate = 230; /// (229) Joins lobby (on master) public const byte JoinLobby = 229; /// (228) Leaves lobby (on master) public const byte LeaveLobby = 228; /// (227) Creates a game (or fails if name exists) public const byte CreateGame = 227; /// (226) Join game (by name) public const byte JoinGame = 226; /// (225) Joins random game (on master) public const byte JoinRandomGame = 225; // public const byte CancelJoinRandom = 224; // obsolete, cause JoinRandom no longer is a "process". now provides result immediately /// (254) Code for OpLeave, to get out of a room. public const byte Leave = (byte)254; /// (253) Raise event (in a room, for other actors/players) public const byte RaiseEvent = (byte)253; /// (252) Set Properties (of room or actor/player) public const byte SetProperties = (byte)252; /// (251) Get Properties public const byte GetProperties = (byte)251; /// (248) Operation code to change interest groups in Rooms (Lite application and extending ones). public const byte ChangeGroups = (byte)248; /// (222) Request the rooms and online status for a list of friends (by name, which should be unique). public const byte FindFriends = 222; /// (221) Request statistics about a specific list of lobbies (their user and game count). public const byte GetLobbyStats = 221; /// (220) Get list of regional servers from a NameServer. public const byte GetRegions = 220; /// (219) WebRpc Operation. public const byte WebRpc = 219; /// (218) Operation to set some server settings. Used with different parameters on various servers. public const byte ServerSettings = 218; /// (217) Get the game list matching a supplied sql filter (SqlListLobby only) public const byte GetGameList = 217; } /// Defines possible values for OpJoinRoom and OpJoinOrCreate. It tells the server if the room can be only be joined normally, created implicitly or found on a web-service for Turnbased games. /// These values are not directly used by a game but implicitly set. public enum JoinMode : byte { /// Regular join. The room must exist. Default = 0, /// Join or create the room if it's not existing. Used for OpJoinOrCreate for example. CreateIfNotExists = 1, /// The room might be out of memory and should be loaded (if possible) from a Turnbased web-service. JoinOrRejoin = 2, /// Only re-join will be allowed. If the user is not yet in the room, this will fail. RejoinOnly = 3, } /// /// Options for matchmaking rules for OpJoinRandom. /// public enum MatchmakingMode : byte { /// Fills up rooms (oldest first) to get players together as fast as possible. Default. /// Makes most sense with MaxPlayers > 0 and games that can only start with more players. FillRoom = 0, /// Distributes players across available rooms sequentially but takes filter into account. Without filter, rooms get players evenly distributed. SerialMatching = 1, /// Joins a (fully) random room. Expected properties must match but aside from this, any available room might be selected. RandomMatching = 2 } /// /// Lite - OpRaiseEvent lets you chose which actors in the room should receive events. /// By default, events are sent to "Others" but you can overrule this. /// public enum ReceiverGroup : byte { /// Default value (not sent). Anyone else gets my event. Others = 0, /// Everyone in the current room (including this peer) will get this event. All = 1, /// The server sends this event only to the actor with the lowest actorNumber. /// The "master client" does not have special rights but is the one who is in this room the longest time. MasterClient = 2, } /// /// Lite - OpRaiseEvent allows you to cache events and automatically send them to joining players in a room. /// Events are cached per event code and player: Event 100 (example!) can be stored once per player. /// Cached events can be modified, replaced and removed. /// /// /// Caching works only combination with ReceiverGroup options Others and All. /// public enum EventCaching : byte { /// Default value (not sent). DoNotCache = 0, /// Will merge this event's keys with those already cached. [Obsolete] MergeCache = 1, /// Replaces the event cache for this eventCode with this event's content. [Obsolete] ReplaceCache = 2, /// Removes this event (by eventCode) from the cache. [Obsolete] RemoveCache = 3, /// Adds an event to the room's cache AddToRoomCache = 4, /// Adds this event to the cache for actor 0 (becoming a "globally owned" event in the cache). AddToRoomCacheGlobal = 5, /// Remove fitting event from the room's cache. RemoveFromRoomCache = 6, /// Removes events of players who already left the room (cleaning up). RemoveFromRoomCacheForActorsLeft = 7, /// Increase the index of the sliced cache. SliceIncreaseIndex = 10, /// Set the index of the sliced cache. You must set RaiseEventOptions.CacheSliceIndex for this. SliceSetIndex = 11, /// Purge cache slice with index. Exactly one slice is removed from cache. You must set RaiseEventOptions.CacheSliceIndex for this. SlicePurgeIndex = 12, /// Purge cache slices with specified index and anything lower than that. You must set RaiseEventOptions.CacheSliceIndex for this. SlicePurgeUpToIndex = 13, } /// /// Flags for "types of properties", being used as filter in OpGetProperties. /// [Flags] public enum PropertyTypeFlag : byte { /// (0x00) Flag type for no property type. None = 0x00, /// (0x01) Flag type for game-attached properties. Game = 0x01, /// (0x02) Flag type for actor related propeties. Actor = 0x02, /// (0x01) Flag type for game AND actor properties. Equal to 'Game' GameAndActor = Game | Actor } /// Wraps up common room properties needed when you create rooms. Read the individual entries for more details. /// This directly maps to the fields in the Room class. public class RoomOptions { /// Defines if this room is listed in the lobby. If not, it also is not joined randomly. /// /// A room that is not visible will be excluded from the room lists that are sent to the clients in lobbies. /// An invisible room can be joined by name but is excluded from random matchmaking. /// /// Use this to "hide" a room and simulate "private rooms". Players can exchange a roomname and create it /// invisble to avoid anyone else joining it. /// public bool IsVisible { get { return this.isVisible; } set { this.isVisible = value; } } private bool isVisible = true; /// Defines if this room can be joined at all. /// /// If a room is closed, no player can join this. As example this makes sense when 3 of 4 possible players /// start their gameplay early and don't want anyone to join during the game. /// The room can still be listed in the lobby (set isVisible to control lobby-visibility). /// public bool IsOpen { get { return this.isOpen; } set { this.isOpen = value; } } private bool isOpen = true; /// Max number of players that can be in the room at any time. 0 means "no limit". public byte MaxPlayers; /// Time To Live (TTL) for an 'actor' in a room. If a client disconnects, this actor is inactive first and removed after this timeout. In milliseconds. public int PlayerTtl; /// Time To Live (TTL) for a room when the last player leaves. Keeps room in memory for case a player re-joins soon. In milliseconds. public int EmptyRoomTtl; /// Removes a user's events and properties from the room when a user leaves. /// /// This makes sense when in rooms where players can't place items in the room and just vanish entirely. /// When you disable this, the event history can become too long to load if the room stays in use indefinitely. /// Default: true. Cleans up the cache and props of leaving users. /// public bool CleanupCacheOnLeave { get { return this.cleanupCacheOnLeave; } set { this.cleanupCacheOnLeave = value; } } private bool cleanupCacheOnLeave = true; /// The room's custom properties to set. Use string keys! /// /// Custom room properties are any key-values you need to define the game's setup. /// The shorter your keys are, the better. /// Example: Map, Mode (could be "m" when used with "Map"), TileSet (could be "t"). /// public Hashtable CustomRoomProperties; /// Defines the custom room properties that get listed in the lobby. /// /// Name the custom room properties that should be available to clients that are in a lobby. /// Use with care. Unless a custom property is essential for matchmaking or user info, it should /// not be sent to the lobby, which causes traffic and delays for clients in the lobby. /// /// Default: No custom properties are sent to the lobby. /// public string[] CustomRoomPropertiesForLobby = new string[0]; /// Informs the server of the expected plugin setup. /// /// The operation will fail in case of a plugin missmatch returning error code PluginMismatch 32757(0x7FFF - 10). /// Setting string[]{} means the client expects no plugin to be setup. /// Note: for backwards compatibility null omits any check. /// public string[] Plugins; /// /// Tells the server to skip room events for joining and leaving players. /// /// /// Using this makes the client unaware of the other players in a room. /// That can save some traffic if you have some server logic that updates players /// but it can also limit the client's usability. /// public bool SuppressRoomEvents { get; set; } /// Disables events join and leave from the server as well as property broadcasts in a room (to minimize traffic) public bool SuppressPlayerInfo { get; set; } /// /// Defines if the UserIds of players get "published" in the room. Useful for FindFriends, if players want to play another game together. /// /// /// When you set this to true, Photon will publish the UserIds of the players in that room. /// In that case, you can use PhotonPlayer.userId, to access any player's userID. /// This is useful for FindFriends and to set "expected users" to reserve slots in a room. /// public bool PublishUserId { get; set; } /// Optionally, properties get deleted, when null gets assigned as value. Defaults to off / false. /// /// When Op SetProperties is setting a key's value to null, the server and clients should remove the key/value from the Custom Properties. /// By default, the server keeps the keys (and null values) and sends them to joining players. /// /// Important: Only when SetProperties does a "broadcast", the change (key, value = null) is sent to clients to update accordingly. /// This applies to Custom Properties for rooms and actors/players. /// public bool DeleteNullProperties { get; set; } /// By default, property changes are sent back to the client that's setting them to avoid de-sync when properties are set concurrently. /// /// This option is enables by default to fix this scenario: /// /// 1) On server, room property ABC is set to value FOO, which triggers notifications to all the clients telling them that the property changed. /// 2) While that notification is in flight, a client sets the ABC property to value BAR. /// 3) Client receives notification from the server and changes it�s local copy of ABC to FOO. /// 4) Server receives the set operation and changes the official value of ABC to BAR, but never notifies the client that sent the set operation that the value is now BAR. /// /// Without this option, the client that set the value to BAR never hears from the server that the official copy has been updated to BAR, and thus gets stuck with a value of FOO. /// public bool BroadcastPropsChangeToAll { get { return this.broadcastPropsChangeToAll; } set { this.broadcastPropsChangeToAll = value; } } private bool broadcastPropsChangeToAll = true; #if SERVERSDK public bool CheckUserOnJoin { get; set; } #endif } /// Aggregates several less-often used options for operation RaiseEvent. See field descriptions for usage details. public class RaiseEventOptions { /// Default options: CachingOption: DoNotCache, InterestGroup: 0, targetActors: null, receivers: Others, sequenceChannel: 0. public readonly static RaiseEventOptions Default = new RaiseEventOptions(); /// Defines if the server should simply send the event, put it in the cache or remove events that are like this one. /// /// When using option: SliceSetIndex, SlicePurgeIndex or SlicePurgeUpToIndex, set a CacheSliceIndex. All other options except SequenceChannel get ignored. /// public EventCaching CachingOption; /// The number of the Interest Group to send this to. 0 goes to all users but to get 1 and up, clients must subscribe to the group first. public byte InterestGroup; /// A list of Player.ActorNumbers to send this event to. You can implement events that just go to specific users this way. public int[] TargetActors; /// Sends the event to All, MasterClient or Others (default). Be careful with MasterClient, as the client might disconnect before it got the event and it gets lost. public ReceiverGroup Receivers; /// Events are ordered per "channel". If you have events that are independent of others, they can go into another sequence or channel. [Obsolete("Not used where SendOptions are a parameter too. Use SendOptions.Channel instead.")] public byte SequenceChannel; /// Optional flags to be used in Photon client SDKs with Op RaiseEvent and Op SetProperties. /// Introduced mainly for webhooks 1.2 to control behavior of forwarded HTTP requests. public WebFlags Flags = WebFlags.Default; ///// Used along with CachingOption SliceSetIndex, SlicePurgeIndex or SlicePurgeUpToIndex if you want to set or purge a specific cache-slice. //public int CacheSliceIndex; } /// Types of lobbies define their behaviour and capabilities. Check each value for details. /// Values of this enum must be matched by the server. public enum LobbyType :byte { /// Standard type and behaviour: While joined to this lobby clients get room-lists and JoinRandomRoom can use a simple filter to match properties (perfectly). Default = 0, /// This lobby type lists rooms like Default but JoinRandom has a parameter for SQL-like "where" clauses for filtering. This allows bigger, less, or and and combinations. SqlLobby = 2, /// This lobby does not send lists of games. It is only used for OpJoinRandomRoom. It keeps rooms available for a while when there are only inactive users left. AsyncRandomLobby = 3 } /// Refers to a specific lobby on the server. /// /// Name and Type combined are the unique identifier for a lobby.
/// The server will create lobbies "on demand", so no registration or setup is required.
/// An empty or null Name always points to the "default lobby" as special case. ///
public class TypedLobby { /// /// Name of the lobby. Default: null, pointing to the "default lobby". /// /// /// If Name is null or empty, a TypedLobby will point to the "default lobby". This ignores the Type value and always acts as . /// public string Name; /// /// Type (and behaviour) of the lobby. /// /// /// An empty or null Name always points to the "default lobby" as special case. /// public LobbyType Type; /// /// A reference to the default lobby which is the unique lobby that uses null as name and is of type . /// /// /// There is only a single lobby with an empty name on the server. It is always of type .
/// On the other hand, this is a shortcut and reusable reference to the default lobby.
/// Do not change Name or Type.
///
public static readonly TypedLobby Default = new TypedLobby(); /// /// Returns whether or not this instance points to the "default lobby" (). /// /// /// This comes up to checking if the Name is null or empty. /// is not the same thing as the "default lobby" (). /// public bool IsDefault { get { return string.IsNullOrEmpty(this.Name); } } /// /// Creates a TypedLobby instance. Unless Name is changed, this points to the "default lobby" (). /// internal TypedLobby() { } /// /// Sets Name and Type of the new instance. Make sure name is not empty or null, as that always points to the "default lobby" (). /// /// Some string to identify a lobby. /// The type of a lobby defines it's capabilities and behaviour. public TypedLobby(string name, LobbyType type) { this.Name = name; this.Type = type; } public override string ToString() { return string.Format("lobby '{0}'[{1}]", this.Name, this.Type); } } /// /// Info for a lobby on the server. Used when is true. /// public class TypedLobbyInfo : TypedLobby { /// Count of players that currently joined this lobby. public int PlayerCount; /// Count of rooms currently associated with this lobby. public int RoomCount; public override string ToString() { return string.Format("TypedLobbyInfo '{0}'[{1}] rooms: {2} players: {3}", this.Name, this.Type, this.RoomCount, this.PlayerCount); } } /// /// Options for authentication modes. From "classic" auth on each server to AuthOnce (on NameServer). /// public enum AuthModeOption { Auth, AuthOnce, AuthOnceWss } /// /// Options for optional "Custom Authentication" services used with Photon. Used by OpAuthenticate after connecting to Photon. /// public enum CustomAuthenticationType : byte { /// Use a custom authentication service. Currently the only implemented option. Custom = 0, /// Authenticates users by their Steam Account. Set Steam's ticket as "ticket" via AddAuthParameter(). Steam = 1, /// Authenticates users by their Facebook Account. Set Facebooks's tocken as "token" via AddAuthParameter(). Facebook = 2, /// Authenticates users by their Oculus Account and token. Set Oculus' userid as "userid" and nonce as "nonce" via AddAuthParameter(). Oculus = 3, /// Authenticates users by their PSN Account and token on PS4. Set token as "token", env as "env" and userName as "userName" via AddAuthParameter(). PlayStation4 = 4, [Obsolete("Use PlayStation4 or PlayStation5 as needed")] PlayStation = 4, /// Authenticates users by their Xbox Account. Pass the XSTS token via SetAuthPostData(). Xbox = 5, /// Authenticates users by their HTC Viveport Account. Set userToken as "userToken" via AddAuthParameter(). Viveport = 10, /// Authenticates users by their NSA ID. Set token as "token" and appversion as "appversion" via AddAuthParameter(). The appversion is optional. NintendoSwitch = 11, /// Authenticates users by their PSN Account and token on PS5. Set token as "token", env as "env" and userName as "userName" via AddAuthParameter(). PlayStation5 = 12, [Obsolete("Use PlayStation4 or PlayStation5 as needed")] Playstation5 = 12, /// Authenticates users with Epic Online Services (EOS). Set token as "token" and ownershipToken as "ownershipToken" via AddAuthParameter(). The ownershipToken is optional. Epic = 13, /// Authenticates users with Facebook Gaming api. Set token as "token" via AddAuthParameter(). FacebookGaming = 15, /// Disables custom authentication. Same as not providing any AuthenticationValues for connect (more precisely for: OpAuthenticate). None = byte.MaxValue } /// /// Container for user authentication in Photon. Set AuthValues before you connect - all else is handled. /// /// /// On Photon, user authentication is optional but can be useful in many cases. /// If you want to FindFriends, a unique ID per user is very practical. /// /// There are basically three options for user authentication: None at all, the client sets some UserId /// or you can use some account web-service to authenticate a user (and set the UserId server-side). /// /// Custom Authentication lets you verify end-users by some kind of login or token. It sends those /// values to Photon which will verify them before granting access or disconnecting the client. /// /// The AuthValues are sent in OpAuthenticate when you connect, so they must be set before you connect. /// If the AuthValues.UserId is null or empty when it's sent to the server, then the Photon Server assigns a UserId! /// /// The Photon Cloud Dashboard will let you enable this feature and set important server values for it. /// https://dashboard.photonengine.com /// public class AuthenticationValues { /// See AuthType. private CustomAuthenticationType authType = CustomAuthenticationType.None; /// The type of authentication provider that should be used. Defaults to None (no auth whatsoever). /// Several auth providers are available and CustomAuthenticationType.Custom can be used if you build your own service. public CustomAuthenticationType AuthType { get { return authType; } set { authType = value; } } /// This string must contain any (http get) parameters expected by the used authentication service. By default, username and token. /// /// Maps to operation parameter 216. /// Standard http get parameters are used here and passed on to the service that's defined in the server (Photon Cloud Dashboard). /// public string AuthGetParameters { get; set; } /// Data to be passed-on to the auth service via POST. Default: null (not sent). Either string or byte[] (see setters). /// Maps to operation parameter 214. public object AuthPostData { get; private set; } /// Internal Photon token. After initial authentication, Photon provides a token for this client, subsequently used as (cached) validation. /// Any token for custom authentication should be set via SetAuthPostData or AddAuthParameter. public object Token { get; protected internal set; } /// The UserId should be a unique identifier per user. This is for finding friends, etc.. /// See remarks of AuthValues for info about how this is set and used. public string UserId { get; set; } /// Creates empty auth values without any info. public AuthenticationValues() { } /// Creates minimal info about the user. If this is authenticated or not, depends on the set AuthType. /// Some UserId to set in Photon. public AuthenticationValues(string userId) { this.UserId = userId; } /// Sets the data to be passed-on to the auth service via POST. /// AuthPostData is just one value. Each SetAuthPostData replaces any previous value. It can be either a string, a byte[] or a dictionary. /// String data to be used in the body of the POST request. Null or empty string will set AuthPostData to null. public virtual void SetAuthPostData(string stringData) { this.AuthPostData = (string.IsNullOrEmpty(stringData)) ? null : stringData; } /// Sets the data to be passed-on to the auth service via POST. /// AuthPostData is just one value. Each SetAuthPostData replaces any previous value. It can be either a string, a byte[] or a dictionary. /// Binary token / auth-data to pass on. public virtual void SetAuthPostData(byte[] byteData) { this.AuthPostData = byteData; } /// Sets data to be passed-on to the auth service as Json (Content-Type: "application/json") via Post. /// AuthPostData is just one value. Each SetAuthPostData replaces any previous value. It can be either a string, a byte[] or a dictionary. /// A authentication-data dictionary will be converted to Json and passed to the Auth webservice via HTTP Post. public virtual void SetAuthPostData(Dictionary dictData) { this.AuthPostData = dictData; } /// Adds a key-value pair to the get-parameters used for Custom Auth (AuthGetParameters). /// This method does uri-encoding for you. /// Key for the value to set. /// Some value relevant for Custom Authentication. public virtual void AddAuthParameter(string key, string value) { string ampersand = string.IsNullOrEmpty(this.AuthGetParameters) ? "" : "&"; this.AuthGetParameters = string.Format("{0}{1}{2}={3}", this.AuthGetParameters, ampersand, System.Uri.EscapeDataString(key), System.Uri.EscapeDataString(value)); } /// /// Transform this object into string. /// /// String info about this object's values. public override string ToString() { return string.Format("AuthenticationValues = AuthType: {0} UserId: {1}{2}{3}{4}", this.AuthType, this.UserId, string.IsNullOrEmpty(this.AuthGetParameters) ? " GetParameters: yes" : "", this.AuthPostData == null ? "" : " PostData: yes", this.Token == null ? "" : " Token: yes"); } /// /// Make a copy of the current object. /// /// The object to be copied into. /// The copied object. public AuthenticationValues CopyTo(AuthenticationValues copy) { copy.AuthType = this.AuthType; copy.AuthGetParameters = this.AuthGetParameters; copy.AuthPostData = this.AuthPostData; copy.UserId = this.UserId; return copy; } } }