PageRenderTime 73ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 1ms

/Assets/Photon Unity Networking/Plugins/PhotonNetwork/NetworkingPeer.cs

https://bitbucket.org/AgentCodeMonkey/gameframework-unity-project
C# | 3079 lines | 2336 code | 426 blank | 317 comment | 609 complexity | 7d0a804d894b99ec16e5bca6ed79f367 MD5 | raw file
  1. // --------------------------------------------------------------------------------------------------------------------
  2. // <copyright file="NetworkingPeer.cs" company="Exit Games GmbH">
  3. // Part of: Photon Unity Networking (PUN)
  4. // </copyright>
  5. // --------------------------------------------------------------------------------------------------------------------
  6. using System;
  7. using System.Collections;
  8. using System.Collections.Generic;
  9. using System.Reflection;
  10. using ExitGames.Client.Photon;
  11. using ExitGames.Client.Photon.Lite;
  12. using UnityEngine;
  13. /// <summary>
  14. /// Implements Photon LoadBalancing used in PUN.
  15. /// This class is used internally by PhotonNetwork and not intended as public API.
  16. /// </summary>
  17. internal class NetworkingPeer : LoadbalancingPeer, IPhotonPeerListener
  18. {
  19. // game properties must be cached, because the game is created on the master and then "re-created" on the game server
  20. // both must use the same props for the game
  21. public string mAppVersion;
  22. private string mAppId;
  23. private string masterServerAddress;
  24. private string playername = "";
  25. private IPhotonPeerListener externalListener;
  26. private JoinType mLastJoinType;
  27. private bool mPlayernameHasToBeUpdated;
  28. public string PlayerName
  29. {
  30. get
  31. {
  32. return this.playername;
  33. }
  34. set
  35. {
  36. if (string.IsNullOrEmpty(value) || value.Equals(this.playername))
  37. {
  38. return;
  39. }
  40. if (this.mLocalActor != null)
  41. {
  42. this.mLocalActor.name = value;
  43. }
  44. this.playername = value;
  45. if (this.mCurrentGame != null)
  46. {
  47. // Only when in a room
  48. this.SendPlayerName();
  49. }
  50. }
  51. }
  52. public PeerState State { get; internal set; }
  53. // "public" access to the current game - is null unless a room is joined on a gameserver
  54. public Room mCurrentGame
  55. {
  56. get
  57. {
  58. if (this.mRoomToGetInto != null && this.mRoomToGetInto.isLocalClientInside)
  59. {
  60. return this.mRoomToGetInto;
  61. }
  62. return null;
  63. }
  64. }
  65. /// <summary>
  66. /// keeps the custom properties, gameServer address and anything else about the room we want to get into
  67. /// </summary>
  68. internal Room mRoomToGetInto { get; set; }
  69. public Dictionary<int, PhotonPlayer> mActors = new Dictionary<int, PhotonPlayer>();
  70. public PhotonPlayer[] mOtherPlayerListCopy = new PhotonPlayer[0];
  71. public PhotonPlayer[] mPlayerListCopy = new PhotonPlayer[0];
  72. public PhotonPlayer mLocalActor { get; internal set; }
  73. public PhotonPlayer mMasterClient = null;
  74. public string mGameserver { get; internal set; }
  75. public bool requestSecurity = true;
  76. private Dictionary<Type, List<MethodInfo>> monoRPCMethodsCache = new Dictionary<Type, List<MethodInfo>>();
  77. public static bool UsePrefabCache = true;
  78. public static Dictionary<string, GameObject> PrefabCache = new Dictionary<string, GameObject>();
  79. public Dictionary<string, RoomInfo> mGameList = new Dictionary<string, RoomInfo>();
  80. public RoomInfo[] mGameListCopy = new RoomInfo[0];
  81. public int mQueuePosition { get; internal set; }
  82. public bool insideLobby = false;
  83. /// <summary>Stat value: Count of players on Master (looking for rooms)</summary>
  84. public int mPlayersOnMasterCount { get; internal set; }
  85. /// <summary>Stat value: Count of Rooms</summary>
  86. public int mGameCount { get; internal set; }
  87. /// <summary>Stat value: Count of Players in rooms</summary>
  88. public int mPlayersInRoomsCount { get; internal set; }
  89. /// <summary>
  90. /// Instantiated objects by their instantiationId. The id (key) is per actor.
  91. /// </summary>
  92. public Dictionary<int, GameObject> instantiatedObjects = new Dictionary<int, GameObject>();
  93. private HashSet<int> allowedReceivingGroups = new HashSet<int>();
  94. private HashSet<int> blockSendingGroups = new HashSet<int>();
  95. internal protected Dictionary<int, PhotonView> photonViewList = new Dictionary<int, PhotonView>(); //TODO: make private again
  96. internal protected short currentLevelPrefix = 0;
  97. private readonly Dictionary<Type, Dictionary<PhotonNetworkingMessage, MethodInfo>> cachedMethods = new Dictionary<Type, Dictionary<PhotonNetworkingMessage, MethodInfo>>();
  98. private readonly Dictionary<string, int> rpcShortcuts; // lookup "table" for the index (shortcut) of an RPC name
  99. public NetworkingPeer(IPhotonPeerListener listener, string playername, ConnectionProtocol connectionProtocol) : base(listener, connectionProtocol)
  100. {
  101. this.Listener = this;
  102. // don't set the field directly! the listener is passed on to other classes, which get updated by the property set method
  103. this.externalListener = listener;
  104. this.PlayerName = playername;
  105. this.mLocalActor = new PhotonPlayer(true, -1, this.playername);
  106. this.AddNewPlayer(this.mLocalActor.ID, this.mLocalActor);
  107. // RPC shortcut lookup creation (from list of RPCs, which is updated by Editor scripts)
  108. rpcShortcuts = new Dictionary<string, int>(PhotonNetwork.PhotonServerSettings.RpcList.Count);
  109. for (int index = 0; index < PhotonNetwork.PhotonServerSettings.RpcList.Count; index++)
  110. {
  111. var name = PhotonNetwork.PhotonServerSettings.RpcList[index];
  112. rpcShortcuts[name] = index;
  113. }
  114. this.State = global::PeerState.PeerCreated;
  115. }
  116. #region Operations and Connection Methods
  117. public override bool Connect(string serverAddress, string appID)
  118. {
  119. if (PhotonNetwork.connectionStateDetailed == global::PeerState.Disconnecting)
  120. {
  121. Debug.LogError("ERROR: Cannot connect to Photon while Disconnecting. Connection failed.");
  122. return false;
  123. }
  124. if (string.IsNullOrEmpty(this.masterServerAddress))
  125. {
  126. this.masterServerAddress = serverAddress;
  127. }
  128. this.mAppId = appID.Trim();
  129. // connect might fail, if the DNS name can't be resolved or if no network connection is available
  130. bool connecting = base.Connect(serverAddress, "");
  131. this.State = connecting ? global::PeerState.Connecting : global::PeerState.Disconnected;
  132. return connecting;
  133. }
  134. /// <summary>
  135. /// Complete disconnect from photon (and the open master OR game server)
  136. /// </summary>
  137. public override void Disconnect()
  138. {
  139. if (this.PeerState == PeerStateValue.Disconnected)
  140. {
  141. if (this.DebugOut >= DebugLevel.WARNING)
  142. {
  143. this.DebugReturn(DebugLevel.WARNING, string.Format("Can't execute Disconnect() while not connected. Nothing changed. State: {0}", this.State));
  144. }
  145. return;
  146. }
  147. base.Disconnect();
  148. this.State = global::PeerState.Disconnecting;
  149. this.LeftRoomCleanup();
  150. this.LeftLobbyCleanup();
  151. }
  152. // just switches servers(Master->Game). don't remove the room, actors, etc
  153. private void DisconnectFromMaster()
  154. {
  155. base.Disconnect();
  156. this.State = global::PeerState.DisconnectingFromMasterserver;
  157. LeftLobbyCleanup();
  158. }
  159. // switches back from gameserver to master and removes the room, actors, etc
  160. private void DisconnectFromGameServer()
  161. {
  162. base.Disconnect();
  163. this.State = global::PeerState.DisconnectingFromGameserver;
  164. this.LeftRoomCleanup();
  165. }
  166. /// <summary>
  167. /// Called at disconnect/leavelobby etc. This CAN also be called when we are not in a lobby (e.g. disconnect from room)
  168. /// </summary>
  169. private void LeftLobbyCleanup()
  170. {
  171. if (!insideLobby)
  172. {
  173. return;
  174. }
  175. SendMonoMessage(PhotonNetworkingMessage.OnLeftLobby);
  176. this.insideLobby = false;
  177. }
  178. /// <summary>
  179. /// Called when "this client" left a room to clean up.
  180. /// </summary>
  181. private void LeftRoomCleanup()
  182. {
  183. bool wasInRoom = mRoomToGetInto != null;
  184. // when leaving a room, we clean up depending on that room's settings.
  185. bool autoCleanupSettingOfRoom = (this.mRoomToGetInto != null) ? this.mRoomToGetInto.autoCleanUp : PhotonNetwork.autoCleanUpPlayerObjects;
  186. this.mRoomToGetInto = null;
  187. this.mActors = new Dictionary<int, PhotonPlayer>();
  188. mPlayerListCopy = new PhotonPlayer[0];
  189. mOtherPlayerListCopy = new PhotonPlayer[0];
  190. this.mMasterClient = null;
  191. this.allowedReceivingGroups = new HashSet<int>();
  192. this.blockSendingGroups = new HashSet<int>();
  193. this.mGameList = new Dictionary<string, RoomInfo>();
  194. mGameListCopy = new RoomInfo[0];
  195. this.ChangeLocalID(-1);
  196. // Cleanup all network objects (all spawned PhotonViews, local and remote)
  197. if (autoCleanupSettingOfRoom)
  198. {
  199. // Fill list with Instantiated objects
  200. List<GameObject> goList = new List<GameObject>(this.instantiatedObjects.Values);
  201. // Fill list with other PhotonViews (contains doubles from Instantiated GO's)
  202. foreach (PhotonView view in this.photonViewList.Values)
  203. {
  204. if (view != null && !view.isSceneView && view.gameObject != null)
  205. {
  206. goList.Add(view.gameObject);
  207. }
  208. }
  209. // Destroy GO's
  210. for (int i = goList.Count - 1; i >= 0; i--)
  211. {
  212. GameObject go = goList[i];
  213. if (go != null)
  214. {
  215. if (this.DebugOut >= DebugLevel.ALL)
  216. {
  217. this.DebugReturn(DebugLevel.ALL, "Network destroy Instantiated GO: " + go.name);
  218. }
  219. this.DestroyGO(go);
  220. }
  221. }
  222. this.instantiatedObjects = new Dictionary<int, GameObject>();
  223. PhotonNetwork.manuallyAllocatedViewIds = new List<int>();
  224. PhotonNetwork.lastUsedViewSubId = 0;
  225. PhotonNetwork.lastUsedViewSubIdStatic = 0;
  226. }
  227. if (wasInRoom)
  228. {
  229. SendMonoMessage(PhotonNetworkingMessage.OnLeftRoom);
  230. }
  231. }
  232. /// <summary>
  233. /// This is a safe way to delete GO's as it makes sure to cleanup our PhotonViews instead of relying on "OnDestroy" which is called at the end of the current frame only.
  234. /// </summary>
  235. /// <param name="go">GameObject to destroy.</param>
  236. void DestroyGO(GameObject go)
  237. {
  238. PhotonView[] views = go.GetComponentsInChildren<PhotonView>();
  239. foreach (PhotonView view in views)
  240. {
  241. if (view != null)
  242. {
  243. view.destroyedByPhotonNetworkOrQuit = true;
  244. this.RemovePhotonView(view);
  245. }
  246. }
  247. GameObject.Destroy(go);
  248. }
  249. // gameID can be null (optional). The server assigns a unique name if no name is set
  250. // joins a room and sets your current username as custom actorproperty (will broadcast that)
  251. #endregion
  252. #region Helpers
  253. private void readoutStandardProperties(Hashtable gameProperties, Hashtable pActorProperties, int targetActorNr)
  254. {
  255. // Debug.LogWarning("readoutStandardProperties game=" + gameProperties + " actors(" + pActorProperties + ")=" + pActorProperties + " " + targetActorNr);
  256. // read game properties and cache them locally
  257. if (this.mCurrentGame != null && gameProperties != null)
  258. {
  259. this.mCurrentGame.CacheProperties(gameProperties);
  260. SendMonoMessage(PhotonNetworkingMessage.OnPhotonCustomRoomPropertiesChanged);
  261. if (PhotonNetwork.automaticallySyncScene)
  262. {
  263. this.AutomaticallySyncScene(); // will load new scene if sceneName was changed
  264. }
  265. }
  266. if (pActorProperties != null && pActorProperties.Count > 0)
  267. {
  268. if (targetActorNr > 0)
  269. {
  270. // we have a single entry in the pActorProperties with one
  271. // user's name
  272. // targets MUST exist before you set properties
  273. PhotonPlayer target = this.GetPlayerWithID(targetActorNr);
  274. if (target != null)
  275. {
  276. target.InternalCacheProperties(this.GetActorPropertiesForActorNr(pActorProperties, targetActorNr));
  277. SendMonoMessage(PhotonNetworkingMessage.OnPhotonPlayerPropertiesChanged, target);
  278. }
  279. }
  280. else
  281. {
  282. // in this case, we've got a key-value pair per actor (each
  283. // value is a hashtable with the actor's properties then)
  284. int actorNr;
  285. Hashtable props;
  286. string newName;
  287. PhotonPlayer target;
  288. foreach (object key in pActorProperties.Keys)
  289. {
  290. actorNr = (int)key;
  291. props = (Hashtable)pActorProperties[key];
  292. newName = (string)props[ActorProperties.PlayerName];
  293. target = this.GetPlayerWithID(actorNr);
  294. if (target == null)
  295. {
  296. target = new PhotonPlayer(false, actorNr, newName);
  297. this.AddNewPlayer(actorNr, target);
  298. }
  299. target.InternalCacheProperties(props);
  300. SendMonoMessage(PhotonNetworkingMessage.OnPhotonPlayerPropertiesChanged, target);
  301. }
  302. }
  303. }
  304. }
  305. private void AddNewPlayer(int ID, PhotonPlayer player)
  306. {
  307. if (!this.mActors.ContainsKey(ID))
  308. {
  309. this.mActors[ID] = player;
  310. RebuildPlayerListCopies();
  311. }
  312. else
  313. {
  314. Debug.LogError("Adding player twice: " + ID);
  315. }
  316. }
  317. void RemovePlayer(int ID, PhotonPlayer player)
  318. {
  319. this.mActors.Remove(ID);
  320. if (!player.isLocal)
  321. {
  322. RebuildPlayerListCopies();
  323. }
  324. }
  325. void RebuildPlayerListCopies()
  326. {
  327. this.mPlayerListCopy = new PhotonPlayer[this.mActors.Count];
  328. this.mActors.Values.CopyTo(this.mPlayerListCopy, 0);
  329. List<PhotonPlayer> otherP = new List<PhotonPlayer>();
  330. foreach (PhotonPlayer player in this.mPlayerListCopy)
  331. {
  332. if (!player.isLocal)
  333. {
  334. otherP.Add(player);
  335. }
  336. }
  337. this.mOtherPlayerListCopy = otherP.ToArray();
  338. }
  339. /// <summary>
  340. /// Resets the PhotonView "lastOnSerializeDataSent" so that "OnReliable" synched PhotonViews send a complete state to new clients (if the state doesnt change, no messages would be send otherwise!).
  341. /// Note that due to this reset, ALL other players will receive the full OnSerialize.
  342. /// </summary>
  343. private void ResetPhotonViewsOnSerialize()
  344. {
  345. foreach (PhotonView photonView in this.photonViewList.Values)
  346. {
  347. photonView.lastOnSerializeDataSent = null;
  348. }
  349. }
  350. /// <summary>
  351. /// Called when the event Leave (of some other player) arrived.
  352. /// Cleans game objects, views locally. The master will also clean the
  353. /// </summary>
  354. /// <param name="actorID">ID of player who left.</param>
  355. private void HandleEventLeave(int actorID)
  356. {
  357. if (this.DebugOut >= DebugLevel.INFO)
  358. {
  359. this.DebugReturn(DebugLevel.INFO, "HandleEventLeave actorNr: " + actorID);
  360. }
  361. // actorNr is fetched out of event above
  362. if (actorID < 0 || !this.mActors.ContainsKey(actorID))
  363. {
  364. if (this.DebugOut >= DebugLevel.ERROR)
  365. {
  366. this.DebugReturn(DebugLevel.ERROR, string.Format("Received event Leave for unknown actorNumber: {0}", actorID));
  367. }
  368. return;
  369. }
  370. PhotonPlayer player = this.GetPlayerWithID(actorID);
  371. if (player == null)
  372. {
  373. Debug.LogError("Error: HandleEventLeave for actorID=" + actorID + " has no PhotonPlayer!");
  374. }
  375. // 1: Elect new masterclient, ignore the leaving player (as it's still in playerlists)
  376. if (this.mMasterClient != null && this.mMasterClient.ID == actorID)
  377. {
  378. this.mMasterClient = null;
  379. }
  380. this.CheckMasterClient(actorID);
  381. // 2: Destroy objects & buffered messages
  382. if (this.mCurrentGame != null && this.mCurrentGame.autoCleanUp)
  383. {
  384. this.DestroyPlayerObjects(player, true);
  385. }
  386. RemovePlayer(actorID, player);
  387. // 4: Finally, send notification (the playerList and masterclient are now updated)
  388. SendMonoMessage(PhotonNetworkingMessage.OnPhotonPlayerDisconnected, player);
  389. }
  390. /// <summary>
  391. /// Chooses the new master client. Supply ignoreActorID to ignore a specific actor (e.g. when this actor has just left)
  392. /// </summary>
  393. /// <param name="ignoreActorID"></param>
  394. private void CheckMasterClient(int ignoreActorID)
  395. {
  396. int lowestActorNumber = int.MaxValue;
  397. if (this.mMasterClient != null && this.mActors.ContainsKey(this.mMasterClient.ID))
  398. {
  399. // the current masterClient is still in the list of players, so it can't change
  400. return;
  401. }
  402. // the master is unknown. find lowest actornumber == master
  403. foreach (int actorNumber in this.mActors.Keys)
  404. {
  405. if (ignoreActorID != -1 && ignoreActorID == actorNumber)
  406. {
  407. continue; //Skip this actor as it's leaving.
  408. }
  409. if (actorNumber < lowestActorNumber)
  410. {
  411. lowestActorNumber = actorNumber;
  412. }
  413. }
  414. if (this.mMasterClient == null || this.mMasterClient.ID != lowestActorNumber)
  415. {
  416. this.mMasterClient = this.mActors[lowestActorNumber];
  417. bool leavingPlayerWasMaster = ignoreActorID > 0; // that value is the playerID who's leaving or -1
  418. if (leavingPlayerWasMaster)
  419. {
  420. SendMonoMessage(PhotonNetworkingMessage.OnMasterClientSwitched, this.mMasterClient);
  421. }
  422. }
  423. }
  424. private Hashtable GetActorPropertiesForActorNr(Hashtable actorProperties, int actorNr)
  425. {
  426. if (actorProperties.ContainsKey(actorNr))
  427. {
  428. return (Hashtable)actorProperties[actorNr];
  429. }
  430. return actorProperties;
  431. }
  432. private PhotonPlayer GetPlayerWithID(int number)
  433. {
  434. if (this.mActors != null && this.mActors.ContainsKey(number))
  435. {
  436. return this.mActors[number];
  437. }
  438. return null;
  439. }
  440. private void SendPlayerName()
  441. {
  442. if (this.State == global::PeerState.Joining)
  443. {
  444. // this means, the join on the gameServer is sent (with an outdated name). send the new when in game
  445. this.mPlayernameHasToBeUpdated = true;
  446. return;
  447. }
  448. if (this.mLocalActor != null)
  449. {
  450. this.mLocalActor.name = this.PlayerName;
  451. Hashtable properties = new Hashtable();
  452. properties[ActorProperties.PlayerName] = this.PlayerName;
  453. this.OpSetPropertiesOfActor(this.mLocalActor.ID, properties, true, (byte)0);
  454. this.mPlayernameHasToBeUpdated = false;
  455. }
  456. }
  457. private void GameEnteredOnGameServer(OperationResponse operationResponse)
  458. {
  459. if (operationResponse.ReturnCode != 0)
  460. {
  461. switch (operationResponse.OperationCode)
  462. {
  463. case OperationCode.CreateGame:
  464. this.DebugReturn(DebugLevel.ERROR, "Create failed on GameServer. Changing back to MasterServer. Msg: " + operationResponse.DebugMessage);
  465. SendMonoMessage(PhotonNetworkingMessage.OnPhotonCreateRoomFailed);
  466. break;
  467. case OperationCode.JoinGame:
  468. this.DebugReturn(DebugLevel.WARNING, "Join failed on GameServer. Changing back to MasterServer. Msg: " + operationResponse.DebugMessage);
  469. if (operationResponse.ReturnCode == ErrorCode.GameDoesNotExist)
  470. {
  471. Debug.Log("Most likely the game became empty during the switch to GameServer.");
  472. }
  473. SendMonoMessage(PhotonNetworkingMessage.OnPhotonJoinRoomFailed);
  474. break;
  475. case OperationCode.JoinRandomGame:
  476. this.DebugReturn(DebugLevel.WARNING, "Join failed on GameServer. Changing back to MasterServer. Msg: " + operationResponse.DebugMessage);
  477. if (operationResponse.ReturnCode == ErrorCode.GameDoesNotExist)
  478. {
  479. Debug.Log("Most likely the game became empty during the switch to GameServer.");
  480. }
  481. SendMonoMessage(PhotonNetworkingMessage.OnPhotonRandomJoinFailed);
  482. break;
  483. }
  484. this.DisconnectFromGameServer();
  485. return;
  486. }
  487. this.State = global::PeerState.Joined;
  488. this.mRoomToGetInto.isLocalClientInside = true;
  489. Hashtable actorProperties = (Hashtable)operationResponse[ParameterCode.PlayerProperties];
  490. Hashtable gameProperties = (Hashtable)operationResponse[ParameterCode.GameProperties];
  491. this.readoutStandardProperties(gameProperties, actorProperties, 0);
  492. // the local player's actor-properties are not returned in join-result. add this player to the list
  493. int localActorNr = (int)operationResponse[ParameterCode.ActorNr];
  494. this.ChangeLocalID(localActorNr);
  495. this.CheckMasterClient(-1);
  496. if (this.mPlayernameHasToBeUpdated)
  497. {
  498. this.SendPlayerName();
  499. }
  500. switch (operationResponse.OperationCode)
  501. {
  502. case OperationCode.CreateGame:
  503. SendMonoMessage(PhotonNetworkingMessage.OnCreatedRoom);
  504. break;
  505. case OperationCode.JoinGame:
  506. case OperationCode.JoinRandomGame:
  507. // the mono message for this is sent at another place
  508. break;
  509. }
  510. }
  511. private Hashtable GetLocalActorProperties()
  512. {
  513. if (PhotonNetwork.player != null)
  514. {
  515. return PhotonNetwork.player.allProperties;
  516. }
  517. Hashtable actorProperties = new Hashtable();
  518. actorProperties[ActorProperties.PlayerName] = this.PlayerName;
  519. return actorProperties;
  520. }
  521. public void ChangeLocalID(int newID)
  522. {
  523. if (this.mLocalActor == null)
  524. {
  525. Debug.LogWarning(
  526. string.Format(
  527. "Local actor is null or not in mActors! mLocalActor: {0} mActors==null: {1} newID: {2}",
  528. this.mLocalActor,
  529. this.mActors == null,
  530. newID));
  531. }
  532. if (this.mActors.ContainsKey(this.mLocalActor.ID))
  533. {
  534. this.mActors.Remove(this.mLocalActor.ID);
  535. }
  536. this.mLocalActor.InternalChangeLocalID(newID);
  537. this.mActors[this.mLocalActor.ID] = this.mLocalActor;
  538. this.RebuildPlayerListCopies();
  539. }
  540. #endregion
  541. #region Operations
  542. public bool OpCreateGame(string gameID, bool isVisible, bool isOpen, byte maxPlayers, bool autoCleanUp, Hashtable customGameProperties, string[] propsListedInLobby)
  543. {
  544. this.mRoomToGetInto = new Room(gameID, customGameProperties, isVisible, isOpen, maxPlayers, autoCleanUp, propsListedInLobby);
  545. return base.OpCreateRoom(gameID, isVisible, isOpen, maxPlayers, autoCleanUp, customGameProperties, this.GetLocalActorProperties(), propsListedInLobby);
  546. }
  547. public bool OpJoin(string gameID)
  548. {
  549. this.mRoomToGetInto = new Room(gameID, null);
  550. return this.OpJoinRoom(gameID, this.GetLocalActorProperties());
  551. }
  552. // this override just makes sure we have a mRoomToGetInto, even if it's blank (the properties provided in this method are filters. they are not set when we join the game)
  553. public override bool OpJoinRandomRoom(Hashtable expectedCustomRoomProperties, byte expectedMaxPlayers, Hashtable playerProperties, MatchmakingMode matchingType)
  554. {
  555. this.mRoomToGetInto = new Room(null, null);
  556. return base.OpJoinRandomRoom(expectedCustomRoomProperties, expectedMaxPlayers, playerProperties, matchingType);
  557. }
  558. /// <summary>
  559. /// Operation Leave will exit any current room.
  560. /// </summary>
  561. /// <remarks>
  562. /// This also happens when you disconnect from the server.
  563. /// Disconnect might be a step less if you don't want to create a new room on the same server.
  564. /// </remarks>
  565. /// <returns></returns>
  566. public virtual bool OpLeave()
  567. {
  568. if (this.State != global::PeerState.Joined)
  569. {
  570. this.DebugReturn(DebugLevel.ERROR, "NetworkingPeer::leaveGame() - ERROR: no game is currently joined");
  571. return false;
  572. }
  573. return this.OpCustom((byte)OperationCode.Leave, null, true, 0);
  574. }
  575. public override bool OpRaiseEvent(byte eventCode, byte interestGroup, Hashtable evData, bool sendReliable, byte channelId)
  576. {
  577. if (PhotonNetwork.offlineMode)
  578. {
  579. return false;
  580. }
  581. return base.OpRaiseEvent(eventCode, interestGroup, evData, sendReliable, channelId);
  582. }
  583. public override bool OpRaiseEvent(byte eventCode, Hashtable evData, bool sendReliable, byte channelId, int[] targetActors, EventCaching cache)
  584. {
  585. if (PhotonNetwork.offlineMode)
  586. {
  587. return false;
  588. }
  589. return base.OpRaiseEvent(eventCode, evData, sendReliable, channelId, targetActors, cache);
  590. }
  591. public override bool OpRaiseEvent(byte eventCode, Hashtable evData, bool sendReliable, byte channelId, EventCaching cache, ReceiverGroup receivers)
  592. {
  593. if (PhotonNetwork.offlineMode)
  594. {
  595. return false;
  596. }
  597. return base.OpRaiseEvent(eventCode, evData, sendReliable, channelId, cache, receivers);
  598. }
  599. #endregion
  600. #region Implementation of IPhotonPeerListener
  601. public void DebugReturn(DebugLevel level, string message)
  602. {
  603. this.externalListener.DebugReturn(level, message);
  604. }
  605. public void OnOperationResponse(OperationResponse operationResponse)
  606. {
  607. if (PhotonNetwork.networkingPeer.State == global::PeerState.Disconnecting)
  608. {
  609. if (this.DebugOut >= DebugLevel.INFO)
  610. {
  611. this.DebugReturn(DebugLevel.INFO, "OperationResponse ignored while disconnecting: " + operationResponse.OperationCode);
  612. }
  613. return;
  614. }
  615. // extra logging for error debugging (helping developers with a bit of automated analysis)
  616. if (operationResponse.ReturnCode == 0)
  617. {
  618. if (this.DebugOut >= DebugLevel.INFO)
  619. {
  620. this.DebugReturn(DebugLevel.INFO, operationResponse.ToString());
  621. }
  622. }
  623. else
  624. {
  625. if (this.DebugOut >= DebugLevel.WARNING)
  626. {
  627. if (operationResponse.ReturnCode == ErrorCode.OperationNotAllowedInCurrentState)
  628. {
  629. this.DebugReturn(DebugLevel.WARNING, "Operation could not be executed yet. Wait for state JoinedLobby or ConnectedToMaster and their respective callbacks before calling OPs. Client must be authorized.");
  630. }
  631. this.DebugReturn(DebugLevel.WARNING, operationResponse.ToStringFull());
  632. }
  633. }
  634. switch (operationResponse.OperationCode)
  635. {
  636. case OperationCode.Authenticate:
  637. {
  638. // PeerState oldState = this.State;
  639. if (operationResponse.ReturnCode != 0)
  640. {
  641. if (this.DebugOut >= DebugLevel.ERROR)
  642. {
  643. this.DebugReturn(DebugLevel.ERROR, string.Format("Authentication failed: '{0}' Code: {1}", operationResponse.DebugMessage, operationResponse.ReturnCode));
  644. }
  645. if (operationResponse.ReturnCode == ErrorCode.InvalidOperationCode)
  646. {
  647. this.DebugReturn(DebugLevel.ERROR, string.Format("If you host Photon yourself, make sure to start the 'Instance LoadBalancing'"));
  648. }
  649. if (operationResponse.ReturnCode == ErrorCode.InvalidAuthentication)
  650. {
  651. this.DebugReturn(DebugLevel.ERROR, string.Format("The appId this client sent is unknown on the server (Cloud). Check settings. If using the Cloud, check account."));
  652. }
  653. this.Disconnect();
  654. this.State = global::PeerState.Disconnecting;
  655. if (operationResponse.ReturnCode == ErrorCode.MaxCcuReached)
  656. {
  657. this.DebugReturn(DebugLevel.ERROR, string.Format("Currently, the limit of users is reached for this title. Try again later. Disconnecting"));
  658. SendMonoMessage(PhotonNetworkingMessage.OnPhotonMaxCccuReached);
  659. SendMonoMessage(PhotonNetworkingMessage.OnConnectionFail, DisconnectCause.MaxCcuReached);
  660. }
  661. else if (operationResponse.ReturnCode == ErrorCode.InvalidRegion)
  662. {
  663. this.DebugReturn(DebugLevel.ERROR, string.Format("The used master server address is not available with the subscription currently used. Got to Photon Cloud Dashboard or change URL. Disconnecting"));
  664. SendMonoMessage(PhotonNetworkingMessage.OnConnectionFail, DisconnectCause.InvalidRegion);
  665. }
  666. break;
  667. }
  668. else
  669. {
  670. if (this.State == global::PeerState.Connected || this.State == global::PeerState.ConnectedComingFromGameserver)
  671. {
  672. if (operationResponse.Parameters.ContainsKey(ParameterCode.Position))
  673. {
  674. this.mQueuePosition = (int)operationResponse[ParameterCode.Position];
  675. // returnValues for Authenticate always include this value!
  676. if (this.mQueuePosition > 0)
  677. {
  678. // should only happen, if just out of nowhere the
  679. // amount of players going online at the same time
  680. // is increasing faster, than automatically started
  681. // additional gameservers could have been booten up
  682. if (this.State == global::PeerState.ConnectedComingFromGameserver)
  683. {
  684. this.State = global::PeerState.QueuedComingFromGameserver;
  685. }
  686. else
  687. {
  688. this.State = global::PeerState.Queued;
  689. }
  690. // we break here (not joining the lobby, etc) as this client is queued
  691. // the EventCode.QueueState will eventually resolve this state
  692. break;
  693. }
  694. }
  695. if (PhotonNetwork.autoJoinLobby)
  696. {
  697. this.OpJoinLobby();
  698. this.State = global::PeerState.Authenticated;
  699. }
  700. else
  701. {
  702. this.State = global::PeerState.ConnectedToMaster;
  703. NetworkingPeer.SendMonoMessage(PhotonNetworkingMessage.OnConnectedToMaster);
  704. }
  705. }
  706. else if (this.State == global::PeerState.ConnectedToGameserver)
  707. {
  708. this.State = global::PeerState.Joining;
  709. if (this.mLastJoinType == JoinType.JoinGame || this.mLastJoinType == JoinType.JoinRandomGame)
  710. {
  711. // if we just "join" the game, do so
  712. this.OpJoin(this.mRoomToGetInto.name);
  713. }
  714. else if (this.mLastJoinType == JoinType.CreateGame)
  715. {
  716. // on the game server, we have to apply the room properties that were chosen for creation of the room, so we use this.mRoomToGetInto
  717. this.OpCreateGame(
  718. this.mRoomToGetInto.name,
  719. this.mRoomToGetInto.visible,
  720. this.mRoomToGetInto.open,
  721. (byte)this.mRoomToGetInto.maxPlayers,
  722. this.mRoomToGetInto.autoCleanUp,
  723. this.mRoomToGetInto.customProperties,
  724. this.mRoomToGetInto.propertiesListedInLobby);
  725. }
  726. break;
  727. }
  728. }
  729. break;
  730. }
  731. case OperationCode.CreateGame:
  732. {
  733. if (this.State != global::PeerState.Joining)
  734. {
  735. if (operationResponse.ReturnCode != 0)
  736. {
  737. if (this.DebugOut >= DebugLevel.ERROR)
  738. {
  739. this.DebugReturn(DebugLevel.ERROR, string.Format("createGame failed, client stays on masterserver: {0}.", operationResponse.ToStringFull()));
  740. }
  741. SendMonoMessage(PhotonNetworkingMessage.OnPhotonCreateRoomFailed);
  742. break;
  743. }
  744. string gameID = (string)operationResponse[ParameterCode.RoomName];
  745. if (!string.IsNullOrEmpty(gameID))
  746. {
  747. // is only sent by the server's response, if it has not been
  748. // sent with the client's request before!
  749. this.mRoomToGetInto.name = gameID;
  750. }
  751. this.mGameserver = (string)operationResponse[ParameterCode.Address];
  752. this.DisconnectFromMaster();
  753. this.mLastJoinType = JoinType.CreateGame;
  754. }
  755. else
  756. {
  757. this.GameEnteredOnGameServer(operationResponse);
  758. }
  759. break;
  760. }
  761. case OperationCode.JoinGame:
  762. {
  763. if (this.State != global::PeerState.Joining)
  764. {
  765. if (operationResponse.ReturnCode != 0)
  766. {
  767. SendMonoMessage(PhotonNetworkingMessage.OnPhotonJoinRoomFailed);
  768. if (this.DebugOut >= DebugLevel.WARNING)
  769. {
  770. this.DebugReturn(DebugLevel.WARNING, string.Format("JoinRoom failed (room maybe closed by now). Client stays on masterserver: {0}. State: {1}", operationResponse.ToStringFull(), this.State));
  771. }
  772. // this.mListener.joinGameReturn(0, null, null, returnCode, debugMsg);
  773. break;
  774. }
  775. this.mGameserver = (string)operationResponse[ParameterCode.Address];
  776. this.DisconnectFromMaster();
  777. this.mLastJoinType = JoinType.JoinGame;
  778. }
  779. else
  780. {
  781. this.GameEnteredOnGameServer(operationResponse);
  782. }
  783. break;
  784. }
  785. case OperationCode.JoinRandomGame:
  786. {
  787. // happens only on master. on gameserver, this is a regular join (we don't need to find a random game again)
  788. // the operation OpJoinRandom either fails (with returncode 8) or returns game-to-join information
  789. if (operationResponse.ReturnCode != 0)
  790. {
  791. if (operationResponse.ReturnCode == ErrorCode.NoRandomMatchFound)
  792. {
  793. this.DebugReturn(DebugLevel.WARNING, "JoinRandom failed: No open game. Client stays in lobby.");
  794. }
  795. else if (this.DebugOut >= DebugLevel.ERROR)
  796. {
  797. this.DebugReturn(DebugLevel.ERROR, string.Format("JoinRandom failed: {0}.", operationResponse.ToStringFull()));
  798. }
  799. SendMonoMessage(PhotonNetworkingMessage.OnPhotonRandomJoinFailed);
  800. // this.mListener.createGameReturn(0, null, null, returnCode, debugMsg);
  801. break;
  802. }
  803. string gameID = (string)operationResponse[ParameterCode.RoomName];
  804. this.mRoomToGetInto.name = gameID;
  805. this.mGameserver = (string)operationResponse[ParameterCode.Address];
  806. this.DisconnectFromMaster();
  807. this.mLastJoinType = JoinType.JoinRandomGame;
  808. break;
  809. }
  810. case OperationCode.JoinLobby:
  811. this.State = global::PeerState.JoinedLobby;
  812. this.insideLobby = true;
  813. SendMonoMessage(PhotonNetworkingMessage.OnJoinedLobby);
  814. // this.mListener.joinLobbyReturn();
  815. break;
  816. case OperationCode.LeaveLobby:
  817. this.State = global::PeerState.Authenticated;
  818. this.LeftLobbyCleanup();
  819. break;
  820. case OperationCode.Leave:
  821. this.DisconnectFromGameServer();
  822. break;
  823. case OperationCode.SetProperties:
  824. // this.mListener.setPropertiesReturn(returnCode, debugMsg);
  825. break;
  826. case OperationCode.GetProperties:
  827. {
  828. Hashtable actorProperties = (Hashtable)operationResponse[ParameterCode.PlayerProperties];
  829. Hashtable gameProperties = (Hashtable)operationResponse[ParameterCode.GameProperties];
  830. this.readoutStandardProperties(gameProperties, actorProperties, 0);
  831. // RemoveByteTypedPropertyKeys(actorProperties, false);
  832. // RemoveByteTypedPropertyKeys(gameProperties, false);
  833. // this.mListener.getPropertiesReturn(gameProperties, actorProperties, returnCode, debugMsg);
  834. break;
  835. }
  836. case OperationCode.RaiseEvent:
  837. // this usually doesn't give us a result. only if the caching is affected the server will send one.
  838. break;
  839. default:
  840. if (this.DebugOut >= DebugLevel.ERROR)
  841. {
  842. this.DebugReturn(DebugLevel.ERROR, string.Format("operationResponse unhandled: {0}", operationResponse.ToString()));
  843. }
  844. break;
  845. }
  846. this.externalListener.OnOperationResponse(operationResponse);
  847. }
  848. public void OnStatusChanged(StatusCode statusCode)
  849. {
  850. if (this.DebugOut >= DebugLevel.INFO)
  851. {
  852. this.DebugReturn(DebugLevel.INFO, string.Format("OnStatusChanged: {0}", statusCode.ToString()));
  853. }
  854. switch (statusCode)
  855. {
  856. case StatusCode.Connect:
  857. if (this.State == global::PeerState.ConnectingToGameserver)
  858. {
  859. if (this.DebugOut >= DebugLevel.ALL)
  860. {
  861. this.DebugReturn(DebugLevel.ALL, "Connected to gameserver.");
  862. }
  863. this.State = global::PeerState.ConnectedToGameserver;
  864. }
  865. else
  866. {
  867. if (this.DebugOut >= DebugLevel.ALL)
  868. {
  869. this.DebugReturn(DebugLevel.ALL, "Connected to masterserver.");
  870. }
  871. if (this.State == global::PeerState.Connecting)
  872. {
  873. SendMonoMessage(PhotonNetworkingMessage.OnConnectedToPhoton);
  874. this.State = global::PeerState.Connected;
  875. }
  876. else
  877. {
  878. this.State = global::PeerState.ConnectedComingFromGameserver;
  879. }
  880. }
  881. if (this.requestSecurity)
  882. {
  883. this.EstablishEncryption();
  884. }
  885. else
  886. {
  887. if (!this.OpAuthenticate(this.mAppId, this.mAppVersion))
  888. {
  889. this.externalListener.DebugReturn(DebugLevel.ERROR, "Error Authenticating! Did not work.");
  890. }
  891. }
  892. break;
  893. case StatusCode.Disconnect:
  894. if (this.State == global::PeerState.DisconnectingFromMasterserver)
  895. {
  896. if (this.Connect(this.mGameserver, this.mAppId))
  897. {
  898. this.State = global::PeerState.ConnectingToGameserver;
  899. }
  900. }
  901. else if (this.State == global::PeerState.DisconnectingFromGameserver)
  902. {
  903. if (this.Connect(this.masterServerAddress, this.mAppId))
  904. {
  905. this.State = global::PeerState.ConnectingToMasterserver;
  906. }
  907. }
  908. else
  909. {
  910. this.LeftRoomCleanup();
  911. this.State = global::PeerState.PeerCreated;
  912. SendMonoMessage(PhotonNetworkingMessage.OnDisconnectedFromPhoton);
  913. }
  914. break;
  915. case StatusCode.SecurityExceptionOnConnect:
  916. case StatusCode.ExceptionOnConnect:
  917. this.State = global::PeerState.PeerCreated;
  918. DisconnectCause cause = (DisconnectCause)statusCode;
  919. SendMonoMessage(PhotonNetworkingMessage.OnFailedToConnectToPhoton, cause);
  920. break;
  921. case StatusCode.Exception:
  922. if (this.State == global::PeerState.Connecting)
  923. {
  924. this.DebugReturn(DebugLevel.WARNING, "Exception while connecting to: " + this.ServerAddress + ". Check if the server is available.");
  925. if (this.ServerAddress == null || this.ServerAddress.StartsWith("127.0.0.1"))
  926. {
  927. this.DebugReturn(DebugLevel.WARNING, "The server address is 127.0.0.1 (localhost): Make sure the server is running on this machine. Android and iOS emulators have their own localhost.");
  928. if (this.ServerAddress == this.mGameserver)
  929. {
  930. this.DebugReturn(DebugLevel.WARNING, "This might be a misconfiguration in the game server config. You need to edit it to a (public) address.");
  931. }
  932. }
  933. this.State = global::PeerState.PeerCreated;
  934. cause = (DisconnectCause)statusCode;
  935. SendMonoMessage(PhotonNetworkingMessage.OnFailedToConnectToPhoton, cause);
  936. }
  937. else
  938. {
  939. this.State = global::PeerState.PeerCreated;
  940. cause = (DisconnectCause)statusCode;
  941. SendMonoMessage(PhotonNetworkingMessage.OnConnectionFail, cause);
  942. }
  943. this.Disconnect();
  944. break;
  945. case StatusCode.TimeoutDisconnect:
  946. case StatusCode.InternalReceiveException:
  947. case StatusCode.DisconnectByServer:
  948. case StatusCode.DisconnectByServerLogic:
  949. case StatusCode.DisconnectByServerUserLimit:
  950. if (this.State == global::PeerState.Connecting)
  951. {
  952. this.DebugReturn(DebugLevel.WARNING, statusCode + " while connecting to: " + this.ServerAddress + ". Check if the server is available.");
  953. this.State = global::PeerState.PeerCreated;
  954. cause = (DisconnectCause)statusCode;
  955. SendMonoMessage(PhotonNetworkingMessage.OnFailedToConnectToPhoton, cause);
  956. }
  957. else
  958. {
  959. this.State = global::PeerState.PeerCreated;
  960. cause = (DisconnectCause)statusCode;
  961. SendMonoMessage(PhotonNetworkingMessage.OnConnectionFail, cause);
  962. }
  963. this.Disconnect();
  964. break;
  965. case StatusCode.SendError:
  966. // this.mListener.clientErrorReturn(statusCode);
  967. break;
  968. case StatusCode.QueueOutgoingReliableWarning:
  969. case StatusCode.QueueOutgoingUnreliableWarning:
  970. case StatusCode.QueueOutgoingAcksWarning:
  971. case StatusCode.QueueSentWarning:
  972. // this.mListener.warningReturn(statusCode);
  973. break;
  974. case StatusCode.EncryptionEstablished:
  975. if (!this.OpAuthenticate(this.mAppId, this.mAppVersion))
  976. {
  977. this.externalListener.DebugReturn(DebugLevel.ERROR, "Error Authenticating! Did not work.");
  978. }
  979. break;
  980. case StatusCode.EncryptionFailedToEstablish:
  981. this.externalListener.DebugReturn(DebugLevel.ERROR, "Encryption wasn't established: " + statusCode + ". Going to authenticate anyways.");
  982. if (!this.OpAuthenticate(this.mAppId, this.mAppVersion))
  983. {
  984. this.externalListener.DebugReturn(DebugLevel.ERROR, "Error Authenticating! Did not work.");
  985. }
  986. break;
  987. // // TCP "routing" is an option of Photon that's not currently needed (or supported) by PUN
  988. //case StatusCode.TcpRouterResponseOk:
  989. // break;
  990. //case StatusCode.TcpRouterResponseEndpointUnknown:
  991. //case StatusCode.TcpRouterResponseNodeIdUnknown:
  992. //case StatusCode.TcpRouterResponseNodeNotReady:
  993. // this.DebugReturn(DebugLevel.ERROR, "Unexpected router response: " + statusCode);
  994. // break;
  995. default:
  996. // this.mListener.serverErrorReturn(statusCode.value());
  997. this.DebugReturn(DebugLevel.ERROR, "Received unknown status code: " + statusCode);
  998. break;
  999. }
  1000. this.externalListener.OnStatusChanged(statusCode);
  1001. }
  1002. public void OnEvent(EventData photonEvent)
  1003. {
  1004. if (this.DebugOut >= DebugLevel.INFO)
  1005. {
  1006. this.DebugReturn(DebugLevel.INFO, string.Format("OnEvent: {0}", photonEvent.ToString()));
  1007. }
  1008. int actorNr = -1;
  1009. PhotonPlayer originatingPlayer = null;
  1010. if (photonEvent.Parameters.ContainsKey(ParameterCode.ActorNr))
  1011. {
  1012. actorNr = (int)photonEvent[ParameterCode.ActorNr];
  1013. if (this.mActors.ContainsKey(actorNr))
  1014. {
  1015. originatingPlayer = (PhotonPlayer)this.mActors[actorNr];
  1016. }
  1017. //else
  1018. //{
  1019. // // the actor sending this event is not in actorlist. this is usually no problem
  1020. // if (photonEvent.Code != (byte)LiteOpCode.Join)
  1021. // {
  1022. // Debug.LogWarning("Received event, but we do not have this actor: " + actorNr);
  1023. // }
  1024. //}
  1025. }
  1026. switch (photonEvent.Code)
  1027. {
  1028. case EventCode.GameList:
  1029. {
  1030. this.mGameList = new Dictionary<string, RoomInfo>();
  1031. Hashtable games = (Hashtable)photonEvent[ParameterCode.GameList];
  1032. foreach (DictionaryEntry game in games)
  1033. {
  1034. string gameName = (string)game.Key;
  1035. this.mGameList[gameName] = new RoomInfo(gameName, (Hashtable)game.Value);
  1036. }
  1037. mGameListCopy = new RoomInfo[mGameList.Count];
  1038. mGameList.Values.CopyTo(mGameListCopy, 0);
  1039. SendMonoMessage(PhotonNetworkingMessage.OnReceivedRoomListUpdate);
  1040. break;
  1041. }
  1042. case EventCode.GameListUpdate:
  1043. {
  1044. Hashtable games = (Hashtable)photonEvent[ParameterCode.GameList];
  1045. foreach (DictionaryEntry room in games)
  1046. {
  1047. string gameName = (string)room.Key;
  1048. Room game = new Room(gameName, (Hashtable)room.Value);
  1049. if (game.removedFromList)
  1050. {
  1051. this.mGameList.Remove(gameName);
  1052. }
  1053. else
  1054. {
  1055. this.mGameList[gameName] = game;
  1056. }
  1057. }
  1058. this.mGameListCopy = new RoomInfo[this.mGameList.Count];
  1059. this.mGameList.Values.CopyTo(this.mGameListCopy, 0);
  1060. SendMonoMessage(PhotonNetworkingMessage.OnReceivedRoomListUpdate);
  1061. break;
  1062. }
  1063. case EventCode.QueueState:
  1064. if (photonEvent.Parameters.ContainsKey(ParameterCode.Position))
  1065. {
  1066. this.mQueuePosition = (int)photonEvent[ParameterCode.Position];
  1067. }
  1068. else
  1069. {
  1070. this.DebugReturn(DebugLevel.ERROR, "Event QueueState must contain position!");
  1071. }
  1072. if (this.mQueuePosition == 0)
  1073. {
  1074. // once we're un-queued, let's join the lobby or simply be "connected to master"
  1075. if (PhotonNetwork.autoJoinLobby)
  1076. {
  1077. this.OpJoinLobby();
  1078. this.State = global::PeerState.Authenticated;
  1079. }
  1080. else
  1081. {
  1082. this.State = global::PeerState.ConnectedToMaster;
  1083. NetworkingPeer.SendMonoMessage(PhotonNetworkingMessage.OnConnectedToMaster);
  1084. }
  1085. }
  1086. break;
  1087. case EventCode.AppStats:
  1088. // Debug.LogInfo("Received stats!");
  1089. this.mPlayersInRoomsCount = (int)photonEvent[ParameterCode.PeerCount];
  1090. this.mPlayersOnMasterCount = (int)photonEvent[ParameterCode.MasterPeerCount];
  1091. this.mGameCount = (int)photonEvent[ParameterCode.GameCount];
  1092. break;
  1093. case EventCode.Join:
  1094. // actorNr is fetched out of event above
  1095. Hashtable actorProperties = (Hashtable)photonEvent[ParameterCode.PlayerProperties];
  1096. if (originatingPlayer == null)
  1097. {
  1098. bool isLocal = this.mLocalActor.ID == actorNr;
  1099. this.AddNewPlayer(actorNr, new PhotonPlayer(isLocal, actorNr, actorProperties));
  1100. this.ResetPhotonViewsOnSerialize(); // This sets the correct OnSerializeState for Reliable OnSerialize
  1101. }
  1102. if (this.mActors[actorNr] == this.mLocalActor)
  1103. {
  1104. // in this player's 'own' join event, we get a complete list of players in the room, so check if we know all players
  1105. int[] actorsInRoom = (int[])photonEvent[ParameterCode.ActorList];
  1106. foreach (int actorNrToCheck in actorsInRoom)
  1107. {
  1108. if (this.mLocalActor.ID != actorNrToCheck && !this.mActors.ContainsKey(actorNrToCheck))
  1109. {
  1110. Debug.Log("creating player");
  1111. this.AddNewPlayer(actorNrToCheck, new PhotonPlayer(false, actorNrToCheck, string.Empty));
  1112. }
  1113. }
  1114. SendMonoMessage(PhotonNetworkingMessage.OnJoinedRoom);
  1115. }
  1116. else
  1117. {
  1118. SendMonoMessage(PhotonNetworkingMessage.OnPhotonPlayerConnected, this.mActors[actorNr]);
  1119. }
  1120. break;
  1121. case EventCode.Leave:
  1122. this.HandleEventLeave(actorNr);
  1123. break;
  1124. case EventCode.PropertiesChanged:
  1125. int targetActorNr = (int)photonEvent[ParameterCode.TargetActorNr];
  1126. Hashtable gameProperties = null;
  1127. Hashtable actorProps = null;
  1128. if (targetActorNr == 0)
  1129. {
  1130. gameProperties = (Hashtable)photonEvent[ParameterCode.Properties];
  1131. }
  1132. else
  1133. {
  1134. actorProps = (Hashtable)photonEvent[ParameterCode.Properties];
  1135. }
  1136. this.readoutStandardProperties(gameProperties, actorProps, targetActorNr);
  1137. break;
  1138. case PhotonNetworkMessages.RPC:
  1139. //ts: each event now contains a single RPC. execute this
  1140. this.ExecuteRPC(photonEvent[ParameterCode.Data] as Hashtable, originatingPlayer);
  1141. break;
  1142. case PhotonNetworkMessages.SendSerialize:
  1143. case PhotonNetworkMessages.SendSerializeReliable:
  1144. Hashtable serializeData = (Hashtable)photonEvent[ParameterCode.Data];
  1145. //Debug.Log(serializeData.ToStringFull());
  1146. int remoteUpdateServerTimestamp = (int)serializeData[(byte)0];
  1147. short remoteLevelPrefix = -1;
  1148. short initialDataIndex = 1;
  1149. if (serializeData.ContainsKey((byte)1))
  1150. {
  1151. remoteLevelPrefix = (short)serializeData[(byte)1];
  1152. initialDataIndex = 2;
  1153. }
  1154. for (short s = initialDataIndex; s < serializeData.Count; s++)
  1155. {
  1156. this.OnSerializeRead(serializeData[s] as Hashtable, originatingPlayer, remoteUpdateServerTimestamp, remoteLevelPrefix);
  1157. }
  1158. break;
  1159. case PhotonNetworkMessages.Instantiation:
  1160. this.DoInstantiate((Hashtable)photonEvent[ParameterCode.Data], originatingPlayer, null);
  1161. break;
  1162. case PhotonNetworkMessages.CloseConnection:
  1163. // MasterClient "requests" a disconnection from us
  1164. if (originatingPlayer == null || !originatingPlayer.isMasterClient)
  1165. {
  1166. Debug.LogError("Error: Someone else(" + originatingPlayer + ") then the masterserver requests a disconnect!");
  1167. }
  1168. else
  1169. {
  1170. PhotonNetwork.LeaveRoom();
  1171. }
  1172. break;
  1173. case PhotonNetworkMessages.Destroy:
  1174. Hashtable data = (Hashtable)photonEvent[ParameterCode.Data];
  1175. int viewID = (int)data[(byte)0];
  1176. PhotonView view = this.GetPhotonView(viewID);
  1177. if (view == null || originatingPlayer == null)
  1178. {
  1179. Debug.LogError("ERROR: Illegal destroy request on view ID=" + viewID + " from player/actorNr: " + actorNr + " view=" + view + " orgPlayer=" + originatingPlayer);
  1180. }
  1181. else
  1182. {
  1183. // use this check when a master-switch also changes the owner
  1184. //if (originatingPlayer == view.owner)
  1185. //{
  1186. this.DestroyPhotonView(view, true);
  1187. //}
  1188. }
  1189. break;
  1190. default:
  1191. // actorNr might be null. it is fetched out of event on top of method
  1192. // Hashtable eventContent = (Hashtable) photonEvent[ParameterCode.Data];
  1193. // this.mListener.customEventAction(actorNr, eventCode, eventContent);
  1194. Debug.LogError("Error. Unhandled event: " + photonEvent);
  1195. break;
  1196. }
  1197. this.externalListener.OnEvent(photonEvent);
  1198. }
  1199. #endregion
  1200. public static void SendMonoMessage(PhotonNetworkingMessage methodString, params object[] parameters)
  1201. {
  1202. HashSet<GameObject> haveSendGOS = new HashSet<GameObject>();
  1203. MonoBehaviour[] mos = (MonoBehaviour[])GameObject.FindObjectsOfType(typeof(MonoBehaviour));
  1204. for (int index = 0; index < mos.Length; index++)
  1205. {
  1206. MonoBehaviour mo = mos[index];
  1207. if (!haveSendGOS.Contains(mo.gameObject))
  1208. {
  1209. haveSendGOS.Add(mo.gameObject);
  1210. if (parameters != null && parameters.Length == 1)
  1211. {
  1212. mo.SendMessage(methodString.ToString(), parameters[0], SendMessageOptions.DontRequireReceiver);
  1213. }
  1214. else
  1215. {
  1216. mo.SendMessage(methodString.ToString(), parameters, SendMessageOptions.DontRequireReceiver);
  1217. }
  1218. }
  1219. }
  1220. }
  1221. // PHOTONVIEW/RPC related
  1222. /// <summary>
  1223. /// Executes a received RPC event
  1224. /// </summary>
  1225. public void ExecuteRPC(Hashtable rpcData, PhotonPlayer sender)
  1226. {
  1227. if (rpcData == null || !rpcData.ContainsKey((byte)0))
  1228. {
  1229. this.DebugReturn(DebugLevel.ERROR, "Malformed RPC; this should never occur.");
  1230. return;
  1231. }
  1232. // ts: updated with "flat" event data
  1233. int netViewID = (int)rpcData[(byte)0]; // LIMITS PHOTONVIEWS&PLAYERS
  1234. int otherSidePrefix = 0; // by default, the prefix is 0 (and this is not being sent)
  1235. if (rpcData.ContainsKey((byte)1))
  1236. {
  1237. otherSidePrefix = (short)rpcData[(byte)1];
  1238. }
  1239. string inMethodName;
  1240. if (rpcData.ContainsKey((byte)5))
  1241. {
  1242. int rpcIndex = (byte)rpcData[(byte)5]; // LIMITS RPC COUNT
  1243. if (rpcIndex > PhotonNetwork.PhotonServerSettings.RpcList.Count - 1)
  1244. {
  1245. Debug.LogError("Could not find RPC with index: " + rpcIndex + ". Going to ignore! Check PhotonServerSettings.RpcList");
  1246. return;
  1247. }
  1248. else
  1249. {
  1250. inMethodName = PhotonNetwork.PhotonServerSettings.RpcList[rpcIndex];
  1251. }
  1252. }
  1253. else
  1254. {
  1255. inMethodName = (string)rpcData[(byte)3];
  1256. }
  1257. object[] inMethodParameters = null;
  1258. if (rpcData.ContainsKey((byte)4))
  1259. {
  1260. inMethodParameters = (object[])rpcData[(byte)4];
  1261. }
  1262. if (inMethodParameters == null)
  1263. {
  1264. inMethodParameters = new object[0];
  1265. }
  1266. PhotonView photonNetview = this.GetPhotonView(netViewID);
  1267. if (photonNetview == null)
  1268. {
  1269. Debug.LogError("Received RPC \"" + inMethodName + "\" for viewID " + netViewID + " but this PhotonView does not exist!");
  1270. return;
  1271. }
  1272. if (photonNetview.prefix != otherSidePrefix)
  1273. {
  1274. Debug.LogError(
  1275. "Received RPC \"" + inMethodName + "\" on viewID " + netViewID + " with a prefix of " + otherSidePrefix
  1276. + ", our prefix is " + photonNetview.prefix + ". The RPC has been ignored.");
  1277. return;
  1278. }
  1279. // Get method name
  1280. if (inMethodName == string.Empty)
  1281. {
  1282. this.DebugReturn(DebugLevel.ERROR, "Malformed RPC; this should never occur.");
  1283. return;
  1284. }
  1285. if (this.DebugOut >= DebugLevel.ALL)
  1286. {
  1287. this.DebugReturn(DebugLevel.ALL, "Received RPC; " + inMethodName);
  1288. }
  1289. // SetReceiving filtering
  1290. if (photonNetview.group != 0 && !allowedReceivingGroups.Contains(photonNetview.group))
  1291. {
  1292. return; // Ignore group
  1293. }
  1294. Type[] argTypes = Type.EmptyTypes;
  1295. if (inMethodParameters.Length > 0)
  1296. {
  1297. argTypes = new Type[inMethodParameters.Length];
  1298. int i = 0;
  1299. for (int index = 0; index < inMethodParameters.Length; index++)
  1300. {
  1301. object objX = inMethodParameters[index];
  1302. if (objX == null)
  1303. {
  1304. argTypes[i] = null;
  1305. }
  1306. else
  1307. {
  1308. argTypes[i] = objX.GetType();
  1309. }
  1310. i++;
  1311. }
  1312. }
  1313. int receivers = 0;
  1314. int foundMethods = 0;
  1315. MonoBehaviour[] mbComponents = photonNetview.GetComponents<MonoBehaviour>(); // NOTE: we could possibly also cache MonoBehaviours per view?!
  1316. for (int componentsIndex = 0; componentsIndex < mbComponents.Length; componentsIndex++)
  1317. {
  1318. MonoBehaviour monob = mbComponents[componentsIndex];
  1319. if (monob == null)
  1320. {
  1321. Debug.LogError("ERROR You have missing MonoBehaviours on your gameobjects!"); continue;
  1322. }
  1323. Type type = monob.GetType();
  1324. // Get [RPC] methods from cache
  1325. List<MethodInfo> cachedRPCMethods = null;
  1326. if (this.monoRPCMethodsCache.ContainsKey(type))
  1327. {
  1328. cachedRPCMethods = this.monoRPCMethodsCache[type];
  1329. }
  1330. if (cachedRPCMethods == null)
  1331. {
  1332. List<MethodInfo> entries = new List<MethodInfo>();
  1333. MethodInfo[] myMethods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  1334. for (int i = 0; i < myMethods.Length; i++)
  1335. {
  1336. if (myMethods[i].IsDefined(typeof(UnityEngine.RPC), false))
  1337. {
  1338. entries.Add(myMethods[i]);
  1339. }
  1340. }
  1341. this.monoRPCMethodsCache[type] = entries;
  1342. cachedRPCMethods = entries;
  1343. }
  1344. if (cachedRPCMethods == null)
  1345. {
  1346. continue;
  1347. }
  1348. // Check cache for valid methodname+arguments
  1349. for (int index = 0; index < cachedRPCMethods.Count; index++)
  1350. {
  1351. MethodInfo mInfo = cachedRPCMethods[index];
  1352. if (mInfo.Name == inMethodName)
  1353. {
  1354. foundMethods++;
  1355. ParameterInfo[] pArray = mInfo.GetParameters();
  1356. if (pArray.Length == argTypes.Length)
  1357. {
  1358. // Normal, PhotonNetworkMessage left out
  1359. if (this.CheckTypeMatch(pArray, argTypes))
  1360. {
  1361. receivers++;
  1362. object result = mInfo.Invoke((object)monob, inMethodParameters);
  1363. if (mInfo.ReturnType == typeof(System.Collections.IEnumerator))
  1364. {
  1365. monob.StartCoroutine((IEnumerator)result);
  1366. }
  1367. }
  1368. }
  1369. else if ((pArray.Length - 1) == argTypes.Length)
  1370. {
  1371. // Check for PhotonNetworkMessage being the last
  1372. if (this.CheckTypeMatch(pArray, argTypes))
  1373. {
  1374. if (pArray[pArray.Length - 1].ParameterType == typeof(PhotonMessageInfo))
  1375. {
  1376. receivers++;
  1377. int sendTime = (int)rpcData[(byte)2];
  1378. object[] deParamsWithInfo = new object[inMethodParameters.Length + 1];
  1379. inMethodParameters.CopyTo(deParamsWithInfo, 0);
  1380. deParamsWithInfo[deParamsWithInfo.Length - 1] = new PhotonMessageInfo(sender, sendTime, photonNetview);
  1381. object result = mInfo.Invoke((object)monob, deParamsWithInfo);
  1382. if (mInfo.ReturnType == typeof(System.Collections.IEnumerator))
  1383. {
  1384. monob.StartCoroutine((IEnumerator)result);
  1385. }
  1386. }
  1387. }
  1388. }
  1389. else if (pArray.Length == 1 && pArray[0].ParameterType.IsArray)
  1390. {
  1391. receivers++;
  1392. object result = mInfo.Invoke((object)monob, new object[] { inMethodParameters });
  1393. if (mInfo.ReturnType == typeof(System.Collections.IEnumerator))
  1394. {
  1395. monob.StartCoroutine((IEnumerator)result);
  1396. }
  1397. }
  1398. }
  1399. }
  1400. }
  1401. // Error handling
  1402. if (receivers != 1)
  1403. {
  1404. string argsString = string.Empty;
  1405. for (int index = 0; index < argTypes.Length; index++)
  1406. {
  1407. Type ty = argTypes[index];
  1408. if (argsString != string.Empty)
  1409. {
  1410. argsString += ", ";
  1411. }
  1412. if (ty == null)
  1413. {
  1414. argsString += "null";
  1415. }
  1416. else
  1417. {
  1418. argsString += ty.Name;
  1419. }
  1420. }
  1421. if (receivers == 0)
  1422. {
  1423. if (foundMethods == 0)
  1424. {
  1425. this.DebugReturn(
  1426. DebugLevel.ERROR,
  1427. "PhotonView with ID " + netViewID + " has no method \"" + inMethodName
  1428. + "\" marked with the [RPC](C#) or @RPC(JS) property! Args: " + argsString);
  1429. }
  1430. else
  1431. {
  1432. this.DebugReturn(
  1433. DebugLevel.ERROR,
  1434. "PhotonView with ID " + netViewID + " has no method \"" + inMethodName + "\" that takes "
  1435. + argTypes.Length + " argument(s): " + argsString);
  1436. }
  1437. }
  1438. else
  1439. {
  1440. this.DebugReturn(
  1441. DebugLevel.ERROR,
  1442. "PhotonView with ID " + netViewID + " has " + receivers + " methods \"" + inMethodName
  1443. + "\" that takes " + argTypes.Length + " argument(s): " + argsString + ". Should be just one?");
  1444. }
  1445. }
  1446. }
  1447. /// <summary>
  1448. /// Check if all types match with parameters. We can have more paramters then types (allow last RPC type to be different).
  1449. /// </summary>
  1450. /// <param name="parameters"></param>
  1451. /// <param name="types"></param>
  1452. /// <returns>If the types-array has matching parameters (of method) in the parameters array (which may be longer).</returns>
  1453. private bool CheckTypeMatch(ParameterInfo[] parameters, Type[] types)
  1454. {
  1455. if (parameters.Length < types.Length)
  1456. {
  1457. return false;
  1458. }
  1459. int i = 0;
  1460. for (int index = 0; index < types.Length; index++)
  1461. {
  1462. Type type = types[index];
  1463. if (type != null && parameters[i].ParameterType != type)
  1464. {
  1465. return false;
  1466. }
  1467. i++;
  1468. }
  1469. return true;
  1470. }
  1471. internal Hashtable SendInstantiate(string prefabName, Vector3 position, Quaternion rotation, int group, int[] viewIDs, object[] data, bool isGlobalObject)
  1472. {
  1473. // first viewID is now also the gameobject's instantiateId
  1474. int instantiateId = viewIDs[0]; // LIMITS PHOTONVIEWS&PLAYERS
  1475. //TODO: reduce hashtable key usage by using a parameter array for the various values
  1476. Hashtable instantiateEvent = new Hashtable(); // This players info is sent via ActorID
  1477. instantiateEvent[(byte)0] = prefabName;
  1478. if (position != Vector3.zero)
  1479. {
  1480. instantiateEvent[(byte)1] = position;
  1481. }
  1482. if (rotation != Quaternion.identity)
  1483. {
  1484. instantiateEvent[(byte)2] = rotation;
  1485. }
  1486. if (group != 0)
  1487. {
  1488. instantiateEvent[(byte)3] = group;
  1489. }
  1490. // send the list of viewIDs only if there are more than one. else the instantiateId is the viewID
  1491. if (viewIDs.Length > 1)
  1492. {
  1493. instantiateEvent[(byte)4] = viewIDs; // LIMITS PHOTONVIEWS&PLAYERS
  1494. }
  1495. if (data != null)
  1496. {
  1497. instantiateEvent[(byte)5] = data;
  1498. }
  1499. if (this.currentLevelPrefix > 0)
  1500. {
  1501. instantiateEvent[(byte)8] = this.currentLevelPrefix; // photonview's / object's level prefix
  1502. }
  1503. instantiateEvent[(byte)6] = this.ServerTimeInMilliSeconds;
  1504. instantiateEvent[(byte)7] = instantiateId;
  1505. EventCaching cacheMode = (isGlobalObject) ? EventCaching.AddToRoomCacheGlobal : EventCaching.AddToRoomCache;
  1506. this.OpRaiseEvent(PhotonNetworkMessages.Instantiation, instantiateEvent, true, 0, cacheMode, ReceiverGroup.Others);
  1507. return instantiateEvent;
  1508. }
  1509. internal GameObject DoInstantiate(Hashtable evData, PhotonPlayer photonPlayer, GameObject resourceGameObject)
  1510. {
  1511. // some values always present:
  1512. string prefabName = (string)evData[(byte)0];
  1513. int serverTime = (int)evData[(byte)6];
  1514. int instantiationId = (int)evData[(byte)7];
  1515. Vector3 position;
  1516. if (evData.ContainsKey((byte)1))
  1517. {
  1518. position = (Vector3)evData[(byte)1];
  1519. }
  1520. else
  1521. {
  1522. position = Vector3.zero;
  1523. }
  1524. Quaternion rotation = Quaternion.identity;
  1525. if (evData.ContainsKey((byte)2))
  1526. {
  1527. rotation = (Quaternion)evData[(byte)2];
  1528. }
  1529. int group = 0;
  1530. if (evData.ContainsKey((byte)3))
  1531. {
  1532. group = (int)evData[(byte)3];
  1533. }
  1534. short objLevelPrefix = 0;
  1535. if (evData.ContainsKey((byte)8))
  1536. {
  1537. objLevelPrefix = (short)evData[(byte)8];
  1538. }
  1539. int[] viewsIDs;
  1540. if (evData.ContainsKey((byte)4))
  1541. {
  1542. viewsIDs = (int[])evData[(byte)4];
  1543. }
  1544. else
  1545. {
  1546. viewsIDs = new int[1] { instantiationId };
  1547. }
  1548. object[] incomingInstantiationData;
  1549. if (evData.ContainsKey((byte)5))
  1550. {
  1551. incomingInstantiationData = (object[])evData[(byte)5];
  1552. }
  1553. else
  1554. {
  1555. incomingInstantiationData = null;
  1556. }
  1557. // SetReceiving filtering
  1558. if (group != 0 && !this.allowedReceivingGroups.Contains(group))
  1559. {
  1560. return null; // Ignore group
  1561. }
  1562. // load prefab, if it wasn't loaded before (calling methods might do this)
  1563. if (resourceGameObject == null)
  1564. {
  1565. if (!NetworkingPeer.UsePrefabCache || !NetworkingPeer.PrefabCache.TryGetValue(prefabName, out resourceGameObject))
  1566. {
  1567. resourceGameObject = (GameObject)Resources.Load(prefabName, typeof(GameObject));
  1568. if (NetworkingPeer.UsePrefabCache)
  1569. {
  1570. NetworkingPeer.PrefabCache.Add(prefabName, resourceGameObject);
  1571. }
  1572. }
  1573. if (resourceGameObject == null)
  1574. {
  1575. Debug.LogError("PhotonNetwork error: Could not Instantiate the prefab [" + prefabName + "]. Please verify you have this gameobject in a Resources folder.");
  1576. return null;
  1577. }
  1578. }
  1579. // now modify the loaded "blueprint" object before it becomes a part of the scene (by instantiating it)
  1580. PhotonView[] resourcePVs = resourceGameObject.GetPhotonViewsInChildren();
  1581. if (resourcePVs.Length != viewsIDs.Length)
  1582. {
  1583. throw new Exception("Error in Instantiation! The resource's PhotonView count is not the same as in incoming data.");
  1584. }
  1585. for (int i = 0; i < viewsIDs.Length; i++)
  1586. {
  1587. // NOTE instantiating the loaded resource will keep the viewID but would not copy instantiation data, so it's set below
  1588. // so we only set the viewID and instantiationId now. the instantiationData can be fetched
  1589. resourcePVs[i].viewID = viewsIDs[i];
  1590. resourcePVs[i].prefix = objLevelPrefix;
  1591. resourcePVs[i].instantiationId = instantiationId;
  1592. }
  1593. this.StoreInstantiationData(instantiationId, incomingInstantiationData);
  1594. // load the resource and set it's values before instantiating it:
  1595. // Debug.Log("PreInstantiate");
  1596. GameObject go = (GameObject)GameObject.Instantiate(resourceGameObject, position, rotation);
  1597. // Debug.LogWarning("PostInstantiate");
  1598. for (int i = 0; i < viewsIDs.Length; i++)
  1599. {
  1600. // NOTE instantiating the loaded resource will keep the viewID but would not copy instantiation data, so it's set below
  1601. // so we only set the viewID and instantiationId now. the instantiationData can be fetched
  1602. resourcePVs[i].viewID = 0;
  1603. resourcePVs[i].prefix = -1;
  1604. resourcePVs[i].prefixBackup = -1;
  1605. resourcePVs[i].instantiationId = -1;
  1606. }
  1607. this.RemoveInstantiationData(instantiationId);
  1608. //TODO: remove this debug check
  1609. if (this.instantiatedObjects.ContainsKey(instantiationId))
  1610. {
  1611. GameObject knownGo = this.instantiatedObjects[instantiationId];
  1612. string pvaInfo = "";
  1613. PhotonView[] pva;
  1614. if (knownGo != null)
  1615. {
  1616. pva = knownGo.GetPhotonViewsInChildren();
  1617. foreach (PhotonView view in pva)
  1618. {
  1619. if (view == null) continue;
  1620. pvaInfo += view.ToString() + ", ";
  1621. }
  1622. }
  1623. Debug.LogError(string.Format("Adding GO \"{0}\" (instantiationID: {1}) to instantiatedObjects failed. instantiatedObjects.Count: {2}. Object taking the same place: {3}. Views on it: {4}. PhotonNetwork.lastUsedViewSubId: {5} PhotonNetwork.lastUsedViewSubIdStatic: {6} this.photonViewList.Count {7}.)", go, instantiationId, this.instantiatedObjects.Count, knownGo, pvaInfo, PhotonNetwork.lastUsedViewSubId, PhotonNetwork.lastUsedViewSubIdStatic, this.photonViewList.Count));
  1624. }
  1625. this.instantiatedObjects.Add(instantiationId, go); //TODO check if instantiatedObjects is (still) needed
  1626. // Send mono event
  1627. // TOD move this callback and script-caching into a method! there should be one already...
  1628. object[] messageInfoParam = new object[1];
  1629. messageInfoParam[0] = new PhotonMessageInfo(photonPlayer, serverTime, null);
  1630. MonoBehaviour[] monos = go.GetComponentsInChildren<MonoBehaviour>();
  1631. for (int index = 0; index < monos.Length; index++)
  1632. {
  1633. MonoBehaviour mono = monos[index];
  1634. MethodInfo methodI = this.GetCachedMethod(mono, PhotonNetworkingMessage.OnPhotonInstantiate);
  1635. if (methodI != null)
  1636. {
  1637. object result = methodI.Invoke((object)mono, messageInfoParam);
  1638. if (methodI.ReturnType == typeof(System.Collections.IEnumerator))
  1639. {
  1640. mono.StartCoroutine((IEnumerator)result);
  1641. }
  1642. }
  1643. }
  1644. return go;
  1645. }
  1646. private Dictionary<int, object[]> tempInstantiationData = new Dictionary<int, object[]>();
  1647. private void StoreInstantiationData(int instantiationId, object[] instantiationData)
  1648. {
  1649. // Debug.Log("StoreInstantiationData() instantiationId: " + instantiationId + " tempInstantiationData.Count: " + tempInstantiationData.Count);
  1650. tempInstantiationData[instantiationId] = instantiationData;
  1651. }
  1652. public object[] FetchInstantiationData(int instantiationId)
  1653. {
  1654. object[] data = null;
  1655. if (instantiationId == 0)
  1656. {
  1657. return null;
  1658. }
  1659. tempInstantiationData.TryGetValue(instantiationId, out data);
  1660. // Debug.Log("FetchInstantiationData() instantiationId: " + instantiationId + " tempInstantiationData.Count: " + tempInstantiationData.Count);
  1661. return data;
  1662. }
  1663. private void RemoveInstantiationData(int instantiationId)
  1664. {
  1665. tempInstantiationData.Remove(instantiationId);
  1666. }
  1667. // Removes PhotonNetwork.Instantiate-ed objects
  1668. // Does not remove any manually assigned PhotonViews.
  1669. public void RemoveAllInstantiatedObjects()
  1670. {
  1671. GameObject[] instantiatedGoArray = new GameObject[this.instantiatedObjects.Count];
  1672. this.instantiatedObjects.Values.CopyTo(instantiatedGoArray, 0);
  1673. for (int index = 0; index < instantiatedGoArray.Length; index++)
  1674. {
  1675. GameObject go = instantiatedGoArray[index];
  1676. if (go == null)
  1677. {
  1678. continue;
  1679. }
  1680. this.RemoveInstantiatedGO(go, false);
  1681. }
  1682. if (this.instantiatedObjects.Count > 0)
  1683. {
  1684. Debug.LogError("RemoveAllInstantiatedObjects() this.instantiatedObjects.Count should be 0 by now.");
  1685. }
  1686. this.instantiatedObjects = new Dictionary<int, GameObject>();
  1687. }
  1688. public void RemoveAllInstantiatedObjectsByPlayer(PhotonPlayer player, bool localOnly)
  1689. {
  1690. GameObject[] instantiatedGoArray = new GameObject[this.instantiatedObjects.Count];
  1691. this.instantiatedObjects.Values.CopyTo(instantiatedGoArray, 0);
  1692. for (int index = 0; index < instantiatedGoArray.Length; index++)
  1693. {
  1694. GameObject go = instantiatedGoArray[index];
  1695. if (go == null)
  1696. {
  1697. continue;
  1698. }
  1699. // all PUN created GameObjects must have a PhotonView, so we could get the owner of it
  1700. PhotonView[] views = go.GetComponentsInChildren<PhotonView>();
  1701. for (int j = views.Length - 1; j >= 0; j--)
  1702. {
  1703. PhotonView view = views[j];
  1704. if (view.OwnerActorNr == player.ID)
  1705. {
  1706. this.RemoveInstantiatedGO(go, localOnly);
  1707. break;
  1708. }
  1709. }
  1710. }
  1711. }
  1712. public void RemoveInstantiatedGO(GameObject go, bool localOnly)
  1713. {
  1714. if (go == null)
  1715. {
  1716. if (DebugOut == DebugLevel.ERROR)
  1717. {
  1718. this.DebugReturn(DebugLevel.ERROR, "Can't remove instantiated GO if it's null.");
  1719. }
  1720. return;
  1721. }
  1722. int instantiateId = this.GetInstantiatedObjectsId(go);
  1723. if (instantiateId == -1)
  1724. {
  1725. if (DebugOut == DebugLevel.ERROR)
  1726. {
  1727. this.DebugReturn(DebugLevel.ERROR, "Can't find GO in instantiation list. Object: " + go);
  1728. }
  1729. return;
  1730. }
  1731. this.instantiatedObjects.Remove(instantiateId);
  1732. PhotonView[] views = go.GetComponentsInChildren<PhotonView>();
  1733. bool removedFromServer = false;
  1734. for (int j = views.Length - 1; j >= 0; j--)
  1735. {
  1736. PhotonView view = views[j];
  1737. if (view == null)
  1738. {
  1739. continue;
  1740. }
  1741. if (!removedFromServer)
  1742. {
  1743. // first view's owner should be the same as any further view's owner. use it to clean cache
  1744. int removeForActorID = view.OwnerActorNr;
  1745. this.RemoveFromServerInstantiationCache(instantiateId, removeForActorID);
  1746. removedFromServer = true;
  1747. }
  1748. this.DestroyPhotonView(view, localOnly);// UnAllocateViewID() is done by DestroyPhotonView() if needed
  1749. }
  1750. if (this.DebugOut >= DebugLevel.ALL)
  1751. {
  1752. this.DebugReturn(DebugLevel.ALL, "Network destroy Instantiated GO: " + go.name);
  1753. }
  1754. this.DestroyGO(go);
  1755. }
  1756. /// <summary>
  1757. /// This returns -1 if the GO could not be found in list of instantiatedObjects.
  1758. /// </summary>
  1759. public int GetInstantiatedObjectsId(GameObject go)
  1760. {
  1761. int id = -1;
  1762. if (go == null)
  1763. {
  1764. this.DebugReturn(DebugLevel.ERROR, "GetInstantiatedObjectsId() for GO == null.");
  1765. return id;
  1766. }
  1767. PhotonView[] pvs = go.GetPhotonViewsInChildren();
  1768. if (pvs != null && pvs.Length > 0 && pvs[0] != null)
  1769. {
  1770. return pvs[0].instantiationId;
  1771. }
  1772. if (DebugOut == DebugLevel.ALL)
  1773. {
  1774. this.DebugReturn(DebugLevel.ALL, "GetInstantiatedObjectsId failed for GO: " + go);
  1775. }
  1776. return id;
  1777. }
  1778. /// <summary>
  1779. /// Removes an instantiation event from the server's cache. Needs id and actorNr of player who instantiated.
  1780. /// </summary>
  1781. private void RemoveFromServerInstantiationCache(int instantiateId, int actorNr)
  1782. {
  1783. Hashtable removeFilter = new Hashtable();
  1784. removeFilter[(byte)7] = instantiateId;
  1785. this.OpRaiseEvent(PhotonNetworkMessages.Instantiation, removeFilter, true, 0, new int[] { actorNr }, EventCaching.RemoveFromRoomCache);
  1786. }
  1787. private void RemoveFromServerInstantiationsOfPlayer(int actorNr)
  1788. {
  1789. // removes all "Instantiation" events of player actorNr. this is not an event for anyone else
  1790. this.OpRaiseEvent(PhotonNetworkMessages.Instantiation, null, true, 0, new int[] { actorNr }, EventCaching.RemoveFromRoomCache);
  1791. }
  1792. // Destroys all gameobjects from a player with a PhotonView that they own
  1793. // FIRST: Instantiated objects are deleted.
  1794. // SECOND: Destroy entire gameobject+children of PhotonViews that they are owner of.
  1795. // This can mess up if theres no PhotonView on root of the objects!
  1796. public void DestroyPlayerObjects(PhotonPlayer player, bool localOnly)
  1797. {
  1798. this.RemoveAllInstantiatedObjectsByPlayer(player, localOnly); // Instantiated objects
  1799. // Manually spawned ones:
  1800. PhotonView[] views = (PhotonView[])GameObject.FindObjectsOfType(typeof(PhotonView));
  1801. for (int i = views.Length - 1; i >= 0; i--)
  1802. {
  1803. PhotonView view = views[i];
  1804. if (view.owner == player)
  1805. {
  1806. this.DestroyPhotonView(view, localOnly);
  1807. }
  1808. }
  1809. }
  1810. public void DestroyPhotonView(PhotonView view, bool localOnly)
  1811. {
  1812. if (!localOnly && (view.isMine || mMasterClient == mLocalActor))
  1813. {
  1814. // sends the "destroy view" message so others will destroy the view, too. this is not cached
  1815. Hashtable evData = new Hashtable();
  1816. evData[(byte)0] = view.viewID;
  1817. this.OpRaiseEvent(PhotonNetworkMessages.Destroy, evData, true, 0, EventCaching.DoNotCache, ReceiverGroup.Others);
  1818. }
  1819. if (view.isMine || mMasterClient == mLocalActor)
  1820. {
  1821. // Only remove cached RPCs if they are ours
  1822. this.RemoveRPCs(view);
  1823. }
  1824. int id = view.instantiationId;
  1825. if (id != -1)
  1826. {
  1827. // Debug.Log("Found view in instantiatedObjects.");
  1828. this.instantiatedObjects.Remove(id);
  1829. }
  1830. if (this.DebugOut >= DebugLevel.ALL)
  1831. {
  1832. this.DebugReturn(DebugLevel.ALL, "Network destroy PhotonView GO: " + view.gameObject.name);
  1833. }
  1834. this.DestroyGO(view.gameObject); // OnDestroy calls RemovePhotonView(view);
  1835. }
  1836. public PhotonView GetPhotonView(int viewID)
  1837. {
  1838. PhotonView result = null;
  1839. this.photonViewList.TryGetValue(viewID, out result);
  1840. if (result == null)
  1841. {
  1842. PhotonView[] views = GameObject.FindObjectsOfType(typeof(PhotonView)) as PhotonView[];
  1843. foreach (PhotonView view in views)
  1844. {
  1845. if (view.viewID == viewID)
  1846. {
  1847. Debug.LogWarning("Had to lookup view that wasn't in dict: " + view);
  1848. return view;
  1849. }
  1850. }
  1851. }
  1852. return result;
  1853. }
  1854. public void RegisterPhotonView(PhotonView netView)
  1855. {
  1856. if (!Application.isPlaying)
  1857. {
  1858. this.photonViewList = new Dictionary<int, PhotonView>();
  1859. return;
  1860. }
  1861. if (netView.subId == 0)
  1862. {
  1863. // don't register views with subId 0 (not initialized). they register when a ID is assigned later on
  1864. // Debug.Log("PhotonView register is ignored, because subId is 0. No id assigned yet to: " + netView);
  1865. return;
  1866. }
  1867. if (!this.photonViewList.ContainsKey(netView.viewID))
  1868. {
  1869. // Debug.Log("adding view to known list: " + netView);
  1870. this.photonViewList.Add(netView.viewID, netView);
  1871. //Debug.LogError("view being added. " + netView); // Exit Games internal log
  1872. if (this.DebugOut >= DebugLevel.ALL)
  1873. {
  1874. this.DebugReturn(DebugLevel.ALL, "Registered PhotonView: " + netView.viewID);
  1875. }
  1876. }
  1877. else
  1878. {
  1879. // if some other view is in the list already, we got a problem. it might be undestructible. print out error
  1880. if (netView != photonViewList[netView.viewID])
  1881. {
  1882. Debug.LogError(string.Format("PhotonView ID duplicate found: {0}. On objects: {1} and {2}. Maybe one wasn't destroyed on scene load?! Check for 'DontDestroyOnLoad'.", netView.viewID, netView, photonViewList[netView.viewID]));
  1883. }
  1884. }
  1885. }
  1886. /// <summary>
  1887. /// Will remove the view from list of views (by its ID).
  1888. /// </summary>
  1889. public void RemovePhotonView(PhotonView netView)
  1890. {
  1891. if (!Application.isPlaying)
  1892. {
  1893. this.photonViewList = new Dictionary<int, PhotonView>();
  1894. return;
  1895. }
  1896. //PhotonView removedView = null;
  1897. //this.photonViewList.TryGetValue(netView.viewID, out removedView);
  1898. //if (removedView != netView)
  1899. //{
  1900. // Debug.LogError("Detected two differing PhotonViews with same viewID: " + netView.viewID);
  1901. //}
  1902. this.photonViewList.Remove(netView.viewID);
  1903. //if (this.DebugOut >= DebugLevel.ALL)
  1904. //{
  1905. // this.DebugReturn(DebugLevel.ALL, "Removed PhotonView: " + netView.viewID);
  1906. //}
  1907. }
  1908. /// <summary>
  1909. /// Removes the RPCs of someone else (to be used as master).
  1910. /// This won't clean any local caches. It just tells the server to forget a player's RPCs and instantiates.
  1911. /// </summary>
  1912. /// <param name="actorNumber"></param>
  1913. public void RemoveRPCs(int actorNumber)
  1914. {
  1915. this.OpRaiseEvent(PhotonNetworkMessages.RPC, null, true, 0, new int[] { actorNumber }, EventCaching.RemoveFromRoomCache);
  1916. }
  1917. /// <summary>
  1918. /// Instead removint RPCs or Instantiates, this removed everything cached by the actor.
  1919. /// </summary>
  1920. /// <param name="actorNumber"></param>
  1921. public void RemoveCompleteCacheOfPlayer(int actorNumber)
  1922. {
  1923. this.OpRaiseEvent(0, null, true, 0, new int[] { actorNumber }, EventCaching.RemoveFromRoomCache);
  1924. }
  1925. /// This clears the cache of any player/actor who's no longer in the room (making it a simple clean-up option for a new master)
  1926. private void RemoveCacheOfLeftPlayers()
  1927. {
  1928. Dictionary<byte, object> opParameters = new Dictionary<byte, object>();
  1929. opParameters[ParameterCode.Code] = (byte)0; // any event
  1930. opParameters[ParameterCode.Cache] = (byte)EventCaching.RemoveFromRoomCacheForActorsLeft; // option to clear the room cache of all events of players who left
  1931. this.OpCustom((byte)OperationCode.RaiseEvent, opParameters, true, 0);
  1932. }
  1933. // Remove RPCs of view (if they are local player's RPCs)
  1934. public void RemoveRPCs(PhotonView view)
  1935. {
  1936. if (!mLocalActor.isMasterClient && view.owner != this.mLocalActor)
  1937. {
  1938. Debug.LogError("Error, cannot remove cached RPCs on a PhotonView thats not ours! " + view.owner + " scene: " + view.isSceneView);
  1939. return;
  1940. }
  1941. Hashtable rpcFilterByViewId = new Hashtable();
  1942. rpcFilterByViewId[(byte)0] = view.viewID;
  1943. this.OpRaiseEvent(PhotonNetworkMessages.RPC, rpcFilterByViewId, true, 0, EventCaching.RemoveFromRoomCache, ReceiverGroup.Others);
  1944. }
  1945. public void RemoveRPCsInGroup(int group)
  1946. {
  1947. foreach (KeyValuePair<int, PhotonView> kvp in this.photonViewList)
  1948. {
  1949. PhotonView view = kvp.Value;
  1950. if (view.group == group)
  1951. {
  1952. this.RemoveRPCs(view);
  1953. }
  1954. }
  1955. }
  1956. public void SetLevelPrefix(short prefix)
  1957. {
  1958. this.currentLevelPrefix = prefix;
  1959. // TODO: should we really change the prefix for existing PVs?! better keep it!
  1960. //foreach (PhotonView view in this.photonViewList.Values)
  1961. //{
  1962. // view.prefix = prefix;
  1963. //}
  1964. }
  1965. public void RPC(PhotonView view, string methodName, PhotonPlayer player, params object[] parameters)
  1966. {
  1967. if (this.blockSendingGroups.Contains(view.group))
  1968. {
  1969. return; // Block sending on this group
  1970. }
  1971. if (view.viewID < 1) //TODO: check why 0 should be illegal
  1972. {
  1973. Debug.LogError("Illegal view ID:" + view.viewID + " method: " + methodName + " GO:" + view.gameObject.name);
  1974. }
  1975. if (this.DebugOut >= DebugLevel.INFO)
  1976. {
  1977. this.DebugReturn(DebugLevel.INFO, "Sending RPC \"" + methodName + "\" to player[" + player + "]");
  1978. }
  1979. //ts: changed RPCs to a one-level hashtable as described in internal.txt
  1980. Hashtable rpcEvent = new Hashtable();
  1981. rpcEvent[(byte)0] = (int)view.viewID; // LIMITS PHOTONVIEWS&PLAYERS
  1982. if (view.prefix > 0)
  1983. {
  1984. rpcEvent[(byte)1] = (short)view.prefix;
  1985. }
  1986. rpcEvent[(byte)2] = this.ServerTimeInMilliSeconds;
  1987. // send name or shortcut (if available)
  1988. int shortcut = 0;
  1989. if (rpcShortcuts.TryGetValue(methodName, out shortcut))
  1990. {
  1991. rpcEvent[(byte)5] = (byte)shortcut; // LIMITS RPC COUNT
  1992. }
  1993. else
  1994. {
  1995. rpcEvent[(byte)3] = methodName;
  1996. }
  1997. if (parameters != null || parameters.Length == 0)
  1998. {
  1999. rpcEvent[(byte) 4] = (object[]) parameters;
  2000. }
  2001. if (this.mLocalActor == player)
  2002. {
  2003. this.ExecuteRPC(rpcEvent, player);
  2004. }
  2005. else
  2006. {
  2007. int[] targetActors = new int[] { player.ID };
  2008. this.OpRaiseEvent(PhotonNetworkMessages.RPC, rpcEvent, true, 0, targetActors);
  2009. }
  2010. }
  2011. /// RPC Hashtable Structure
  2012. /// (byte)0 -> (int) ViewId (combined from actorNr and actor-unique-id)
  2013. /// (byte)1 -> (short) prefix (level)
  2014. /// (byte)2 -> (int) server timestamp
  2015. /// (byte)3 -> (string) methodname
  2016. /// (byte)4 -> (object[]) parameters
  2017. /// (byte)5 -> (string) method shortcut (alternative to name)
  2018. ///
  2019. /// This is sent as event (code: 200) which will contain a sender (origin of this RPC).
  2020. public void RPC(PhotonView view, string methodName, PhotonTargets target, params object[] parameters)
  2021. {
  2022. if (this.blockSendingGroups.Contains(view.group))
  2023. {
  2024. return; // Block sending on this group
  2025. }
  2026. if (view.viewID < 1)
  2027. {
  2028. Debug.LogError("Illegal view ID:" + view.viewID + " method: " + methodName + " GO:" + view.gameObject.name);
  2029. }
  2030. if (this.DebugOut >= DebugLevel.INFO)
  2031. {
  2032. this.DebugReturn(DebugLevel.INFO, "Sending RPC \"" + methodName + "\" to " + target);
  2033. }
  2034. //ts: changed RPCs to a one-level hashtable as described in internal.txt
  2035. Hashtable rpcEvent = new Hashtable();
  2036. rpcEvent[(byte)0] = (int)view.viewID; // LIMITS NETWORKVIEWS&PLAYERS
  2037. if (view.prefix > 0)
  2038. {
  2039. rpcEvent[(byte)1] = (short)view.prefix;
  2040. }
  2041. rpcEvent[(byte)2] = this.ServerTimeInMilliSeconds;
  2042. // send name or shortcut (if available)
  2043. int shortcut = 0;
  2044. if (rpcShortcuts.TryGetValue(methodName, out shortcut))
  2045. {
  2046. rpcEvent[(byte)5] = (byte)shortcut; // LIMITS RPC COUNT
  2047. }
  2048. else
  2049. {
  2050. rpcEvent[(byte)3] = methodName;
  2051. }
  2052. if (parameters != null || parameters.Length == 0)
  2053. {
  2054. rpcEvent[(byte)4] = (object[])parameters;
  2055. }
  2056. // Check scoping
  2057. if (target == PhotonTargets.All)
  2058. {
  2059. this.OpRaiseEvent(PhotonNetworkMessages.RPC, (byte)view.group, rpcEvent, true, 0);
  2060. // Execute local
  2061. this.ExecuteRPC(rpcEvent, this.mLocalActor);
  2062. }
  2063. else if (target == PhotonTargets.Others)
  2064. {
  2065. this.OpRaiseEvent(PhotonNetworkMessages.RPC, (byte)view.group, rpcEvent, true, 0);
  2066. }
  2067. else if (target == PhotonTargets.AllBuffered)
  2068. {
  2069. this.OpRaiseEvent(PhotonNetworkMessages.RPC, rpcEvent, true, 0, EventCaching.AddToRoomCache, ReceiverGroup.Others);
  2070. // Execute local
  2071. this.ExecuteRPC(rpcEvent, this.mLocalActor);
  2072. }
  2073. else if (target == PhotonTargets.OthersBuffered)
  2074. {
  2075. this.OpRaiseEvent(PhotonNetworkMessages.RPC, rpcEvent, true, 0, EventCaching.AddToRoomCache, ReceiverGroup.Others);
  2076. }
  2077. else if (target == PhotonTargets.MasterClient)
  2078. {
  2079. if (this.mMasterClient == this.mLocalActor)
  2080. {
  2081. this.ExecuteRPC(rpcEvent, this.mLocalActor);
  2082. }
  2083. else
  2084. {
  2085. this.OpRaiseEvent(PhotonNetworkMessages.RPC, rpcEvent, true, 0, EventCaching.DoNotCache, ReceiverGroup.MasterClient);//TS: changed from caching to non-cached. this goes to master only
  2086. }
  2087. }
  2088. else
  2089. {
  2090. Debug.LogError("Unsupported target enum: " + target);
  2091. }
  2092. }
  2093. // SetReceiving
  2094. public void SetReceivingEnabled(int group, bool enabled)
  2095. {
  2096. if (group <= 0)
  2097. {
  2098. Debug.LogError("Error: PhotonNetwork.SetReceivingEnabled was called with an illegal group number: " + group + ". The group number should be at least 1.");
  2099. return;
  2100. }
  2101. if (enabled)
  2102. {
  2103. if (!this.allowedReceivingGroups.Contains(group))
  2104. {
  2105. this.allowedReceivingGroups.Add(group);
  2106. byte[] groups = new byte[1] { (byte)group };
  2107. this.OpChangeGroups(null, groups);
  2108. }
  2109. }
  2110. else
  2111. {
  2112. if (this.allowedReceivingGroups.Contains(group))
  2113. {
  2114. this.allowedReceivingGroups.Remove(group);
  2115. byte[] groups = new byte[1] { (byte)group };
  2116. this.OpChangeGroups(groups, null);
  2117. }
  2118. }
  2119. }
  2120. // SetSending
  2121. public void SetSendingEnabled(int group, bool enabled)
  2122. {
  2123. if (!enabled)
  2124. {
  2125. this.blockSendingGroups.Add(group); // can be added to HashSet no matter if already in it
  2126. }
  2127. else
  2128. {
  2129. this.blockSendingGroups.Remove(group);
  2130. }
  2131. }
  2132. public void NewSceneLoaded()
  2133. {
  2134. if (this.loadingLevelAndPausedNetwork && PhotonNetwork.isMessageQueueRunning == false)
  2135. {
  2136. this.loadingLevelAndPausedNetwork = false;
  2137. PhotonNetwork.isMessageQueueRunning = true;
  2138. }
  2139. // Debug.Log("OnLevelWasLoaded photonViewList.Count: " + photonViewList.Count); // Exit Games internal log
  2140. List<int> removeKeys = new List<int>();
  2141. foreach (KeyValuePair<int, PhotonView> kvp in this.photonViewList)
  2142. {
  2143. PhotonView view = kvp.Value;
  2144. if (view == null)
  2145. {
  2146. removeKeys.Add(kvp.Key);
  2147. }
  2148. }
  2149. for (int index = 0; index < removeKeys.Count; index++)
  2150. {
  2151. int key = removeKeys[index];
  2152. this.photonViewList.Remove(key);
  2153. }
  2154. if (removeKeys.Count > 0)
  2155. {
  2156. if (this.DebugOut >= DebugLevel.INFO)
  2157. {
  2158. this.DebugReturn(DebugLevel.INFO, "Removed " + removeKeys.Count + " scene view IDs from last scene.");
  2159. }
  2160. }
  2161. }
  2162. // this is called by Update() and in Unity that means it's single threaded.
  2163. public void RunViewUpdate()
  2164. {
  2165. if (!PhotonNetwork.connected || PhotonNetwork.offlineMode)
  2166. {
  2167. return;
  2168. }
  2169. if (this.mActors == null || this.mActors.Count <= 1)
  2170. {
  2171. return; // No need to send OnSerialize messages (these are never buffered anyway)
  2172. }
  2173. Dictionary<int, Hashtable> dataPerGroupReliable = new Dictionary<int, Hashtable>();
  2174. Dictionary<int, Hashtable> dataPerGroupUnreliable = new Dictionary<int, Hashtable>();
  2175. /* Format of the data hashtable:
  2176. * Hasthable dataPergroup*
  2177. * [(byte)0] = this.ServerTimeInMilliSeconds;
  2178. * OPTIONAL: [(byte)1] = currentLevelPrefix;
  2179. * + data
  2180. */
  2181. foreach (KeyValuePair<int, PhotonView> kvp in this.photonViewList)
  2182. {
  2183. PhotonView view = kvp.Value;
  2184. if (view.observed != null && view.synchronization != ViewSynchronization.Off)
  2185. {
  2186. // Fetch all sending photonViews
  2187. if (view.owner == this.mLocalActor || (view.isSceneView && this.mMasterClient == this.mLocalActor))
  2188. {
  2189. #if UNITY_2_6_1 || UNITY_2_6 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5
  2190. if (!view.gameObject.active)
  2191. {
  2192. continue; // Only on actives
  2193. }
  2194. #else
  2195. if (!view.gameObject.activeInHierarchy)
  2196. {
  2197. continue; // Only on actives
  2198. }
  2199. #endif
  2200. if (this.blockSendingGroups.Contains(view.group))
  2201. {
  2202. continue; // Block sending on this group
  2203. }
  2204. // Run it trough its OnSerialize
  2205. Hashtable evData = this.OnSerializeWrite(view);
  2206. if (evData == null)
  2207. {
  2208. continue;
  2209. }
  2210. if (view.synchronization == ViewSynchronization.ReliableDeltaCompressed)
  2211. {
  2212. if (!evData.ContainsKey((byte)1) && !evData.ContainsKey((byte)2))
  2213. {
  2214. // Everything has been removed by compression, nothing to send
  2215. }
  2216. else
  2217. {
  2218. if (!dataPerGroupReliable.ContainsKey(view.group))
  2219. {
  2220. dataPerGroupReliable[view.group] = new Hashtable();
  2221. dataPerGroupReliable[view.group][(byte)0] = this.ServerTimeInMilliSeconds;
  2222. if (currentLevelPrefix >= 0)
  2223. {
  2224. dataPerGroupReliable[view.group][(byte)1] = this.currentLevelPrefix;
  2225. }
  2226. }
  2227. Hashtable groupHashtable = dataPerGroupReliable[view.group];
  2228. groupHashtable.Add((short)groupHashtable.Count, evData);
  2229. }
  2230. }
  2231. else
  2232. {
  2233. if (!dataPerGroupUnreliable.ContainsKey(view.group))
  2234. {
  2235. dataPerGroupUnreliable[view.group] = new Hashtable();
  2236. dataPerGroupUnreliable[view.group][(byte)0] = this.ServerTimeInMilliSeconds;
  2237. if (currentLevelPrefix >= 0)
  2238. {
  2239. dataPerGroupUnreliable[view.group][(byte)1] = this.currentLevelPrefix;
  2240. }
  2241. }
  2242. Hashtable groupHashtable = dataPerGroupUnreliable[view.group];
  2243. groupHashtable.Add((short)groupHashtable.Count, evData);
  2244. }
  2245. }
  2246. else
  2247. {
  2248. // Debug.Log(" NO OBS on " + view.name + " " + view.owner);
  2249. }
  2250. }
  2251. else
  2252. {
  2253. }
  2254. }
  2255. //Send the messages: every group is send in it's own message and unreliable and reliable are split as well
  2256. foreach (KeyValuePair<int, Hashtable> kvp in dataPerGroupReliable)
  2257. {
  2258. this.OpRaiseEvent(PhotonNetworkMessages.SendSerializeReliable, (byte)kvp.Key, kvp.Value, true, 0);
  2259. }
  2260. foreach (KeyValuePair<int, Hashtable> kvp in dataPerGroupUnreliable)
  2261. {
  2262. this.OpRaiseEvent(PhotonNetworkMessages.SendSerialize, (byte)kvp.Key, kvp.Value, false, 0);
  2263. }
  2264. }
  2265. private void ExecuteOnSerialize(MonoBehaviour monob, PhotonStream pStream, PhotonMessageInfo info)
  2266. {
  2267. object[] paramsX = new object[2];
  2268. paramsX[0] = pStream;
  2269. paramsX[1] = info;
  2270. MethodInfo methodI = this.GetCachedMethod(monob, PhotonNetworkingMessage.OnPhotonSerializeView);
  2271. if (methodI != null)
  2272. {
  2273. object result = methodI.Invoke((object)monob, paramsX);
  2274. if (methodI.ReturnType == typeof(System.Collections.IEnumerator))
  2275. {
  2276. monob.StartCoroutine((IEnumerator)result);
  2277. }
  2278. }
  2279. else
  2280. {
  2281. Debug.LogError("Tried to run " + PhotonNetworkingMessage.OnPhotonSerializeView + ", but this method was missing on: " + monob);
  2282. }
  2283. }
  2284. // calls OnPhotonSerializeView (through ExecuteOnSerialize)
  2285. // the content created here is consumed by receivers in: ReadOnSerialize
  2286. private Hashtable OnSerializeWrite(PhotonView view)
  2287. {
  2288. // each view creates a list of values that should be sent
  2289. List<object> data = new List<object>();
  2290. // 1=Specific data
  2291. if (view.observed is MonoBehaviour)
  2292. {
  2293. MonoBehaviour monob = (MonoBehaviour)view.observed;
  2294. PhotonStream pStream = new PhotonStream(true, null);
  2295. PhotonMessageInfo info = new PhotonMessageInfo(this.mLocalActor, this.ServerTimeInMilliSeconds, view);
  2296. this.ExecuteOnSerialize(monob, pStream, info);
  2297. if (pStream.Count == 0)
  2298. {
  2299. // if an observed script didn't write any data, we don't send anything
  2300. return null;
  2301. }
  2302. // we want to use the content of the stream (filled in by user scripts)
  2303. data = pStream.data;
  2304. }
  2305. else if (view.observed is Transform)
  2306. {
  2307. Transform trans = (Transform)view.observed;
  2308. if (view.onSerializeTransformOption == OnSerializeTransform.OnlyPosition
  2309. || view.onSerializeTransformOption == OnSerializeTransform.PositionAndRotation
  2310. || view.onSerializeTransformOption == OnSerializeTransform.All)
  2311. data.Add(trans.localPosition);
  2312. else
  2313. data.Add(null);
  2314. if (view.onSerializeTransformOption == OnSerializeTransform.OnlyRotation
  2315. || view.onSerializeTransformOption == OnSerializeTransform.PositionAndRotation
  2316. || view.onSerializeTransformOption == OnSerializeTransform.All)
  2317. data.Add(trans.localRotation);
  2318. else
  2319. data.Add(null);
  2320. if (view.onSerializeTransformOption == OnSerializeTransform.OnlyScale
  2321. || view.onSerializeTransformOption == OnSerializeTransform.All)
  2322. data.Add(trans.localScale);
  2323. }
  2324. else if (view.observed is Rigidbody)
  2325. {
  2326. Rigidbody rigidB = (Rigidbody)view.observed;
  2327. if (view.onSerializeRigidBodyOption != OnSerializeRigidBody.OnlyAngularVelocity)
  2328. data.Add(rigidB.velocity);
  2329. else
  2330. data.Add(null);
  2331. if (view.onSerializeRigidBodyOption != OnSerializeRigidBody.OnlyVelocity)
  2332. data.Add(rigidB.angularVelocity);
  2333. }
  2334. else
  2335. {
  2336. Debug.LogError("Observed type is not serializable: " + view.observed.GetType());
  2337. return null;
  2338. }
  2339. object[] dataArray = data.ToArray();
  2340. // EVDATA:
  2341. // 0=View ID (an int, never compressed cause it's not in the data)
  2342. // 1=data of observed type (different per type of observed object)
  2343. // 2=compressed data (in this case, key 1 is empty)
  2344. // 3=list of values that are actually null (if something was changed but actually IS null)
  2345. Hashtable evData = new Hashtable();
  2346. evData[(byte)0] = (int)view.viewID;
  2347. evData[(byte)1] = dataArray; // this is the actual data (script or observed object)
  2348. if (view.synchronization == ViewSynchronization.ReliableDeltaCompressed)
  2349. {
  2350. // compress content of data set (by comparing to view.lastOnSerializeDataSent)
  2351. // the "original" dataArray is NOT modified by DeltaCompressionWrite
  2352. // if something was compressed, the evData key 2 and 3 are used (see above)
  2353. bool somethingLeftToSend = this.DeltaCompressionWrite(view, evData);
  2354. // buffer the full data set (for next compression)
  2355. view.lastOnSerializeDataSent = dataArray;
  2356. if (!somethingLeftToSend)
  2357. {
  2358. return null;
  2359. }
  2360. }
  2361. return evData;
  2362. }
  2363. /// <summary>
  2364. /// Reads updates created by OnSerializeWrite
  2365. /// </summary>
  2366. private void OnSerializeRead(Hashtable data, PhotonPlayer sender, int networkTime, short correctPrefix)
  2367. {
  2368. // read view ID from key (byte)0: a int-array (PUN 1.17++)
  2369. int viewID = (int)data[(byte)0];
  2370. PhotonView view = this.GetPhotonView(viewID);
  2371. if (view == null)
  2372. {
  2373. Debug.LogWarning("Received OnSerialization for view ID " + viewID + ". We have no such PhotonView! Ignored this if you're leaving a room. State: " + this.State);
  2374. return;
  2375. }
  2376. if (view.prefix > 0 && correctPrefix != view.prefix)
  2377. {
  2378. Debug.LogError("Received OnSerialization for view ID " + viewID + " with prefix " + correctPrefix + ". Our prefix is " + view.prefix);
  2379. return;
  2380. }
  2381. // SetReceiving filtering
  2382. if (view.group != 0 && !this.allowedReceivingGroups.Contains(view.group))
  2383. {
  2384. return; // Ignore group
  2385. }
  2386. if (view.synchronization == ViewSynchronization.ReliableDeltaCompressed)
  2387. {
  2388. if (!this.DeltaCompressionRead(view, data))
  2389. {
  2390. // Skip this packet as we haven't got received complete-copy of this view yet.
  2391. this.DebugReturn(DebugLevel.INFO, "Skipping packet for " + view.name + " [" + view.viewID + "] as we haven't received a full packet for delta compression yet. This is OK if it happens for the first few frames after joining a game.");
  2392. return;
  2393. }
  2394. // store last received for delta-compression usage
  2395. view.lastOnSerializeDataReceived = data[(byte)1] as object[];
  2396. }
  2397. // Use incoming data according to observed type
  2398. if (view.observed is MonoBehaviour)
  2399. {
  2400. object[] contents = data[(byte)1] as object[];
  2401. MonoBehaviour monob = (MonoBehaviour)view.observed;
  2402. PhotonStream pStream = new PhotonStream(false, contents);
  2403. PhotonMessageInfo info = new PhotonMessageInfo(sender, networkTime, view);
  2404. this.ExecuteOnSerialize(monob, pStream, info);
  2405. }
  2406. else if (view.observed is Transform)
  2407. {
  2408. object[] contents = data[(byte)1] as object[];
  2409. Transform trans = (Transform)view.observed;
  2410. if (contents.Length >= 1 && contents[0] != null)
  2411. trans.localPosition = (Vector3)contents[0];
  2412. if (contents.Length >= 2 && contents[1] != null)
  2413. trans.localRotation = (Quaternion)contents[1];
  2414. if (contents.Length >= 3 && contents[2] != null)
  2415. trans.localScale = (Vector3)contents[2];
  2416. }
  2417. else if (view.observed is Rigidbody)
  2418. {
  2419. object[] contents = data[(byte)1] as object[];
  2420. Rigidbody rigidB = (Rigidbody)view.observed;
  2421. if (contents.Length >= 1 && contents[0] != null)
  2422. rigidB.velocity = (Vector3)contents[0];
  2423. if (contents.Length >= 2 && contents[1] != null)
  2424. rigidB.angularVelocity = (Vector3)contents[1];
  2425. }
  2426. else
  2427. {
  2428. Debug.LogError("Type of observed is unknown when receiving.");
  2429. }
  2430. }
  2431. /// <summary>
  2432. /// Compares the new data with previously sent data and skips values that didn't change.
  2433. /// </summary>
  2434. /// <returns>True if anything has to be sent, false if nothing new or no data</returns>
  2435. private bool DeltaCompressionWrite(PhotonView view, Hashtable data)
  2436. {
  2437. if (view.lastOnSerializeDataSent == null)
  2438. {
  2439. return true; // all has to be sent
  2440. }
  2441. // We can compress as we sent a full update previously (readers can re-use previous values)
  2442. object[] lastData = view.lastOnSerializeDataSent;
  2443. object[] currentContent = data[(byte)1] as object[];
  2444. if (currentContent == null)
  2445. {
  2446. // no data to be sent
  2447. return false;
  2448. }
  2449. if (lastData.Length != currentContent.Length)
  2450. {
  2451. // if new data isn't same length as before, we send the complete data-set uncompressed
  2452. return true;
  2453. }
  2454. object[] compressedContent = new object[currentContent.Length];
  2455. int compressedValues = 0;
  2456. List<int> valuesThatAreChangedToNull = new List<int>();
  2457. for (int index = 0; index < compressedContent.Length; index++)
  2458. {
  2459. object newObj = currentContent[index];
  2460. object oldObj = lastData[index];
  2461. if (this.ObjectIsSameWithInprecision(newObj, oldObj))
  2462. {
  2463. // compress (by using null, instead of value, which is same as before)
  2464. compressedValues++;
  2465. // compressedContent[index] is already null (initialized)
  2466. }
  2467. else
  2468. {
  2469. compressedContent[index] = currentContent[index];
  2470. // value changed, we don't replace it with null
  2471. // new value is null (like a compressed value): we have to mark it so it STAYS null instead of being replaced with previous value
  2472. if (newObj == null)
  2473. {
  2474. valuesThatAreChangedToNull.Add(index);
  2475. }
  2476. }
  2477. }
  2478. // Only send the list of compressed fields if we actually compressed 1 or more fields.
  2479. if (compressedValues > 0)
  2480. {
  2481. data.Remove((byte)1); // remove the original data (we only send compressed data)
  2482. if (compressedValues == currentContent.Length)
  2483. {
  2484. // all values are compressed to null, we have nothing to send
  2485. return false;
  2486. }
  2487. data[(byte)2] = compressedContent; // current, compressted data is moved to key 2 to mark it as compressed
  2488. if (valuesThatAreChangedToNull.Count > 0)
  2489. {
  2490. data[(byte)3] = valuesThatAreChangedToNull.ToArray(); // data that is actually null (not just cause we didn't want to send it)
  2491. }
  2492. }
  2493. return true; // some data was compressed but we need to send something
  2494. }
  2495. /// <summary>
  2496. /// reads incoming messages created by "OnSerialize"
  2497. /// </summary>
  2498. private bool DeltaCompressionRead(PhotonView view, Hashtable data)
  2499. {
  2500. if (data.ContainsKey((byte)1))
  2501. {
  2502. // we have a full list of data (cause key 1 is used), so return "we have uncompressed all"
  2503. return true;
  2504. }
  2505. // Compression was applied as data[(byte)2] exists (this is the data with some fields being compressed to null)
  2506. // now we also need a previous "full" list of values to restore values that are null in this msg
  2507. if (view.lastOnSerializeDataReceived == null)
  2508. {
  2509. return false; // We dont have a full match yet, we cannot work with missing values: skip this message
  2510. }
  2511. object[] compressedContents = data[(byte)2] as object[];
  2512. if (compressedContents == null)
  2513. {
  2514. // despite expectation, there is no compressed data in this msg. shouldn't happen. just a null check
  2515. return false;
  2516. }
  2517. int[] indexesThatAreChangedToNull = data[(byte)3] as int[];
  2518. if (indexesThatAreChangedToNull == null)
  2519. {
  2520. indexesThatAreChangedToNull = new int[0];
  2521. }
  2522. object[] lastReceivedData = view.lastOnSerializeDataReceived;
  2523. for (int index = 0; index < compressedContents.Length; index++)
  2524. {
  2525. if (compressedContents[index] == null && !indexesThatAreChangedToNull.Contains(index))
  2526. {
  2527. // we replace null values in this received msg unless a index is in the "changed to null" list
  2528. object lastValue = lastReceivedData[index];
  2529. compressedContents[index] = lastValue;
  2530. }
  2531. }
  2532. data[(byte)1] = compressedContents; // compressedContents are now uncompressed...
  2533. return true;
  2534. }
  2535. /// <summary>
  2536. /// Returns true if both objects are almost identical.
  2537. /// Used to check whether two objects are similar enough to skip an update.
  2538. /// </summary>
  2539. bool ObjectIsSameWithInprecision(object one, object two)
  2540. {
  2541. if (one == null || two == null)
  2542. {
  2543. return one == null && two == null;
  2544. }
  2545. if (!one.Equals(two))
  2546. {
  2547. // if A is not B, lets check if A is almost B
  2548. if (one is Vector3)
  2549. {
  2550. Vector3 a = (Vector3)one;
  2551. Vector3 b = (Vector3)two;
  2552. if (a.AlmostEquals(b, PhotonNetwork.precisionForVectorSynchronization))
  2553. {
  2554. return true;
  2555. }
  2556. }
  2557. else if (one is Vector2)
  2558. {
  2559. Vector2 a = (Vector2)one;
  2560. Vector2 b = (Vector2)two;
  2561. if (a.AlmostEquals(b, PhotonNetwork.precisionForVectorSynchronization))
  2562. {
  2563. return true;
  2564. }
  2565. }
  2566. else if (one is Quaternion)
  2567. {
  2568. Quaternion a = (Quaternion)one;
  2569. Quaternion b = (Quaternion)two;
  2570. if (a.AlmostEquals(b, PhotonNetwork.precisionForQuaternionSynchronization))
  2571. {
  2572. return true;
  2573. }
  2574. }
  2575. else if (one is float)
  2576. {
  2577. float a = (float)one;
  2578. float b = (float)two;
  2579. if (a.AlmostEquals(b, PhotonNetwork.precisionForFloatSynchronization))
  2580. {
  2581. return true;
  2582. }
  2583. }
  2584. // one does not equal two
  2585. return false;
  2586. }
  2587. return true;
  2588. }
  2589. private MethodInfo GetCachedMethod(MonoBehaviour monob, PhotonNetworkingMessage methodType)
  2590. {
  2591. Type type = monob.GetType();
  2592. if (!this.cachedMethods.ContainsKey(type))
  2593. {
  2594. Dictionary<PhotonNetworkingMessage, MethodInfo> newMethodsDict = new Dictionary<PhotonNetworkingMessage, MethodInfo>();
  2595. this.cachedMethods.Add(type, newMethodsDict);
  2596. }
  2597. // Get method type list
  2598. Dictionary<PhotonNetworkingMessage, MethodInfo> methods = this.cachedMethods[type];
  2599. if (!methods.ContainsKey(methodType))
  2600. {
  2601. // Load into cache
  2602. Type[] argTypes;
  2603. if (methodType == PhotonNetworkingMessage.OnPhotonSerializeView)
  2604. {
  2605. argTypes = new Type[2];
  2606. argTypes[0] = typeof(PhotonStream);
  2607. argTypes[1] = typeof(PhotonMessageInfo);
  2608. }
  2609. else if (methodType == PhotonNetworkingMessage.OnPhotonInstantiate)
  2610. {
  2611. argTypes = new Type[1];
  2612. argTypes[0] = typeof(PhotonMessageInfo);
  2613. }
  2614. else
  2615. {
  2616. Debug.LogError("Invalid PhotonNetworkingMessage!");
  2617. return null;
  2618. }
  2619. MethodInfo metInfo = monob.GetType().GetMethod(methodType + string.Empty, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, argTypes, null);
  2620. if (metInfo != null)
  2621. {
  2622. methods.Add(methodType, metInfo);
  2623. }
  2624. }
  2625. if (methods.ContainsKey(methodType))
  2626. {
  2627. return methods[methodType];
  2628. }
  2629. else
  2630. {
  2631. return null;
  2632. }
  2633. }
  2634. /// <summary>Internally used to flag if the message queue was disabled by a "scene sync" situation (to re-enable it).</summary>
  2635. internal protected bool loadingLevelAndPausedNetwork = false;
  2636. /// <summary>For automatic scene syncing, the loaded scene is put into a room property. This is the name of said prop.</summary>
  2637. internal protected const string CurrentSceneProperty = "curScn";
  2638. /// <summary>Internally used to detect the current scene and load it if PhotonNetwork.automaticallySyncScene is enabled.</summary>
  2639. internal protected void AutomaticallySyncScene()
  2640. {
  2641. if (PhotonNetwork.room != null && PhotonNetwork.automaticallySyncScene && !PhotonNetwork.isMasterClient)
  2642. {
  2643. string sceneName = (string)PhotonNetwork.room.customProperties[NetworkingPeer.CurrentSceneProperty];
  2644. if (!string.IsNullOrEmpty(sceneName))
  2645. {
  2646. if (sceneName != Application.loadedLevelName)
  2647. {
  2648. PhotonNetwork.LoadLevel(sceneName);
  2649. }
  2650. }
  2651. }
  2652. }
  2653. }