PunTurnManager.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. // ----------------------------------------------------------------------------
  2. // <copyright file="PunTurnManager.cs" company="Exit Games GmbH">
  3. // PhotonNetwork Framework for Unity - Copyright (C) 2018 Exit Games GmbH
  4. // </copyright>
  5. // <summary>
  6. // Manager for Turn Based games, using PUN
  7. // </summary>
  8. // <author>developer@exitgames.com</author>
  9. // ----------------------------------------------------------------------------
  10. using System;
  11. using System.Collections.Generic;
  12. using UnityEngine;
  13. using Photon.Realtime;
  14. using ExitGames.Client.Photon;
  15. using Hashtable = ExitGames.Client.Photon.Hashtable;
  16. namespace Photon.Pun.UtilityScripts
  17. {
  18. /// <summary>
  19. /// Pun turnBased Game manager.
  20. /// Provides an Interface (IPunTurnManagerCallbacks) for the typical turn flow and logic, between players
  21. /// Provides Extensions for Player, Room and RoomInfo to feature dedicated api for TurnBased Needs
  22. /// </summary>
  23. public class PunTurnManager : MonoBehaviourPunCallbacks, IOnEventCallback
  24. {
  25. /// <summary>
  26. /// External definition for better garbage collection management, used in ProcessEvent.
  27. /// </summary>
  28. Player sender;
  29. /// <summary>
  30. /// Wraps accessing the "turn" custom properties of a room.
  31. /// </summary>
  32. /// <value>The turn index</value>
  33. public int Turn
  34. {
  35. get { return PhotonNetwork.CurrentRoom.GetTurn(); }
  36. private set
  37. {
  38. _isOverCallProcessed = false;
  39. PhotonNetwork.CurrentRoom.SetTurn(value, true);
  40. }
  41. }
  42. /// <summary>
  43. /// The duration of the turn in seconds.
  44. /// </summary>
  45. public float TurnDuration = 20f;
  46. /// <summary>
  47. /// Gets the elapsed time in the current turn in seconds
  48. /// </summary>
  49. /// <value>The elapsed time in the turn.</value>
  50. public float ElapsedTimeInTurn
  51. {
  52. get { return ((float) (PhotonNetwork.ServerTimestamp - PhotonNetwork.CurrentRoom.GetTurnStart())) / 1000.0f; }
  53. }
  54. /// <summary>
  55. /// Gets the remaining seconds for the current turn. Ranges from 0 to TurnDuration
  56. /// </summary>
  57. /// <value>The remaining seconds fo the current turn</value>
  58. public float RemainingSecondsInTurn
  59. {
  60. get { return Mathf.Max(0f, this.TurnDuration - this.ElapsedTimeInTurn); }
  61. }
  62. /// <summary>
  63. /// Gets a value indicating whether the turn is completed by all.
  64. /// </summary>
  65. /// <value><c>true</c> if this turn is completed by all; otherwise, <c>false</c>.</value>
  66. public bool IsCompletedByAll
  67. {
  68. get { return PhotonNetwork.CurrentRoom != null && Turn > 0 && this.finishedPlayers.Count == PhotonNetwork.CurrentRoom.PlayerCount; }
  69. }
  70. /// <summary>
  71. /// Gets a value indicating whether the current turn is finished by me.
  72. /// </summary>
  73. /// <value><c>true</c> if the current turn is finished by me; otherwise, <c>false</c>.</value>
  74. public bool IsFinishedByMe
  75. {
  76. get { return this.finishedPlayers.Contains(PhotonNetwork.LocalPlayer); }
  77. }
  78. /// <summary>
  79. /// Gets a value indicating whether the current turn is over. That is the ElapsedTimeinTurn is greater or equal to the TurnDuration
  80. /// </summary>
  81. /// <value><c>true</c> if the current turn is over; otherwise, <c>false</c>.</value>
  82. public bool IsOver
  83. {
  84. get { return this.RemainingSecondsInTurn <= 0f; }
  85. }
  86. /// <summary>
  87. /// The turn manager listener. Set this to your own script instance to catch Callbacks
  88. /// </summary>
  89. public IPunTurnManagerCallbacks TurnManagerListener;
  90. /// <summary>
  91. /// The finished players.
  92. /// </summary>
  93. private readonly HashSet<Player> finishedPlayers = new HashSet<Player>();
  94. /// <summary>
  95. /// The turn manager event offset event message byte. Used internaly for defining data in Room Custom Properties
  96. /// </summary>
  97. public const byte TurnManagerEventOffset = 0;
  98. /// <summary>
  99. /// The Move event message byte. Used internaly for saving data in Room Custom Properties
  100. /// </summary>
  101. public const byte EvMove = 1 + TurnManagerEventOffset;
  102. /// <summary>
  103. /// The Final Move event message byte. Used internaly for saving data in Room Custom Properties
  104. /// </summary>
  105. public const byte EvFinalMove = 2 + TurnManagerEventOffset;
  106. // keep track of message calls
  107. private bool _isOverCallProcessed = false;
  108. #region MonoBehaviour CallBack
  109. void Start(){}
  110. void Update()
  111. {
  112. if (Turn > 0 && this.IsOver && !_isOverCallProcessed)
  113. {
  114. _isOverCallProcessed = true;
  115. this.TurnManagerListener.OnTurnTimeEnds(this.Turn);
  116. }
  117. }
  118. #endregion
  119. /// <summary>
  120. /// Tells the TurnManager to begins a new turn.
  121. /// </summary>
  122. public void BeginTurn()
  123. {
  124. Turn = this.Turn + 1; // note: this will set a property in the room, which is available to the other players.
  125. }
  126. /// <summary>
  127. /// Call to send an action. Optionally finish the turn, too.
  128. /// The move object can be anything. Try to optimize though and only send the strict minimum set of information to define the turn move.
  129. /// </summary>
  130. /// <param name="move"></param>
  131. /// <param name="finished"></param>
  132. public void SendMove(object move, bool finished)
  133. {
  134. if (IsFinishedByMe)
  135. {
  136. UnityEngine.Debug.LogWarning("Can't SendMove. Turn is finished by this player.");
  137. return;
  138. }
  139. // along with the actual move, we have to send which turn this move belongs to
  140. Hashtable moveHt = new Hashtable();
  141. moveHt.Add("turn", Turn);
  142. moveHt.Add("move", move);
  143. byte evCode = (finished) ? EvFinalMove : EvMove;
  144. PhotonNetwork.RaiseEvent(evCode, moveHt, new RaiseEventOptions() {CachingOption = EventCaching.AddToRoomCache}, SendOptions.SendReliable);
  145. if (finished)
  146. {
  147. PhotonNetwork.LocalPlayer.SetFinishedTurn(Turn);
  148. }
  149. // the server won't send the event back to the origin (by default). to get the event, call it locally
  150. // (note: the order of events might be mixed up as we do this locally)
  151. ProcessOnEvent(evCode, moveHt, PhotonNetwork.LocalPlayer.ActorNumber);
  152. }
  153. /// <summary>
  154. /// Gets if the player finished the current turn.
  155. /// </summary>
  156. /// <returns><c>true</c>, if player finished the current turn, <c>false</c> otherwise.</returns>
  157. /// <param name="player">The Player to check for</param>
  158. public bool GetPlayerFinishedTurn(Player player)
  159. {
  160. if (player != null && this.finishedPlayers != null && this.finishedPlayers.Contains(player))
  161. {
  162. return true;
  163. }
  164. return false;
  165. }
  166. #region Callbacks
  167. // called internally
  168. void ProcessOnEvent(byte eventCode, object content, int senderId)
  169. {
  170. if (senderId == -1)
  171. {
  172. return;
  173. }
  174. sender = PhotonNetwork.CurrentRoom.GetPlayer(senderId);
  175. switch (eventCode)
  176. {
  177. case EvMove:
  178. {
  179. Hashtable evTable = content as Hashtable;
  180. int turn = (int)evTable["turn"];
  181. object move = evTable["move"];
  182. this.TurnManagerListener.OnPlayerMove(sender, turn, move);
  183. break;
  184. }
  185. case EvFinalMove:
  186. {
  187. Hashtable evTable = content as Hashtable;
  188. int turn = (int)evTable["turn"];
  189. object move = evTable["move"];
  190. if (turn == this.Turn)
  191. {
  192. this.finishedPlayers.Add(sender);
  193. this.TurnManagerListener.OnPlayerFinished(sender, turn, move);
  194. }
  195. if (IsCompletedByAll)
  196. {
  197. this.TurnManagerListener.OnTurnCompleted(this.Turn);
  198. }
  199. break;
  200. }
  201. }
  202. }
  203. /// <summary>
  204. /// Called by PhotonNetwork.OnEventCall registration
  205. /// </summary>
  206. /// <param name="photonEvent">Photon event.</param>
  207. public void OnEvent(EventData photonEvent)
  208. {
  209. this.ProcessOnEvent(photonEvent.Code, photonEvent.CustomData, photonEvent.Sender);
  210. }
  211. /// <summary>
  212. /// Called by PhotonNetwork
  213. /// </summary>
  214. /// <param name="propertiesThatChanged">Properties that changed.</param>
  215. public override void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
  216. {
  217. // Debug.Log("OnRoomPropertiesUpdate: "+propertiesThatChanged.ToStringFull());
  218. if (propertiesThatChanged.ContainsKey("Turn"))
  219. {
  220. _isOverCallProcessed = false;
  221. this.finishedPlayers.Clear();
  222. this.TurnManagerListener.OnTurnBegins(this.Turn);
  223. }
  224. }
  225. #endregion
  226. }
  227. public interface IPunTurnManagerCallbacks
  228. {
  229. /// <summary>
  230. /// Called the turn begins event.
  231. /// </summary>
  232. /// <param name="turn">Turn Index</param>
  233. void OnTurnBegins(int turn);
  234. /// <summary>
  235. /// Called when a turn is completed (finished by all players)
  236. /// </summary>
  237. /// <param name="turn">Turn Index</param>
  238. void OnTurnCompleted(int turn);
  239. /// <summary>
  240. /// Called when a player moved (but did not finish the turn)
  241. /// </summary>
  242. /// <param name="player">Player reference</param>
  243. /// <param name="turn">Turn Index</param>
  244. /// <param name="move">Move Object data</param>
  245. void OnPlayerMove(Player player, int turn, object move);
  246. /// <summary>
  247. /// When a player finishes a turn (includes the action/move of that player)
  248. /// </summary>
  249. /// <param name="player">Player reference</param>
  250. /// <param name="turn">Turn index</param>
  251. /// <param name="move">Move Object data</param>
  252. void OnPlayerFinished(Player player, int turn, object move);
  253. /// <summary>
  254. /// Called when a turn completes due to a time constraint (timeout for a turn)
  255. /// </summary>
  256. /// <param name="turn">Turn index</param>
  257. void OnTurnTimeEnds(int turn);
  258. }
  259. public static class TurnExtensions
  260. {
  261. /// <summary>
  262. /// currently ongoing turn number
  263. /// </summary>
  264. public static readonly string TurnPropKey = "Turn";
  265. /// <summary>
  266. /// start (server) time for currently ongoing turn (used to calculate end)
  267. /// </summary>
  268. public static readonly string TurnStartPropKey = "TStart";
  269. /// <summary>
  270. /// Finished Turn of Actor (followed by number)
  271. /// </summary>
  272. public static readonly string FinishedTurnPropKey = "FToA";
  273. /// <summary>
  274. /// Sets the turn.
  275. /// </summary>
  276. /// <param name="room">Room reference</param>
  277. /// <param name="turn">Turn index</param>
  278. /// <param name="setStartTime">If set to <c>true</c> set start time.</param>
  279. public static void SetTurn(this Room room, int turn, bool setStartTime = false)
  280. {
  281. if (room == null || room.CustomProperties == null)
  282. {
  283. return;
  284. }
  285. Hashtable turnProps = new Hashtable();
  286. turnProps[TurnPropKey] = turn;
  287. if (setStartTime)
  288. {
  289. turnProps[TurnStartPropKey] = PhotonNetwork.ServerTimestamp;
  290. }
  291. room.SetCustomProperties(turnProps);
  292. }
  293. /// <summary>
  294. /// Gets the current turn from a RoomInfo
  295. /// </summary>
  296. /// <returns>The turn index </returns>
  297. /// <param name="room">RoomInfo reference</param>
  298. public static int GetTurn(this RoomInfo room)
  299. {
  300. if (room == null || room.CustomProperties == null || !room.CustomProperties.ContainsKey(TurnPropKey))
  301. {
  302. return 0;
  303. }
  304. return (int) room.CustomProperties[TurnPropKey];
  305. }
  306. /// <summary>
  307. /// Returns the start time when the turn began. This can be used to calculate how long it's going on.
  308. /// </summary>
  309. /// <returns>The turn start.</returns>
  310. /// <param name="room">Room.</param>
  311. public static int GetTurnStart(this RoomInfo room)
  312. {
  313. if (room == null || room.CustomProperties == null || !room.CustomProperties.ContainsKey(TurnStartPropKey))
  314. {
  315. return 0;
  316. }
  317. return (int) room.CustomProperties[TurnStartPropKey];
  318. }
  319. /// <summary>
  320. /// gets the player's finished turn (from the ROOM properties)
  321. /// </summary>
  322. /// <returns>The finished turn index</returns>
  323. /// <param name="player">Player reference</param>
  324. public static int GetFinishedTurn(this Player player)
  325. {
  326. Room room = PhotonNetwork.CurrentRoom;
  327. if (room == null || room.CustomProperties == null || !room.CustomProperties.ContainsKey(TurnPropKey))
  328. {
  329. return 0;
  330. }
  331. string propKey = FinishedTurnPropKey + player.ActorNumber;
  332. return (int) room.CustomProperties[propKey];
  333. }
  334. /// <summary>
  335. /// Sets the player's finished turn (in the ROOM properties)
  336. /// </summary>
  337. /// <param name="player">Player Reference</param>
  338. /// <param name="turn">Turn Index</param>
  339. public static void SetFinishedTurn(this Player player, int turn)
  340. {
  341. Room room = PhotonNetwork.CurrentRoom;
  342. if (room == null || room.CustomProperties == null)
  343. {
  344. return;
  345. }
  346. string propKey = FinishedTurnPropKey + player.ActorNumber;
  347. Hashtable finishedTurnProp = new Hashtable();
  348. finishedTurnProp[propKey] = turn;
  349. room.SetCustomProperties(finishedTurnProp);
  350. }
  351. }
  352. }