Seven is the number.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

424 lines
19 KiB

4 years ago
  1. // ----------------------------------------------------------------------------
  2. // <copyright file="Player.cs" company="Exit Games GmbH">
  3. // Loadbalancing Framework for Photon - Copyright (C) 2018 Exit Games GmbH
  4. // </copyright>
  5. // <summary>
  6. // Per client in a room, a Player is created. This client's Player is also
  7. // known as PhotonClient.LocalPlayer and the only one you might change
  8. // properties for.
  9. // </summary>
  10. // <author>developer@photonengine.com</author>
  11. // ----------------------------------------------------------------------------
  12. #if UNITY_4_7 || UNITY_5 || UNITY_5_3_OR_NEWER
  13. #define SUPPORTED_UNITY
  14. #endif
  15. namespace Photon.Realtime
  16. {
  17. using System;
  18. using System.Collections;
  19. using System.Collections.Generic;
  20. using ExitGames.Client.Photon;
  21. #if SUPPORTED_UNITY
  22. using UnityEngine;
  23. #endif
  24. #if SUPPORTED_UNITY || NETFX_CORE
  25. using Hashtable = ExitGames.Client.Photon.Hashtable;
  26. using SupportClass = ExitGames.Client.Photon.SupportClass;
  27. #endif
  28. /// <summary>
  29. /// Summarizes a "player" within a room, identified (in that room) by ID (or "actorNumber").
  30. /// </summary>
  31. /// <remarks>
  32. /// Each player has a actorNumber, valid for that room. It's -1 until assigned by server (and client logic).
  33. /// </remarks>
  34. public class Player
  35. {
  36. /// <summary>
  37. /// Used internally to identify the masterclient of a room.
  38. /// </summary>
  39. protected internal Room RoomReference { get; set; }
  40. /// <summary>Backing field for property.</summary>
  41. private int actorNumber = -1;
  42. /// <summary>Identifier of this player in current room. Also known as: actorNumber or actorNumber. It's -1 outside of rooms.</summary>
  43. /// <remarks>The ID is assigned per room and only valid in that context. It will change even on leave and re-join. IDs are never re-used per room.</remarks>
  44. public int ActorNumber
  45. {
  46. get { return this.actorNumber; }
  47. }
  48. /// <summary>Only one player is controlled by each client. Others are not local.</summary>
  49. public readonly bool IsLocal;
  50. /// <summary>Background field for nickName.</summary>
  51. private string nickName = string.Empty;
  52. /// <summary>Non-unique nickname of this player. Synced automatically in a room.</summary>
  53. /// <remarks>
  54. /// A player might change his own playername in a room (it's only a property).
  55. /// Setting this value updates the server and other players (using an operation).
  56. /// </remarks>
  57. public string NickName
  58. {
  59. get
  60. {
  61. return this.nickName;
  62. }
  63. set
  64. {
  65. if (!string.IsNullOrEmpty(this.nickName) && this.nickName.Equals(value))
  66. {
  67. return;
  68. }
  69. this.nickName = value;
  70. // update a room, if we changed our nickName locally
  71. if (this.IsLocal)
  72. {
  73. this.SetPlayerNameProperty();
  74. }
  75. }
  76. }
  77. /// <summary>UserId of the player, available when the room got created with RoomOptions.PublishUserId = true.</summary>
  78. /// <remarks>Useful for <see cref="LoadBalancingClient.OpFindFriends"/> and blocking slots in a room for expected players (e.g. in <see cref="LoadBalancingClient.OpCreateRoom"/>).</remarks>
  79. public string UserId { get; internal set; }
  80. /// <summary>
  81. /// True if this player is the Master Client of the current room.
  82. /// </summary>
  83. public bool IsMasterClient
  84. {
  85. get
  86. {
  87. if (this.RoomReference == null)
  88. {
  89. return false;
  90. }
  91. return this.ActorNumber == this.RoomReference.MasterClientId;
  92. }
  93. }
  94. /// <summary>If this player is active in the room (and getting events which are currently being sent).</summary>
  95. /// <remarks>
  96. /// Inactive players keep their spot in a room but otherwise behave as if offline (no matter what their actual connection status is).
  97. /// The room needs a PlayerTTL != 0. If a player is inactive for longer than PlayerTTL, the server will remove this player from the room.
  98. /// For a client "rejoining" a room, is the same as joining it: It gets properties, cached events and then the live events.
  99. /// </remarks>
  100. public bool IsInactive { get; protected internal set; }
  101. /// <summary>Read-only cache for custom properties of player. Set via Player.SetCustomProperties.</summary>
  102. /// <remarks>
  103. /// Don't modify the content of this Hashtable. Use SetCustomProperties and the
  104. /// properties of this class to modify values. When you use those, the client will
  105. /// sync values with the server.
  106. /// </remarks>
  107. /// <see cref="SetCustomProperties"/>
  108. public Hashtable CustomProperties { get; set; }
  109. /// <summary>Can be used to store a reference that's useful to know "by player".</summary>
  110. /// <remarks>Example: Set a player's character as Tag by assigning the GameObject on Instantiate.</remarks>
  111. public object TagObject;
  112. /// <summary>
  113. /// Creates a player instance.
  114. /// To extend and replace this Player, override LoadBalancingPeer.CreatePlayer().
  115. /// </summary>
  116. /// <param name="nickName">NickName of the player (a "well known property").</param>
  117. /// <param name="actorNumber">ID or ActorNumber of this player in the current room (a shortcut to identify each player in room)</param>
  118. /// <param name="isLocal">If this is the local peer's player (or a remote one).</param>
  119. protected internal Player(string nickName, int actorNumber, bool isLocal) : this(nickName, actorNumber, isLocal, null)
  120. {
  121. }
  122. /// <summary>
  123. /// Creates a player instance.
  124. /// To extend and replace this Player, override LoadBalancingPeer.CreatePlayer().
  125. /// </summary>
  126. /// <param name="nickName">NickName of the player (a "well known property").</param>
  127. /// <param name="actorNumber">ID or ActorNumber of this player in the current room (a shortcut to identify each player in room)</param>
  128. /// <param name="isLocal">If this is the local peer's player (or a remote one).</param>
  129. /// <param name="playerProperties">A Hashtable of custom properties to be synced. Must use String-typed keys and serializable datatypes as values.</param>
  130. protected internal Player(string nickName, int actorNumber, bool isLocal, Hashtable playerProperties)
  131. {
  132. this.IsLocal = isLocal;
  133. this.actorNumber = actorNumber;
  134. this.NickName = nickName;
  135. this.CustomProperties = new Hashtable();
  136. this.InternalCacheProperties(playerProperties);
  137. }
  138. /// <summary>
  139. /// Get a Player by ActorNumber (Player.ID).
  140. /// </summary>
  141. /// <param name="id">ActorNumber of the a player in this room.</param>
  142. /// <returns>Player or null.</returns>
  143. public Player Get(int id)
  144. {
  145. if (this.RoomReference == null)
  146. {
  147. return null;
  148. }
  149. return this.RoomReference.GetPlayer(id);
  150. }
  151. /// <summary>Gets this Player's next Player, as sorted by ActorNumber (Player.ID). Wraps around.</summary>
  152. /// <returns>Player or null.</returns>
  153. public Player GetNext()
  154. {
  155. return GetNextFor(this.ActorNumber);
  156. }
  157. /// <summary>Gets a Player's next Player, as sorted by ActorNumber (Player.ID). Wraps around.</summary>
  158. /// <remarks>Useful when you pass something to the next player. For example: passing the turn to the next player.</remarks>
  159. /// <param name="currentPlayer">The Player for which the next is being needed.</param>
  160. /// <returns>Player or null.</returns>
  161. public Player GetNextFor(Player currentPlayer)
  162. {
  163. if (currentPlayer == null)
  164. {
  165. return null;
  166. }
  167. return GetNextFor(currentPlayer.ActorNumber);
  168. }
  169. /// <summary>Gets a Player's next Player, as sorted by ActorNumber (Player.ID). Wraps around.</summary>
  170. /// <remarks>Useful when you pass something to the next player. For example: passing the turn to the next player.</remarks>
  171. /// <param name="currentPlayerId">The ActorNumber (Player.ID) for which the next is being needed.</param>
  172. /// <returns>Player or null.</returns>
  173. public Player GetNextFor(int currentPlayerId)
  174. {
  175. if (this.RoomReference == null || this.RoomReference.Players == null || this.RoomReference.Players.Count < 2)
  176. {
  177. return null;
  178. }
  179. Dictionary<int, Player> players = this.RoomReference.Players;
  180. int nextHigherId = int.MaxValue; // we look for the next higher ID
  181. 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
  182. foreach (int playerid in players.Keys)
  183. {
  184. if (playerid < lowestId)
  185. {
  186. lowestId = playerid; // less than any other ID (which must be at least less than this player's id).
  187. }
  188. else if (playerid > currentPlayerId && playerid < nextHigherId)
  189. {
  190. nextHigherId = playerid; // more than our ID and less than those found so far.
  191. }
  192. }
  193. //UnityEngine.Debug.LogWarning("Debug. " + currentPlayerId + " lower: " + lowestId + " higher: " + nextHigherId + " ");
  194. //UnityEngine.Debug.LogWarning(this.RoomReference.GetPlayer(currentPlayerId));
  195. //UnityEngine.Debug.LogWarning(this.RoomReference.GetPlayer(lowestId));
  196. //if (nextHigherId != int.MaxValue) UnityEngine.Debug.LogWarning(this.RoomReference.GetPlayer(nextHigherId));
  197. return (nextHigherId != int.MaxValue) ? players[nextHigherId] : players[lowestId];
  198. }
  199. /// <summary>Caches properties for new Players or when updates of remote players are received. Use SetCustomProperties() for a synced update.</summary>
  200. /// <remarks>
  201. /// This only updates the CustomProperties and doesn't send them to the server.
  202. /// Mostly used when creating new remote players, where the server sends their properties.
  203. /// </remarks>
  204. public virtual void InternalCacheProperties(Hashtable properties)
  205. {
  206. if (properties == null || properties.Count == 0 || this.CustomProperties.Equals(properties))
  207. {
  208. return;
  209. }
  210. if (properties.ContainsKey(ActorProperties.PlayerName))
  211. {
  212. string nameInServersProperties = (string)properties[ActorProperties.PlayerName];
  213. if (nameInServersProperties != null)
  214. {
  215. if (this.IsLocal)
  216. {
  217. // the local playername is different than in the properties coming from the server
  218. // so the local nickName was changed and the server is outdated -> update server
  219. // update property instead of using the outdated nickName coming from server
  220. if (!nameInServersProperties.Equals(this.nickName))
  221. {
  222. this.SetPlayerNameProperty();
  223. }
  224. }
  225. else
  226. {
  227. this.NickName = nameInServersProperties;
  228. }
  229. }
  230. }
  231. if (properties.ContainsKey(ActorProperties.UserId))
  232. {
  233. this.UserId = (string)properties[ActorProperties.UserId];
  234. }
  235. if (properties.ContainsKey(ActorProperties.IsInactive))
  236. {
  237. this.IsInactive = (bool)properties[ActorProperties.IsInactive]; //TURNBASED new well-known propery for players
  238. }
  239. this.CustomProperties.MergeStringKeys(properties);
  240. this.CustomProperties.StripKeysWithNullValues();
  241. }
  242. /// <summary>
  243. /// Brief summary string of the Player: ActorNumber and NickName
  244. /// </summary>
  245. public override string ToString()
  246. {
  247. return string.Format("#{0:00} '{1}'",this.ActorNumber, this.NickName);
  248. }
  249. /// <summary>
  250. /// String summary of the Player: player.ID, name and all custom properties of this user.
  251. /// </summary>
  252. /// <remarks>
  253. /// Use with care and not every frame!
  254. /// Converts the customProperties to a String on every single call.
  255. /// </remarks>
  256. public string ToStringFull()
  257. {
  258. return string.Format("#{0:00} '{1}'{2} {3}", this.ActorNumber, this.NickName, this.IsInactive ? " (inactive)" : "", this.CustomProperties.ToStringFull());
  259. }
  260. /// <summary>
  261. /// If players are equal (by GetHasCode, which returns this.ID).
  262. /// </summary>
  263. public override bool Equals(object p)
  264. {
  265. Player pp = p as Player;
  266. return (pp != null && this.GetHashCode() == pp.GetHashCode());
  267. }
  268. /// <summary>
  269. /// Accompanies Equals, using the ID (actorNumber) as HashCode to return.
  270. /// </summary>
  271. public override int GetHashCode()
  272. {
  273. return this.ActorNumber;
  274. }
  275. /// <summary>
  276. /// Used internally, to update this client's playerID when assigned (doesn't change after assignment).
  277. /// </summary>
  278. protected internal void ChangeLocalID(int newID)
  279. {
  280. if (!this.IsLocal)
  281. {
  282. //Debug.LogError("ERROR You should never change Player IDs!");
  283. return;
  284. }
  285. this.actorNumber = newID;
  286. }
  287. /// <summary>
  288. /// Updates and synchronizes this Player's Custom Properties. Optionally, expectedProperties can be provided as condition.
  289. /// </summary>
  290. /// <remarks>
  291. /// Custom Properties are a set of string keys and arbitrary values which is synchronized
  292. /// for the players in a Room. They are available when the client enters the room, as
  293. /// they are in the response of OpJoin and OpCreate.
  294. ///
  295. /// Custom Properties either relate to the (current) Room or a Player (in that Room).
  296. ///
  297. /// Both classes locally cache the current key/values and make them available as
  298. /// property: CustomProperties. This is provided only to read them.
  299. /// You must use the method SetCustomProperties to set/modify them.
  300. ///
  301. /// Any client can set any Custom Properties anytime (when in a room).
  302. /// It's up to the game logic to organize how they are best used.
  303. ///
  304. /// You should call SetCustomProperties only with key/values that are new or changed. This reduces
  305. /// traffic and performance.
  306. ///
  307. /// Unless you define some expectedProperties, setting key/values is always permitted.
  308. /// In this case, the property-setting client will not receive the new values from the server but
  309. /// instead update its local cache in SetCustomProperties.
  310. ///
  311. /// If you define expectedProperties, the server will skip updates if the server property-cache
  312. /// does not contain all expectedProperties with the same values.
  313. /// In this case, the property-setting client will get an update from the server and update it's
  314. /// cached key/values at about the same time as everyone else.
  315. ///
  316. /// The benefit of using expectedProperties can be only one client successfully sets a key from
  317. /// one known value to another.
  318. /// As example: Store who owns an item in a Custom Property "ownedBy". It's 0 initally.
  319. /// When multiple players reach the item, they all attempt to change "ownedBy" from 0 to their
  320. /// actorNumber. If you use expectedProperties {"ownedBy", 0} as condition, the first player to
  321. /// take the item will have it (and the others fail to set the ownership).
  322. ///
  323. /// Properties get saved with the game state for Turnbased games (which use IsPersistent = true).
  324. /// </remarks>
  325. /// <param name="propertiesToSet">Hashtable of Custom Properties to be set. </param>
  326. /// <param name="expectedValues">If non-null, these are the property-values the server will check as condition for this update.</param>
  327. /// <param name="webFlags">Defines if this SetCustomProperties-operation gets forwarded to your WebHooks. Client must be in room.</param>
  328. public void SetCustomProperties(Hashtable propertiesToSet, Hashtable expectedValues = null, WebFlags webFlags = null)
  329. {
  330. if (propertiesToSet == null)
  331. {
  332. return;
  333. }
  334. Hashtable customProps = propertiesToSet.StripToStringKeys() as Hashtable;
  335. Hashtable customPropsToCheck = expectedValues.StripToStringKeys() as Hashtable;
  336. // no expected values -> set and callback
  337. bool noCas = customPropsToCheck == null || customPropsToCheck.Count == 0;
  338. if (noCas)
  339. {
  340. this.CustomProperties.Merge(customProps);
  341. this.CustomProperties.StripKeysWithNullValues();
  342. }
  343. if (this.RoomReference != null)
  344. {
  345. if (this.RoomReference.IsOffline)
  346. {
  347. // invoking callbacks
  348. this.RoomReference.LoadBalancingClient.InRoomCallbackTargets.OnPlayerPropertiesUpdate(this, customProps);
  349. }
  350. else
  351. {
  352. // send (sync) these new values if in online room
  353. this.RoomReference.LoadBalancingClient.LoadBalancingPeer.OpSetPropertiesOfActor(this.actorNumber, customProps, customPropsToCheck, webFlags);
  354. }
  355. }
  356. }
  357. /// <summary>Uses OpSetPropertiesOfActor to sync this player's NickName (server is being updated with this.NickName).</summary>
  358. private void SetPlayerNameProperty()
  359. {
  360. if (this.RoomReference != null && !this.RoomReference.IsOffline)
  361. {
  362. Hashtable properties = new Hashtable();
  363. properties[ActorProperties.PlayerName] = this.nickName;
  364. this.RoomReference.LoadBalancingClient.LoadBalancingPeer.OpSetPropertiesOfActor(this.ActorNumber, properties);
  365. }
  366. }
  367. }
  368. }