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.

451 lines
20 KiB

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