/// <summary> /// Updates and synchronizes this Player's Custom Properties. Optionally, expectedProperties can be provided as condition. /// </summary> /// <remarks> /// Custom Properties are a set of string keys and arbitrary values which is synchronized /// for the players in a Room. They are available when the client enters the room, as /// they are in the response of OpJoin and OpCreate. /// /// Custom Properties either relate to the (current) Room or a Player (in that Room). /// /// Both classes locally cache the current key/values and make them available as /// property: CustomProperties. This is provided only to read them. /// You must use the method SetCustomProperties to set/modify them. /// /// Any client can set any Custom Properties anytime (when in a room). /// It's up to the game logic to organize how they are best used. /// /// You should call SetCustomProperties only with key/values that are new or changed. This reduces /// traffic and performance. /// /// Unless you define some expectedProperties, setting key/values is always permitted. /// In this case, the property-setting client will not receive the new values from the server but /// instead update its local cache in SetCustomProperties. /// /// If you define expectedProperties, the server will skip updates if the server property-cache /// does not contain all expectedProperties with the same values. /// In this case, the property-setting client will get an update from the server and update it's /// cached key/values at about the same time as everyone else. /// /// The benefit of using expectedProperties can be only one client successfully sets a key from /// one known value to another. /// As example: Store who owns an item in a Custom Property "ownedBy". It's 0 initally. /// When multiple players reach the item, they all attempt to change "ownedBy" from 0 to their /// actorNumber. If you use expectedProperties {"ownedBy", 0} as condition, the first player to /// take the item will have it (and the others fail to set the ownership). /// /// Properties get saved with the game state for Turnbased games (which use IsPersistent = true). /// </remarks> /// <param name="propertiesToSet">Hashtable of Custom Properties to be set. </param> /// <param name="expectedValues">If non-null, these are the property-values the server will check as condition for this update.</param> /// <param name="webFlags">Defines if this SetCustomProperties-operation gets forwarded to your WebHooks. Client must be in room.</param> public void SetCustomProperties(Hashtable propertiesToSet, Hashtable expectedValues = null, WebFlags webFlags = null) { if (propertiesToSet == null) { return; } Hashtable customProps = propertiesToSet.StripToStringKeys() as Hashtable; Hashtable customPropsToCheck = expectedValues.StripToStringKeys() as Hashtable; // no expected values -> set and callback bool noCas = customPropsToCheck == null || customPropsToCheck.Count == 0; if (noCas) { this.CustomProperties.Merge(customProps); this.CustomProperties.StripKeysWithNullValues(); } // send (sync) these new values if in room if (this.RoomReference != null && this.RoomReference.IsLocalClientInside) { this.RoomReference.LoadBalancingClient.loadBalancingPeer.OpSetPropertiesOfActor(this.actorID, customProps, customPropsToCheck, webFlags); } }
/// <summary>Uses OpSetPropertiesOfActor to sync this player's NickName (server is being updated with this.NickName).</summary> private void SetPlayerNameProperty() { if (this.RoomReference != null && this.RoomReference.IsLocalClientInside) { Hashtable properties = new Hashtable(); properties[ActorProperties.PlayerName] = this.nickName; this.RoomReference.LoadBalancingClient.loadBalancingPeer.OpSetPropertiesOfActor(this.ID, properties); } }
/// <summary> /// Creates a player instance. /// To extend and replace this Player, override LoadBalancingPeer.CreatePlayer(). /// </summary> /// <param name="nickName">NickName of the player (a "well known property").</param> /// <param name="actorID">ID or ActorNumber of this player in the current room (a shortcut to identify each player in room)</param> /// <param name="isLocal">If this is the local peer's player (or a remote one).</param> /// <param name="playerProperties">A Hashtable of custom properties to be synced. Must use String-typed keys and serializable datatypes as values.</param> protected internal Player(string nickName, int actorID, bool isLocal, Hashtable playerProperties) { this.IsLocal = isLocal; this.actorID = actorID; this.NickName = nickName; this.CustomProperties = new Hashtable(); this.InternalCacheProperties(playerProperties); }
/// <summary> /// Attempts to remove all current expected users from the server's Slot Reservation list. /// </summary> /// <remarks> /// Note that this operation can conflict with new/other users joining. They might be /// adding users to the list of expected users before or after this client called ClearExpectedUsers. /// /// This room's expectedUsers value will update, when the server sends a successful update. /// /// Internals: This methods wraps up setting the ExpectedUsers property of a room. /// </remarks> public void ClearExpectedUsers() { Hashtable props = new Hashtable(); props[GamePropertyKey.ExpectedUsers] = new string[0]; Hashtable expected = new Hashtable(); expected[GamePropertyKey.ExpectedUsers] = this.ExpectedUsers; this.LoadBalancingClient.OpSetPropertiesOfRoom(props, expected); }
/// <summary> /// Enables you to define the properties available in the lobby if not all properties are needed to pick a room. /// </summary> /// <remarks> /// Limit the amount of properties sent to users in the lobby to improve speed and stability. /// </remarks> /// <param name="propertiesListedInLobby">An array of custom room property names to forward to the lobby.</param> public void SetPropertiesListedInLobby(string[] propertiesListedInLobby) { Hashtable customProps = new Hashtable(); customProps[GamePropertyKey.PropsListedInLobby] = propertiesListedInLobby; bool sent = this.LoadBalancingClient.OpSetPropertiesOfRoom(customProps); if (sent) { this.propertiesListedInLobby = propertiesListedInLobby; } }
/// <summary> /// This method copies all string-typed keys of the original into a new Hashtable. /// </summary> /// <remarks> /// Does not recurse (!) into hashes that might be values in the root-hash. /// This does not modify the original. /// </remarks> /// <param name="original">The original IDictonary to get string-typed keys from.</param> /// <returns>New Hashtable containing only string-typed keys of the original.</returns> public static Hashtable StripToStringKeys(this IDictionary original) { Hashtable target = new Hashtable(); if (original != null) { foreach (object key in original.Keys) { if (key is string) { target[key] = original[key]; } } } return(target); }
/// <summary> /// Updates and synchronizes this Room's Custom Properties. Optionally, expectedProperties can be provided as condition. /// </summary> /// <remarks> /// Custom Properties are a set of string keys and arbitrary values which is synchronized /// for the players in a Room. They are available when the client enters the room, as /// they are in the response of OpJoin and OpCreate. /// /// Custom Properties either relate to the (current) Room or a Player (in that Room). /// /// Both classes locally cache the current key/values and make them available as /// property: CustomProperties. This is provided only to read them. /// You must use the method SetCustomProperties to set/modify them. /// /// Any client can set any Custom Properties anytime (when in a room). /// It's up to the game logic to organize how they are best used. /// /// You should call SetCustomProperties only with key/values that are new or changed. This reduces /// traffic and performance. /// /// Unless you define some expectedProperties, setting key/values is always permitted. /// In this case, the property-setting client will not receive the new values from the server but /// instead update its local cache in SetCustomProperties. /// /// If you define expectedProperties, the server will skip updates if the server property-cache /// does not contain all expectedProperties with the same values. /// In this case, the property-setting client will get an update from the server and update it's /// cached key/values at about the same time as everyone else. /// /// The benefit of using expectedProperties can be only one client successfully sets a key from /// one known value to another. /// As example: Store who owns an item in a Custom Property "ownedBy". It's 0 initally. /// When multiple players reach the item, they all attempt to change "ownedBy" from 0 to their /// actorNumber. If you use expectedProperties {"ownedBy", 0} as condition, the first player to /// take the item will have it (and the others fail to set the ownership). /// /// Properties get saved with the game state for Turnbased games (which use IsPersistent = true). /// </remarks> /// <param name="propertiesToSet">Hashtable of Custom Properties that changes.</param> /// <param name="expectedProperties">Provide some keys/values to use as condition for setting the new values. Client must be in room.</param> /// <param name="webFlags">Defines if this SetCustomProperties-operation gets forwarded to your WebHooks. Client must be in room.</param> public virtual void SetCustomProperties(Hashtable propertiesToSet, Hashtable expectedProperties = null, WebFlags webFlags = null) { Hashtable customProps = propertiesToSet.StripToStringKeys() as Hashtable; // merge (and delete null-values), unless we use CAS (expected props) if (expectedProperties == null || expectedProperties.Count == 0) { this.CustomProperties.Merge(customProps); this.CustomProperties.StripKeysWithNullValues(); } // send (sync) these new values if in room if (this.IsLocalClientInside) { this.LoadBalancingClient.loadBalancingPeer.OpSetPropertiesOfRoom(customProps, expectedProperties, webFlags); } }
/// <summary> /// Asks the server to assign another player as Master Client of your current room. /// </summary> /// <remarks> /// RaiseEvent has the option to send messages only to the Master Client of a room. /// SetMasterClient affects which client gets those messages. /// /// This method calls an operation on the server to set a new Master Client, which takes a roundtrip. /// In case of success, this client and the others get the new Master Client from the server. /// /// SetMasterClient tells the server which current Master Client should be replaced with the new one. /// It will fail, if anything switches the Master Client moments earlier. There is no callback for this /// error. All clients should get the new Master Client assigned by the server anyways. /// /// See also: MasterClientId /// </remarks> /// <param name="masterClientPlayer">The player to become the next Master Client.</param> /// <returns>False when this operation couldn't be done currently. Requires a v4 Photon Server.</returns> public bool SetMasterClient(Player masterClientPlayer) { if (!this.IsLocalClientInside) { this.LoadBalancingClient.DebugReturn(DebugLevel.WARNING, "SetMasterClient can only be called for the current room (being in one)."); return(false); } Hashtable newProps = new Hashtable() { { GamePropertyKey.MasterClientId, masterClientPlayer.ID } }; Hashtable prevProps = new Hashtable() { { GamePropertyKey.MasterClientId, this.MasterClientId } }; return(this.LoadBalancingClient.OpSetPropertiesOfRoom(newProps, prevProps)); }
/// <summary>Caches properties for new Players or when updates of remote players are received. Use SetCustomProperties() for a synced update.</summary> /// <remarks> /// This only updates the CustomProperties and doesn't send them to the server. /// Mostly used when creating new remote players, where the server sends their properties. /// </remarks> public virtual void InternalCacheProperties(Hashtable properties) { if (properties == null || properties.Count == 0 || this.CustomProperties.Equals(properties)) { return; } if (properties.ContainsKey(ActorProperties.PlayerName)) { string nameInServersProperties = (string)properties[ActorProperties.PlayerName]; if (nameInServersProperties != null) { if (this.IsLocal) { // the local playername is different than in the properties coming from the server // so the local nickName was changed and the server is outdated -> update server // update property instead of using the outdated nickName coming from server if (!nameInServersProperties.Equals(this.nickName)) { this.SetPlayerNameProperty(); } } else { this.NickName = nameInServersProperties; } } } if (properties.ContainsKey(ActorProperties.UserId)) { this.UserId = (string)properties[ActorProperties.UserId]; } if (properties.ContainsKey(ActorProperties.IsInactive)) { this.IsInactive = (bool)properties[ActorProperties.IsInactive]; //TURNBASED new well-known propery for players } this.CustomProperties.MergeStringKeys(properties); this.CustomProperties.StripKeysWithNullValues(); }
/// <summary>Copies "well known" properties to fields (IsVisible, etc) and caches the custom properties (string-keys only) in a local hashtable.</summary> /// <param name="propertiesToCache">New or updated properties to store in this RoomInfo.</param> protected internal virtual void InternalCacheProperties(Hashtable propertiesToCache) { if (propertiesToCache == null || propertiesToCache.Count == 0 || this.customProperties.Equals(propertiesToCache)) { return; } // check of this game was removed from the list. in that case, we don't // need to read any further properties // list updates will remove this game from the game listing if (propertiesToCache.ContainsKey(GamePropertyKey.Removed)) { this.removedFromList = (bool)propertiesToCache[GamePropertyKey.Removed]; if (this.removedFromList) { return; } } // fetch the "well known" properties of the room, if available if (propertiesToCache.ContainsKey(GamePropertyKey.MaxPlayers)) { this.maxPlayers = (byte)propertiesToCache[GamePropertyKey.MaxPlayers]; } if (propertiesToCache.ContainsKey(GamePropertyKey.IsOpen)) { this.isOpen = (bool)propertiesToCache[GamePropertyKey.IsOpen]; } if (propertiesToCache.ContainsKey(GamePropertyKey.IsVisible)) { this.isVisible = (bool)propertiesToCache[GamePropertyKey.IsVisible]; } if (propertiesToCache.ContainsKey(GamePropertyKey.PlayerCount)) { this.PlayerCount = (int)((byte)propertiesToCache[GamePropertyKey.PlayerCount]); } if (propertiesToCache.ContainsKey(GamePropertyKey.CleanupCacheOnLeave)) { this.autoCleanUp = (bool)propertiesToCache[GamePropertyKey.CleanupCacheOnLeave]; } if (propertiesToCache.ContainsKey(GamePropertyKey.MasterClientId)) { this.masterClientId = (int)propertiesToCache[GamePropertyKey.MasterClientId]; } if (propertiesToCache.ContainsKey(GamePropertyKey.PropsListedInLobby)) { this.propertiesListedInLobby = propertiesToCache[GamePropertyKey.PropsListedInLobby] as string[]; } if (propertiesToCache.ContainsKey((byte)GamePropertyKey.ExpectedUsers)) { this.expectedUsers = (string[])propertiesToCache[GamePropertyKey.ExpectedUsers]; } // merge the custom properties (from your application) to the cache (only string-typed keys will be kept) this.customProperties.MergeStringKeys(propertiesToCache); this.customProperties.StripKeysWithNullValues(); }
/// <summary> /// Constructs a RoomInfo to be used in room listings in lobby. /// </summary> /// <param name="roomName">Name of the room and unique ID at the same time.</param> /// <param name="roomProperties">Properties for this room.</param> protected internal RoomInfo(string roomName, Hashtable roomProperties) { this.InternalCacheProperties(roomProperties); this.name = roomName; }