// ---------------------------------------------------------------------------- // // PhotonNetwork Framework for Unity - Copyright (C) 2011 Exit Games GmbH // // // Represents a player, identified by actorID (a.k.a. ActorNumber). // Caches properties of a player. // // developer@exitgames.com // ---------------------------------------------------------------------------- using System; using System.Collections.Generic; using ExitGames.Client.Photon; using UnityEngine; using Hashtable = ExitGames.Client.Photon.Hashtable; /// /// Summarizes a "player" within a room, identified (in that room) by actorID. /// /// /// Each player has an actorId (or ID), valid for that room. It's -1 until it's assigned by server. /// Each client can set it's player's custom properties with SetCustomProperties, even before being in a room. /// They are synced when joining a room. /// /// \ingroup publicApi public class PhotonPlayer : IComparable, IComparable, IEquatable, IEquatable { /// This player's actorID public int ID { get { return this.actorID; } } /// Identifier of this player in current room. private int actorID = -1; private string nameField = ""; /// Nickname of this player. /// Set the PhotonNetwork.playerName to make the name synchronized in a room. public string NickName { get { return this.nameField; } set { if (!IsLocal) { Debug.LogError("Error: Cannot change the name of a remote player!"); return; } if (string.IsNullOrEmpty(value) || value.Equals(this.nameField)) { return; } this.nameField = value; PhotonNetwork.playerName = value; // this will sync the local player's name in a room } } /// UserId of the player, available when the room got created with RoomOptions.PublishUserId = true. /// Useful for PhotonNetwork.FindFriends and blocking slots in a room for expected players (e.g. in PhotonNetwork.CreateRoom). public string UserId { get; internal set; } /// Only one player is controlled by each client. Others are not local. public readonly bool IsLocal = false; /// /// True if this player is the Master Client of the current room. /// /// /// See also: PhotonNetwork.masterClient. /// public bool IsMasterClient { get { return (PhotonNetwork.networkingPeer.mMasterClientId == this.ID); } } /// Players might be inactive in a room when PlayerTTL for a room is > 0. If true, the player is not getting events from this room (now) but can return later. public bool IsInactive { get; set; } // needed for rejoins /// Read-only cache for custom properties of player. Set via PhotonPlayer.SetCustomProperties. /// /// Don't modify the content of this Hashtable. Use SetCustomProperties and the /// properties of this class to modify values. When you use those, the client will /// sync values with the server. /// /// public Hashtable CustomProperties { get; internal set; } /// Creates a Hashtable with all properties (custom and "well known" ones). /// If used more often, this should be cached. public Hashtable AllProperties { get { Hashtable allProps = new Hashtable(); allProps.Merge(this.CustomProperties); allProps[ActorProperties.PlayerName] = this.NickName; return allProps; } } /// Can be used to store a reference that's useful to know "by player". /// Example: Set a player's character as Tag by assigning the GameObject on Instantiate. public object TagObject; /// /// Creates a PhotonPlayer instance. /// /// If this is the local peer's player (or a remote one). /// ID or ActorNumber of this player in the current room (a shortcut to identify each player in room) /// Name of the player (a "well known property"). public PhotonPlayer(bool isLocal, int actorID, string name) { this.CustomProperties = new Hashtable(); this.IsLocal = isLocal; this.actorID = actorID; this.nameField = name; } /// /// Internally used to create players from event Join /// protected internal PhotonPlayer(bool isLocal, int actorID, Hashtable properties) { this.CustomProperties = new Hashtable(); this.IsLocal = isLocal; this.actorID = actorID; this.InternalCacheProperties(properties); } /// /// Makes PhotonPlayer comparable /// public override bool Equals(object p) { PhotonPlayer pp = p as PhotonPlayer; return (pp != null && this.GetHashCode() == pp.GetHashCode()); } public override int GetHashCode() { return this.ID; } /// /// Used internally, to update this client's playerID when assigned. /// internal void InternalChangeLocalID(int newID) { if (!this.IsLocal) { Debug.LogError("ERROR You should never change PhotonPlayer IDs!"); return; } this.actorID = newID; } /// /// Caches custom properties for this player. /// internal void InternalCacheProperties(Hashtable properties) { if (properties == null || properties.Count == 0 || this.CustomProperties.Equals(properties)) { return; } if (properties.ContainsKey(ActorProperties.PlayerName)) { this.nameField = (string)properties[ActorProperties.PlayerName]; } 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(); } /// /// Updates the this player's Custom Properties with new/updated key-values. /// /// /// Custom Properties are a key-value set (Hashtable) which is available to all players in a room. /// They can relate to the room or individual players and are useful when only the current value /// of something is of interest. For example: The map of a room. /// All keys must be strings. /// /// The Room and the PhotonPlayer class both have SetCustomProperties methods. /// Also, both classes offer access to current key-values by: customProperties. /// /// Always use SetCustomProperties to change values. /// To reduce network traffic, set only values that actually changed. /// New properties are added, existing values are updated. /// Other values will not be changed, so only provide values that changed or are new. /// /// To delete a named (custom) property of this room, use null as value. /// /// Locally, SetCustomProperties will update it's cache without delay. /// Other clients are updated through Photon (the server) with a fitting operation. /// /// Check and Swap /// /// SetCustomProperties have the option to do a server-side Check-And-Swap (CAS): /// Values only get updated if the expected values are correct. /// The expectedValues can be different key/values than the propertiesToSet. So you can /// check some key and set another key's value (if the check succeeds). /// /// If the client's knowledge of properties is wrong or outdated, it can't set values with CAS. /// This can be useful to keep players from concurrently setting values. For example: If all players /// try to pickup some card or item, only one should get it. With CAS, only the first SetProperties /// gets executed server-side and any other (sent at the same time) fails. /// /// The server will broadcast successfully changed values and the local "cache" of customProperties /// only gets updated after a roundtrip (if anything changed). /// /// You can do a "webForward": Photon will send the changed properties to a WebHook defined /// for your application. /// /// OfflineMode /// /// While PhotonNetwork.offlineMode is true, the expectedValues and webForward parameters are ignored. /// In OfflineMode, the local customProperties values are immediately updated (without the roundtrip). /// /// The new properties to be set. /// At least one property key/value set to check server-side. Key and value must be correct. Ignored in OfflineMode. /// Set to true, to forward the set properties to a WebHook, defined for this app (in Dashboard). Ignored in OfflineMode. public void SetCustomProperties(Hashtable propertiesToSet, Hashtable expectedValues = null, bool webForward = false) { 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; bool inOnlineRoom = this.actorID > 0 && !PhotonNetwork.offlineMode; if (noCas) { this.CustomProperties.Merge(customProps); this.CustomProperties.StripKeysWithNullValues(); } if (inOnlineRoom) { PhotonNetwork.networkingPeer.OpSetPropertiesOfActor(this.actorID, customProps, customPropsToCheck, webForward); } if (!inOnlineRoom || noCas) { this.InternalCacheProperties(customProps); NetworkingPeer.SendMonoMessage(PhotonNetworkingMessage.OnPhotonPlayerPropertiesChanged, this, customProps); } } /// /// Try to get a specific player by id. /// /// ActorID /// The player with matching actorID or null, if the actorID is not in use. public static PhotonPlayer Find(int ID) { if (PhotonNetwork.networkingPeer != null) { return PhotonNetwork.networkingPeer.GetPlayerWithId(ID); } return null; } public PhotonPlayer Get(int id) { return PhotonPlayer.Find(id); } public PhotonPlayer GetNext() { return GetNextFor(this.ID); } public PhotonPlayer GetNextFor(PhotonPlayer currentPlayer) { if (currentPlayer == null) { return null; } return GetNextFor(currentPlayer.ID); } public PhotonPlayer GetNextFor(int currentPlayerId) { if (PhotonNetwork.networkingPeer == null || PhotonNetwork.networkingPeer.mActors == null || PhotonNetwork.networkingPeer.mActors.Count < 2) { return null; } Dictionary players = PhotonNetwork.networkingPeer.mActors; int nextHigherId = int.MaxValue; // we look for the next higher ID int lowestId = currentPlayerId; // if we are the player with the highest ID, there is no higher and we return to the lowest player's id foreach (int playerid in players.Keys) { if (playerid < lowestId) { lowestId = playerid; // less than any other ID (which must be at least less than this player's id). } else if (playerid > currentPlayerId && playerid < nextHigherId) { nextHigherId = playerid; // more than our ID and less than those found so far. } } //UnityEngine.Debug.LogWarning("Debug. " + currentPlayerId + " lower: " + lowestId + " higher: " + nextHigherId + " "); //UnityEngine.Debug.LogWarning(this.RoomReference.GetPlayer(currentPlayerId)); //UnityEngine.Debug.LogWarning(this.RoomReference.GetPlayer(lowestId)); //if (nextHigherId != int.MaxValue) UnityEngine.Debug.LogWarning(this.RoomReference.GetPlayer(nextHigherId)); return (nextHigherId != int.MaxValue) ? players[nextHigherId] : players[lowestId]; } #region IComparable implementation public int CompareTo (PhotonPlayer other) { if ( other == null) { return 0; } return this.GetHashCode().CompareTo(other.GetHashCode()); } public int CompareTo (int other) { return this.GetHashCode().CompareTo(other); } #endregion #region IEquatable implementation public bool Equals (PhotonPlayer other) { if ( other == null) { return false; } return this.GetHashCode().Equals(other.GetHashCode()); } public bool Equals (int other) { return this.GetHashCode().Equals(other); } #endregion /// /// Brief summary string of the PhotonPlayer. Includes name or player.ID and if it's the Master Client. /// public override string ToString() { if (string.IsNullOrEmpty(this.NickName)) { return string.Format("#{0:00}{1}{2}", this.ID, this.IsInactive ? " (inactive)" : " ", this.IsMasterClient ? "(master)":""); } return string.Format("'{0}'{1}{2}", this.NickName, this.IsInactive ? " (inactive)" : " ", this.IsMasterClient ? "(master)" : ""); } /// /// String summary of the PhotonPlayer: player.ID, name and all custom properties of this user. /// /// /// Use with care and not every frame! /// Converts the customProperties to a String on every single call. /// public string ToStringFull() { return string.Format("#{0:00} '{1}'{2} {3}", this.ID, this.NickName, this.IsInactive ? " (inactive)" : "", this.CustomProperties.ToStringFull()); } #region Obsoleted variable names [Obsolete("Please use NickName (updated case for naming).")] public string name { get { return this.NickName; } set { this.NickName = value; } } [Obsolete("Please use UserId (updated case for naming).")] public string userId { get { return this.UserId; } internal set { this.UserId = value; } } [Obsolete("Please use IsLocal (updated case for naming).")] public bool isLocal { get { return this.IsLocal; } } [Obsolete("Please use IsMasterClient (updated case for naming).")] public bool isMasterClient { get { return this.IsMasterClient; } } [Obsolete("Please use IsInactive (updated case for naming).")] public bool isInactive { get { return this.IsInactive; } set { this.IsInactive = value; } } [Obsolete("Please use CustomProperties (updated case for naming).")] public Hashtable customProperties { get { return this.CustomProperties; } internal set { this.CustomProperties = value; } } [Obsolete("Please use AllProperties (updated case for naming).")] public Hashtable allProperties { get { return this.AllProperties; } } #endregion }