Advertisement
Guest User

BlaBlaa

a guest
Apr 20th, 2014
204
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 144.02 KB | None | 0 0
  1. // --------------------------------------------------------------------------------------------------------------------
  2. // <copyright file="NetworkingPeer.cs" company="Exit Games GmbH">
  3. // Part of: Photon Unity Networking (PUN)
  4. // </copyright>
  5. // --------------------------------------------------------------------------------------------------------------------
  6.  
  7. using ExitGames.Client.Photon;
  8. using ExitGames.Client.Photon.Lite;
  9. using System;
  10. using System.Collections;
  11. using System.Collections.Generic;
  12. using System.Reflection;
  13. using UnityEngine;
  14. using Hashtable = ExitGames.Client.Photon.Hashtable;
  15.  
  16. /// <summary>
  17. /// Implements Photon LoadBalancing used in PUN.
  18. /// This class is used internally by PhotonNetwork and not intended as public API.
  19. /// </summary>
  20. internal class NetworkingPeer : LoadbalancingPeer, IPhotonPeerListener
  21. {
  22. /// <summary>The AppVersion is a combination of GameVersion+"_"+PunVersion. Separates players per app by version.</summary>
  23. public string mAppVersion;
  24.  
  25. /// <summary>Contains the AppId for the Photon Cloud (ignored by Photon Servers).</summary>
  26. private string mAppId;
  27.  
  28. /// <summary>
  29. /// A user's authentication values used during connect for Custom Authentication with Photon (and a custom service/community).
  30. /// Set these before calling Connect if you want custom authentication.
  31. /// </summary>
  32. public AuthenticationValues CustomAuthenticationValues { get; set; }
  33.  
  34. public string MasterServerAddress { get; protected internal set; }
  35.  
  36. private string playername = "";
  37.  
  38. private IPhotonPeerListener externalListener;
  39.  
  40. private JoinType mLastJoinType;
  41.  
  42. private bool mPlayernameHasToBeUpdated;
  43.  
  44. public string PlayerName
  45. {
  46. get
  47. {
  48. return this.playername;
  49. }
  50.  
  51. set
  52. {
  53. if (string.IsNullOrEmpty(value) || value.Equals(this.playername))
  54. {
  55. return;
  56. }
  57.  
  58. if (this.mLocalActor != null)
  59. {
  60. this.mLocalActor.name = value;
  61. }
  62.  
  63. this.playername = value;
  64. if (this.mCurrentGame != null)
  65. {
  66. // Only when in a room
  67. this.SendPlayerName();
  68. }
  69. }
  70. }
  71.  
  72. public PeerState State { get; internal set; }
  73.  
  74. // "public" access to the current game - is null unless a room is joined on a gameserver
  75. public Room mCurrentGame
  76. {
  77. get
  78. {
  79. if (this.mRoomToGetInto != null && this.mRoomToGetInto.isLocalClientInside)
  80. {
  81. return this.mRoomToGetInto;
  82. }
  83.  
  84. return null;
  85. }
  86. }
  87.  
  88. /// <summary>
  89. /// keeps the custom properties, gameServer address and anything else about the room we want to get into
  90. /// </summary>
  91. internal Room mRoomToGetInto { get; set; }
  92. internal RoomOptions mRoomOptionsForCreate { get; set; }
  93. internal TypedLobby mRoomToEnterLobby { get; set; }
  94.  
  95. public Dictionary<int, PhotonPlayer> mActors = new Dictionary<int, PhotonPlayer>();
  96.  
  97. public PhotonPlayer[] mOtherPlayerListCopy = new PhotonPlayer[0];
  98. public PhotonPlayer[] mPlayerListCopy = new PhotonPlayer[0];
  99.  
  100. public PhotonPlayer mLocalActor { get; internal set; }
  101.  
  102. public PhotonPlayer mMasterClient = null;
  103.  
  104. public bool hasSwitchedMC = false;
  105.  
  106. public string mGameserver { get; internal set; }
  107.  
  108. public bool requestSecurity = true;
  109.  
  110. private Dictionary<Type, List<MethodInfo>> monoRPCMethodsCache = new Dictionary<Type, List<MethodInfo>>();
  111.  
  112. public static bool UsePrefabCache = true;
  113.  
  114. public static Dictionary<string, GameObject> PrefabCache = new Dictionary<string, GameObject>();
  115.  
  116. public Dictionary<string, RoomInfo> mGameList = new Dictionary<string, RoomInfo>();
  117. public RoomInfo[] mGameListCopy = new RoomInfo[0];
  118.  
  119. public int mQueuePosition { get; internal set; }
  120.  
  121. public TypedLobby lobby { get; set; }
  122.  
  123. public bool insideLobby = false;
  124.  
  125. /// <summary>Stat value: Count of players on Master (looking for rooms)</summary>
  126. public int mPlayersOnMasterCount { get; internal set; }
  127.  
  128. /// <summary>Stat value: Count of Rooms</summary>
  129. public int mGameCount { get; internal set; }
  130.  
  131. /// <summary>Stat value: Count of Players in rooms</summary>
  132. public int mPlayersInRoomsCount { get; internal set; }
  133.  
  134. /// <summary>
  135. /// Instantiated objects by their instantiationId. The id (key) is the instantiationId (created per actor).
  136. /// </summary>
  137. public Dictionary<int, GameObject> instantiatedObjects = new Dictionary<int, GameObject>();
  138.  
  139. private HashSet<int> allowedReceivingGroups = new HashSet<int>();
  140.  
  141. private HashSet<int> blockSendingGroups = new HashSet<int>();
  142.  
  143. internal protected Dictionary<int, PhotonView> photonViewList = new Dictionary<int, PhotonView>(); //TODO: make private again
  144.  
  145. internal protected short currentLevelPrefix = 0;
  146.  
  147. private readonly Dictionary<string, int> rpcShortcuts; // lookup "table" for the index (shortcut) of an RPC name
  148.  
  149. /// <summary>The server this client is currently connected or connecting to.</summary>
  150. internal protected ServerConnection server { get; private set; }
  151.  
  152. public bool IsInitialConnect = false;
  153.  
  154. /// <summary>True if this client uses a NameServer to get the Master Server address.</summary>
  155. public bool IsUsingNameServer { get; protected internal set; }
  156.  
  157. /// <summary>Name Server Address that this client uses. You can use the default values and usually won't have to set this value.</summary>
  158. public string NameServerAddress = "ns.exitgamescloud.com:5058";
  159.  
  160. /// <summary>A list of region names for the Photon Cloud. Set by the result of OpGetRegions().</summary>
  161. /// <remarks>Put a "case OperationCode.GetRegions:" into your OnOperationResponse method to notice when the result is available.</remarks>
  162. public string[] AvailableRegions { get; protected internal set; }
  163.  
  164. /// <summary>A list of region server (IP addresses with port) for the Photon Cloud. Set by the result of OpGetRegions().</summary>
  165. /// <remarks>Put a "case OperationCode.GetRegions:" into your OnOperationResponse method to notice when the result is available.</remarks>
  166. public string[] AvailableRegionsServers { get; protected internal set; }
  167.  
  168. /// <summary>The cloud region this client connects to. Set by ConnectToRegionMaster().</summary>
  169. public string CloudRegion { get; protected internal set; }
  170.  
  171. /// <summary>Internally used to check if a "Secret" is available to use. Sent by Photon Cloud servers, it simplifies authentication when switching servers.</summary>
  172. public bool IsAuthorizeSecretAvailable
  173. {
  174. get
  175. {
  176. // TODO: comment in the code again, when the new auth-workflow is available in the Cloud
  177. return false; // this.CustomAuthenticationValues != null && !String.IsNullOrEmpty(this.CustomAuthenticationValues.Secret);
  178. }
  179. }
  180.  
  181. /// <summary>Internally used to trigger OpAuthenticate when encryption was established after a connect.</summary>
  182. private bool didAuthenticate;
  183.  
  184. public NetworkingPeer(IPhotonPeerListener listener, string playername, ConnectionProtocol connectionProtocol) : base(listener, connectionProtocol)
  185. {
  186. #pragma warning disable 0162 // the library variant defines if we should use PUN's SocketUdp variant (at all)
  187. if (PhotonPeer.NoSocket )
  188. {
  189. #if !UNITY_EDITOR && (UNITY_PS3 || UNITY_ANDROID)
  190. Debug.Log("Using class SocketUdpNativeDynamic");
  191. this.SocketImplementation = typeof(SocketUdpNativeDynamic);
  192. #elif !UNITY_EDITOR && UNITY_IPHONE
  193. Debug.Log("Using class SocketUdpNativeStatic");
  194. this.SocketImplementation = typeof(SocketUdpNativeStatic);
  195. #elif !UNITY_EDITOR && (UNITY_WINRT || UNITY_WP8)
  196. // this automatically uses a separate assembly-file with Win8-style Socket usage (not possible in Editor)
  197. #else
  198. this.SocketImplementation = typeof(SocketUdp);
  199.  
  200. if (this.SocketImplementation == null)
  201. {
  202. Debug.Log("No socket implementation set for 'NoSocket' assembly. Please contact Exit Games.");
  203. }
  204. #endif
  205. }
  206. #pragma warning restore 0162
  207.  
  208. this.Listener = this;
  209. this.lobby = TypedLobby.Default;
  210.  
  211. // don't set the field directly! the listener is passed on to other classes, which get updated by the property set method
  212. this.externalListener = listener;
  213. this.PlayerName = playername;
  214. this.mLocalActor = new PhotonPlayer(true, -1, this.playername);
  215. this.AddNewPlayer(this.mLocalActor.ID, this.mLocalActor);
  216.  
  217. // RPC shortcut lookup creation (from list of RPCs, which is updated by Editor scripts)
  218. rpcShortcuts = new Dictionary<string, int>(PhotonNetwork.PhotonServerSettings.RpcList.Count);
  219. for (int index = 0; index < PhotonNetwork.PhotonServerSettings.RpcList.Count; index++)
  220. {
  221. var name = PhotonNetwork.PhotonServerSettings.RpcList[index];
  222. rpcShortcuts[name] = index;
  223. }
  224.  
  225. this.State = global::PeerState.PeerCreated;
  226. }
  227.  
  228. #region Operations and Connection Methods
  229.  
  230.  
  231. public override bool Connect(string serverAddress, string applicationName)
  232. {
  233. Debug.LogError("Avoid using this directly. Thanks.");
  234. return false;
  235. }
  236.  
  237. public bool Connect(string serverAddress, ServerConnection type)
  238. {
  239. if (PhotonNetwork.connectionStateDetailed == global::PeerState.Disconnecting)
  240. {
  241. Debug.LogError("Connect() failed. Can't connect while disconnecting (still). Current state: " + PhotonNetwork.connectionStateDetailed);
  242. return false;
  243. }
  244.  
  245. // connect might fail, if the DNS name can't be resolved or if no network connection is available
  246. bool connecting = base.Connect(serverAddress, "");
  247. if (connecting)
  248. {
  249. switch (type)
  250. {
  251. case ServerConnection.NameServer:
  252. State = global::PeerState.ConnectingToNameServer;
  253. break;
  254. case ServerConnection.MasterServer:
  255. State = global::PeerState.ConnectingToMasterserver;
  256. break;
  257. case ServerConnection.GameServer:
  258. State = global::PeerState.ConnectingToGameserver;
  259. break;
  260. }
  261. }
  262.  
  263. return connecting;
  264. }
  265.  
  266.  
  267. /// <summary>
  268. /// Connects to the NameServer for Photon Cloud, where a region and server list can be obtained.
  269. /// </summary>
  270. /// <see cref="OpGetRegions"/>
  271. /// <returns>If the workflow was started or failed right away.</returns>
  272. public bool ConnectToNameServer()
  273. {
  274. IsUsingNameServer = true;
  275. this.CloudRegion = null;
  276.  
  277. if (this.State == global::PeerState.ConnectedToNameServer)
  278. {
  279. return true;
  280. }
  281.  
  282. if (!base.Connect(this.NameServerAddress, "ns"))
  283. {
  284. return false;
  285. }
  286.  
  287. this.State = global::PeerState.ConnectingToNameServer;
  288. return true;
  289. }
  290.  
  291. /// <summary>
  292. /// Connects you to a specific region's Master Server, using the Name Server to find the IP.
  293. /// </summary>
  294. /// <returns>If the operation could be sent. If false, no operation was sent.</returns>
  295. public bool ConnectToRegionMaster(string region)
  296. {
  297. IsUsingNameServer = true;
  298. this.CloudRegion = region;
  299.  
  300. if (this.State == global::PeerState.ConnectedToNameServer)
  301. {
  302. return this.OpAuthenticate(this.mAppId, this.mAppVersion, this.PlayerName, this.CustomAuthenticationValues, region);
  303. }
  304.  
  305. if (!base.Connect(this.NameServerAddress, "ns"))
  306. {
  307. return false;
  308. }
  309.  
  310. this.State = global::PeerState.ConnectingToNameServer;
  311. return true;
  312. }
  313.  
  314. /// <summary>
  315. /// While on the NameServer, this gets you the list of regional servers (short names and their IPs to ping them).
  316. /// </summary>
  317. /// <returns>If the operation could be sent. If false, no operation was sent (e.g. while not connected to the NameServer).</returns>
  318. public bool GetRegions()
  319. {
  320. if (this.server != ServerConnection.NameServer)
  321. {
  322. return false;
  323. }
  324.  
  325. bool sent = this.OpGetRegions(this.mAppId);
  326. if (sent)
  327. {
  328. this.AvailableRegions = null;
  329. }
  330.  
  331. return sent;
  332. }
  333.  
  334. /// <summary>
  335. /// Complete disconnect from photon (and the open master OR game server)
  336. /// </summary>
  337. public override void Disconnect()
  338. {
  339. if (this.PeerState == PeerStateValue.Disconnected)
  340. {
  341. Debug.LogWarning(string.Format("Can't execute Disconnect() while not connected. Nothing changed. State: {0}", this.State));
  342. return;
  343. }
  344.  
  345. this.State = global::PeerState.Disconnecting;
  346. base.Disconnect();
  347.  
  348. this.LeftRoomCleanup();
  349. this.LeftLobbyCleanup();
  350. }
  351.  
  352.  
  353. /// <summary>
  354. /// Internally used only.
  355. /// </summary>
  356. private void DisconnectToReconnect()
  357. {
  358. switch (this.server)
  359. {
  360. case ServerConnection.NameServer:
  361. this.State = global::PeerState.DisconnectingFromNameServer;
  362. base.Disconnect();
  363. break;
  364. case ServerConnection.MasterServer:
  365. this.State = global::PeerState.DisconnectingFromMasterserver;
  366. base.Disconnect();
  367. LeftLobbyCleanup();
  368. break;
  369. case ServerConnection.GameServer:
  370. this.State = global::PeerState.DisconnectingFromGameserver;
  371. base.Disconnect();
  372. this.LeftRoomCleanup();
  373. break;
  374. }
  375. }
  376.  
  377. /// <summary>
  378. /// Called at disconnect/leavelobby etc. This CAN also be called when we are not in a lobby (e.g. disconnect from room)
  379. /// </summary>
  380. /// <remarks>Calls callback method OnLeftLobby if this client was in a lobby initially. Clears the lobby's game lists.</remarks>
  381. private void LeftLobbyCleanup()
  382. {
  383. this.mGameList = new Dictionary<string, RoomInfo>();
  384. this.mGameListCopy = new RoomInfo[0];
  385.  
  386. if (insideLobby)
  387. {
  388. this.insideLobby = false;
  389. SendMonoMessage(PhotonNetworkingMessage.OnLeftLobby);
  390. }
  391. }
  392.  
  393. /// <summary>
  394. /// Called when "this client" left a room to clean up.
  395. /// </summary>
  396. private void LeftRoomCleanup()
  397. {
  398. bool wasInRoom = mRoomToGetInto != null;
  399. // when leaving a room, we clean up depending on that room's settings.
  400. bool autoCleanupSettingOfRoom = (this.mRoomToGetInto != null) ? this.mRoomToGetInto.autoCleanUp : PhotonNetwork.autoCleanUpPlayerObjects;
  401.  
  402. this.hasSwitchedMC = false;
  403. this.mRoomToGetInto = null;
  404. this.mActors = new Dictionary<int, PhotonPlayer>();
  405. this.mPlayerListCopy = new PhotonPlayer[0];
  406. this.mOtherPlayerListCopy = new PhotonPlayer[0];
  407. this.mMasterClient = null;
  408. this.allowedReceivingGroups = new HashSet<int>();
  409. this.blockSendingGroups = new HashSet<int>();
  410. this.mGameList = new Dictionary<string, RoomInfo>();
  411. this.mGameListCopy = new RoomInfo[0];
  412. this.isFetchingFriends = false;
  413.  
  414. this.ChangeLocalID(-1);
  415.  
  416. // Cleanup all network objects (all spawned PhotonViews, local and remote)
  417. if (autoCleanupSettingOfRoom)
  418. {
  419. this.LocalCleanupAnythingInstantiated(true);
  420. PhotonNetwork.manuallyAllocatedViewIds = new List<int>(); // filled and easier to replace completely
  421. }
  422.  
  423. if (wasInRoom)
  424. {
  425. SendMonoMessage(PhotonNetworkingMessage.OnLeftRoom);
  426. }
  427. }
  428.  
  429. protected internal void LocalCleanupAnythingInstantiated(bool destroyInstantiatedGameObjects)
  430. {
  431. if (tempInstantiationData.Count > 0)
  432. {
  433. Debug.LogWarning("It seems some instantiation is not completed, as instantiation data is used. You should make sure instantiations are paused when calling this method. Cleaning now, despite this.");
  434. }
  435.  
  436. // Destroy GO's (if we should)
  437. if (destroyInstantiatedGameObjects)
  438. {
  439. // Fill list with Instantiated objects
  440. HashSet<GameObject> goList = new HashSet<GameObject>(this.instantiatedObjects.Values);
  441. foreach (GameObject go in goList)
  442. {
  443. this.RemoveInstantiatedGO(go, true);
  444. }
  445. }
  446.  
  447. // photonViewList is cleared of anything instantiated (so scene items are left inside)
  448. // any other lists can be
  449. this.tempInstantiationData.Clear(); // should be empty but to be safe we clear (no new list needed)
  450. this.instantiatedObjects = new Dictionary<int, GameObject>(); // filled and easier to replace completely
  451. PhotonNetwork.lastUsedViewSubId = 0;
  452. PhotonNetwork.lastUsedViewSubIdStatic = 0;
  453. }
  454.  
  455. // gameID can be null (optional). The server assigns a unique name if no name is set
  456.  
  457. // joins a room and sets your current username as custom actorproperty (will broadcast that)
  458.  
  459. #endregion
  460.  
  461. #region Helpers
  462.  
  463. private void ReadoutProperties(Hashtable gameProperties, Hashtable pActorProperties, int targetActorNr)
  464. {
  465. // Debug.LogWarning("ReadoutProperties gameProperties: " + gameProperties.ToStringFull() + " pActorProperties: " + pActorProperties.ToStringFull() + " targetActorNr: " + targetActorNr);
  466. // read game properties and cache them locally
  467. if (this.mCurrentGame != null && gameProperties != null)
  468. {
  469. this.mCurrentGame.CacheProperties(gameProperties);
  470. SendMonoMessage(PhotonNetworkingMessage.OnPhotonCustomRoomPropertiesChanged, gameProperties);
  471. if (PhotonNetwork.automaticallySyncScene)
  472. {
  473. this.LoadLevelIfSynced(); // will load new scene if sceneName was changed
  474. }
  475. }
  476.  
  477. if (pActorProperties != null && pActorProperties.Count > 0)
  478. {
  479. if (targetActorNr > 0)
  480. {
  481. // we have a single entry in the pActorProperties with one
  482. // user's name
  483. // targets MUST exist before you set properties
  484. PhotonPlayer target = this.GetPlayerWithID(targetActorNr);
  485. if (target != null)
  486. {
  487. Hashtable props = this.GetActorPropertiesForActorNr(pActorProperties, targetActorNr);
  488. target.InternalCacheProperties(props);
  489. SendMonoMessage(PhotonNetworkingMessage.OnPhotonPlayerPropertiesChanged, target, props);
  490. }
  491. }
  492. else
  493. {
  494. // in this case, we've got a key-value pair per actor (each
  495. // value is a hashtable with the actor's properties then)
  496. int actorNr;
  497. Hashtable props;
  498. string newName;
  499. PhotonPlayer target;
  500.  
  501. foreach (object key in pActorProperties.Keys)
  502. {
  503. actorNr = (int)key;
  504. props = (Hashtable)pActorProperties[key];
  505. newName = (string)props[ActorProperties.PlayerName];
  506.  
  507. target = this.GetPlayerWithID(actorNr);
  508. if (target == null)
  509. {
  510. target = new PhotonPlayer(false, actorNr, newName);
  511. this.AddNewPlayer(actorNr, target);
  512. }
  513.  
  514. target.InternalCacheProperties(props);
  515. SendMonoMessage(PhotonNetworkingMessage.OnPhotonPlayerPropertiesChanged, target, props);
  516. }
  517. }
  518. }
  519. }
  520.  
  521. private void AddNewPlayer(int ID, PhotonPlayer player)
  522. {
  523. if (!this.mActors.ContainsKey(ID))
  524. {
  525. this.mActors[ID] = player;
  526. RebuildPlayerListCopies();
  527. }
  528. else
  529. {
  530. Debug.LogError("Adding player twice: " + ID);
  531. }
  532. }
  533.  
  534. void RemovePlayer(int ID, PhotonPlayer player)
  535. {
  536. this.mActors.Remove(ID);
  537. if (!player.isLocal)
  538. {
  539. RebuildPlayerListCopies();
  540. }
  541. }
  542.  
  543. void RebuildPlayerListCopies()
  544. {
  545. this.mPlayerListCopy = new PhotonPlayer[this.mActors.Count];
  546. this.mActors.Values.CopyTo(this.mPlayerListCopy, 0);
  547.  
  548. List<PhotonPlayer> otherP = new List<PhotonPlayer>();
  549. foreach (PhotonPlayer player in this.mPlayerListCopy)
  550. {
  551. if (!player.isLocal)
  552. {
  553. otherP.Add(player);
  554. }
  555. }
  556.  
  557. this.mOtherPlayerListCopy = otherP.ToArray();
  558. }
  559.  
  560. /// <summary>
  561. /// 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!).
  562. /// Note that due to this reset, ALL other players will receive the full OnSerialize.
  563. /// </summary>
  564. private void ResetPhotonViewsOnSerialize()
  565. {
  566. foreach (PhotonView photonView in this.photonViewList.Values)
  567. {
  568. photonView.lastOnSerializeDataSent = null;
  569. }
  570. }
  571.  
  572. /// <summary>
  573. /// Called when the event Leave (of some other player) arrived.
  574. /// Cleans game objects, views locally. The master will also clean the
  575. /// </summary>
  576. /// <param name="actorID">ID of player who left.</param>
  577. private void HandleEventLeave(int actorID)
  578. {
  579. if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
  580. Debug.Log("HandleEventLeave for player ID: " + actorID);
  581.  
  582.  
  583. // actorNr is fetched out of event above
  584. if (actorID < 0 || !this.mActors.ContainsKey(actorID))
  585. {
  586. Debug.LogError(String.Format("Received event Leave for unknown player ID: {0}", actorID));
  587. return;
  588. }
  589.  
  590. PhotonPlayer player = this.GetPlayerWithID(actorID);
  591. if (player == null)
  592. {
  593. Debug.LogError("HandleEventLeave for player ID: " + actorID + " has no PhotonPlayer!");
  594. }
  595.  
  596. // having a new master before calling destroy for the leaving player is important!
  597. // so we elect a new masterclient and ignore the leaving player (who is still in playerlists).
  598. this.CheckMasterClient(actorID);
  599.  
  600.  
  601. // destroy objects & buffered messages
  602. if (this.mCurrentGame != null && this.mCurrentGame.autoCleanUp)
  603. {
  604. this.DestroyPlayerObjects(actorID, true);
  605. }
  606.  
  607. RemovePlayer(actorID, player);
  608.  
  609. // finally, send notification (the playerList and masterclient are now updated)
  610. SendMonoMessage(PhotonNetworkingMessage.OnPhotonPlayerDisconnected, player);
  611. }
  612.  
  613. /// <summary>Picks the new master client from player list, if the current Master is leaving (leavingPlayerId) or if no master was assigned so far.</summary>
  614. /// <param name="leavingPlayerId">
  615. /// The ignored player is the one who's leaving and should not become master (again). Pass -1 to select any player from the list.
  616. /// </param>
  617. private void CheckMasterClient(int leavingPlayerId)
  618. {
  619. bool currentMasterIsLeaving = this.mMasterClient != null && this.mMasterClient.ID == leavingPlayerId;
  620. bool someoneIsLeaving = leavingPlayerId > 0;
  621.  
  622. // return early if SOME player (leavingId > 0) is leaving AND it's NOT the current master
  623. if (someoneIsLeaving && !currentMasterIsLeaving)
  624. {
  625. return;
  626. }
  627.  
  628. // picking the player with lowest ID (longest in game).
  629. if (this.mActors.Count <= 1)
  630. {
  631. this.mMasterClient = this.mLocalActor;
  632. }
  633. else
  634. {
  635. // keys in mActors are their actorNumbers
  636. int lowestActorNumber = Int32.MaxValue;
  637. foreach (int key in this.mActors.Keys)
  638. {
  639. if (key < lowestActorNumber && key != leavingPlayerId)
  640. {
  641. lowestActorNumber = key;
  642. }
  643. }
  644.  
  645. this.mMasterClient = this.mActors[lowestActorNumber];
  646. }
  647.  
  648. // make a callback ONLY when a player/Master left
  649. if (someoneIsLeaving)
  650. {
  651. SendMonoMessage(PhotonNetworkingMessage.OnMasterClientSwitched, this.mMasterClient);
  652. }
  653. }
  654.  
  655. /// <summary>
  656. /// Returns the lowest player.ID - used for Master Client picking.
  657. /// </summary>
  658. /// <remarks></remarks>
  659. private static int ReturnLowestPlayerId(PhotonPlayer[] players, int playerIdToIgnore)
  660. {
  661. if (players == null || players.Length == 0)
  662. {
  663. return -1;
  664. }
  665.  
  666. int lowestActorNumber = Int32.MaxValue;
  667. for (int i = 0; i < players.Length; i++)
  668. {
  669. PhotonPlayer photonPlayer = players[i];
  670. if (photonPlayer.ID == playerIdToIgnore)
  671. {
  672. continue;
  673. }
  674.  
  675. if (photonPlayer.ID < lowestActorNumber)
  676. {
  677. lowestActorNumber = photonPlayer.ID;
  678. }
  679. }
  680.  
  681. return lowestActorNumber;
  682. }
  683.  
  684. internal protected bool SetMasterClient(int playerId, bool sync)
  685. {
  686. bool masterReplaced = this.mMasterClient != null && this.mMasterClient.ID != playerId;
  687. if (!masterReplaced || !this.mActors.ContainsKey(playerId))
  688. {
  689. return false;
  690. }
  691.  
  692. if (sync)
  693. {
  694. bool sent = this.OpRaiseEvent(PunEvent.AssignMaster, new Hashtable() { { (byte)1, playerId } }, true, null);
  695. if (!sent)
  696. {
  697. return false;
  698. }
  699. }
  700.  
  701. this.hasSwitchedMC = true;
  702. this.mMasterClient = this.mActors[playerId];
  703. SendMonoMessage(PhotonNetworkingMessage.OnMasterClientSwitched, this.mMasterClient); // we only callback when an actual change is done
  704. return true;
  705. }
  706.  
  707. private Hashtable GetActorPropertiesForActorNr(Hashtable actorProperties, int actorNr)
  708. {
  709. if (actorProperties.ContainsKey(actorNr))
  710. {
  711. return (Hashtable)actorProperties[actorNr];
  712. }
  713.  
  714. return actorProperties;
  715. }
  716.  
  717. private PhotonPlayer GetPlayerWithID(int number)
  718. {
  719. if (this.mActors != null && this.mActors.ContainsKey(number))
  720. {
  721. return this.mActors[number];
  722. }
  723.  
  724. return null;
  725. }
  726.  
  727. private void SendPlayerName()
  728. {
  729. if (this.State == global::PeerState.Joining)
  730. {
  731. // this means, the join on the gameServer is sent (with an outdated name). send the new when in game
  732. this.mPlayernameHasToBeUpdated = true;
  733. return;
  734. }
  735.  
  736. if (this.mLocalActor != null)
  737. {
  738. this.mLocalActor.name = this.PlayerName;
  739. Hashtable properties = new Hashtable();
  740. properties[ActorProperties.PlayerName] = this.PlayerName;
  741. if (this.mLocalActor.ID > 0)
  742. {
  743. this.OpSetPropertiesOfActor(this.mLocalActor.ID, properties, true, (byte)0);
  744. this.mPlayernameHasToBeUpdated = false;
  745. }
  746. }
  747. }
  748.  
  749. private void GameEnteredOnGameServer(OperationResponse operationResponse)
  750. {
  751. if (operationResponse.ReturnCode != 0)
  752. {
  753. switch (operationResponse.OperationCode)
  754. {
  755. case OperationCode.CreateGame:
  756. if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
  757. Debug.Log("Create failed on GameServer. Changing back to MasterServer. Msg: " + operationResponse.DebugMessage);
  758. SendMonoMessage(PhotonNetworkingMessage.OnPhotonCreateRoomFailed);
  759. break;
  760. case OperationCode.JoinGame:
  761. if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
  762. Debug.Log("Join failed on GameServer. Changing back to MasterServer. Msg: " + operationResponse.DebugMessage);
  763. if (operationResponse.ReturnCode == ErrorCode.GameDoesNotExist)
  764. {
  765. Debug.Log("Most likely the game became empty during the switch to GameServer.");
  766. }
  767. SendMonoMessage(PhotonNetworkingMessage.OnPhotonJoinRoomFailed);
  768. break;
  769. case OperationCode.JoinRandomGame:
  770. if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
  771. Debug.Log("Join failed on GameServer. Changing back to MasterServer. Msg: " + operationResponse.DebugMessage);
  772. if (operationResponse.ReturnCode == ErrorCode.GameDoesNotExist)
  773. {
  774. Debug.Log("Most likely the game became empty during the switch to GameServer.");
  775. }
  776. SendMonoMessage(PhotonNetworkingMessage.OnPhotonRandomJoinFailed);
  777. break;
  778. }
  779.  
  780. this.DisconnectToReconnect();
  781. return;
  782. }
  783.  
  784. this.State = global::PeerState.Joined;
  785. this.mRoomToGetInto.isLocalClientInside = true;
  786.  
  787. Hashtable actorProperties = (Hashtable)operationResponse[ParameterCode.PlayerProperties];
  788. Hashtable gameProperties = (Hashtable)operationResponse[ParameterCode.GameProperties];
  789. this.ReadoutProperties(gameProperties, actorProperties, 0);
  790.  
  791. // the local player's actor-properties are not returned in join-result. add this player to the list
  792. int localActorNr = (int)operationResponse[ParameterCode.ActorNr];
  793.  
  794. this.ChangeLocalID(localActorNr);
  795. this.CheckMasterClient(-1);
  796.  
  797. if (this.mPlayernameHasToBeUpdated)
  798. {
  799. this.SendPlayerName();
  800. }
  801.  
  802. switch (operationResponse.OperationCode)
  803. {
  804. case OperationCode.CreateGame:
  805. SendMonoMessage(PhotonNetworkingMessage.OnCreatedRoom);
  806. break;
  807. case OperationCode.JoinGame:
  808. case OperationCode.JoinRandomGame:
  809. // the mono message for this is sent at another place
  810. break;
  811. }
  812. }
  813.  
  814. private Hashtable GetLocalActorProperties()
  815. {
  816. if (PhotonNetwork.player != null)
  817. {
  818. return PhotonNetwork.player.allProperties;
  819. }
  820.  
  821. Hashtable actorProperties = new Hashtable();
  822. actorProperties[ActorProperties.PlayerName] = this.PlayerName;
  823. return actorProperties;
  824. }
  825.  
  826. public void ChangeLocalID(int newID)
  827. {
  828. if (this.mLocalActor == null)
  829. {
  830. Debug.LogWarning(
  831. string.Format(
  832. "Local actor is null or not in mActors! mLocalActor: {0} mActors==null: {1} newID: {2}",
  833. this.mLocalActor,
  834. this.mActors == null,
  835. newID));
  836. }
  837.  
  838. if (this.mActors.ContainsKey(this.mLocalActor.ID))
  839. {
  840. this.mActors.Remove(this.mLocalActor.ID);
  841. }
  842.  
  843. this.mLocalActor.InternalChangeLocalID(newID);
  844. this.mActors[this.mLocalActor.ID] = this.mLocalActor;
  845. this.RebuildPlayerListCopies();
  846. }
  847.  
  848. #endregion
  849.  
  850. #region Operations
  851.  
  852. /// <summary>NetworkingPeer.OpCreateGame</summary>
  853. public bool OpCreateGame(string roomName, RoomOptions roomOptions, TypedLobby typedLobby)
  854. {
  855. bool onGameServer = this.server == ServerConnection.GameServer;
  856. if (!onGameServer)
  857. {
  858. this.mRoomOptionsForCreate = roomOptions;
  859. this.mRoomToGetInto = new Room(roomName, roomOptions);
  860. this.mRoomToEnterLobby = typedLobby ?? ((this.insideLobby) ? this.lobby : null); // use given lobby, or active lobby (if any active) or none
  861. }
  862.  
  863. this.mLastJoinType = JoinType.CreateGame;
  864. return base.OpCreateRoom(roomName, roomOptions, this.mRoomToEnterLobby, this.GetLocalActorProperties(), onGameServer);
  865. }
  866.  
  867. /// <summary>NetworkingPeer.OpJoinRoom</summary>
  868. public bool OpJoinRoom(string roomName, RoomOptions roomOptions, TypedLobby typedLobby, bool createIfNotExists)
  869. {
  870. bool onGameServer = this.server == ServerConnection.GameServer;
  871. if (!onGameServer)
  872. {
  873. // roomOptions and typedLobby will be null, unless createIfNotExists is true
  874. this.mRoomOptionsForCreate = roomOptions;
  875. this.mRoomToGetInto = new Room(roomName, roomOptions);
  876. this.mRoomToEnterLobby = null;
  877. if (createIfNotExists)
  878. {
  879. this.mRoomToEnterLobby = typedLobby ?? ((this.insideLobby) ? this.lobby : null); // use given lobby, or active lobby (if any active) or none
  880. }
  881. }
  882.  
  883. this.mLastJoinType = (createIfNotExists) ? JoinType.JoinOrCreateOnDemand : JoinType.JoinGame;
  884. return base.OpJoinRoom(roomName, roomOptions, this.mRoomToEnterLobby, createIfNotExists, this.GetLocalActorProperties(), onGameServer);
  885. }
  886.  
  887. /// <summary>NetworkingPeer.OpJoinRandomRoom</summary>
  888. /// <remarks>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)</remarks>
  889. public override bool OpJoinRandomRoom(Hashtable expectedCustomRoomProperties, byte expectedMaxPlayers, Hashtable playerProperties, MatchmakingMode matchingType, TypedLobby typedLobby, string sqlLobbyFilter)
  890. {
  891. this.mRoomToGetInto = new Room(null, null);
  892. this.mRoomToEnterLobby = null; // join random never stores the lobby. the following join will not affect the room lobby
  893. // if typedLobby is null, the server will automatically use the active lobby or default, which is what we want anyways
  894.  
  895. this.mLastJoinType = JoinType.JoinRandomGame;
  896. return base.OpJoinRandomRoom(expectedCustomRoomProperties, expectedMaxPlayers, playerProperties, matchingType, typedLobby, sqlLobbyFilter);
  897. }
  898.  
  899. /// <summary>
  900. /// Operation Leave will exit any current room.
  901. /// </summary>
  902. /// <remarks>
  903. /// This also happens when you disconnect from the server.
  904. /// Disconnect might be a step less if you don't want to create a new room on the same server.
  905. /// </remarks>
  906. /// <returns></returns>
  907. public virtual bool OpLeave()
  908. {
  909. if (this.State != global::PeerState.Joined)
  910. {
  911. Debug.LogWarning("Not sending leave operation. State is not 'Joined': " + this.State);
  912. return false;
  913. }
  914.  
  915. return this.OpCustom((byte)OperationCode.Leave, null, true, 0);
  916. }
  917.  
  918. public override bool OpRaiseEvent(byte eventCode, object customEventContent, bool sendReliable, RaiseEventOptions raiseEventOptions)
  919. {
  920. if (PhotonNetwork.offlineMode)
  921. {
  922. return false;
  923. }
  924.  
  925. return base.OpRaiseEvent(eventCode, customEventContent, sendReliable, raiseEventOptions);
  926. }
  927.  
  928. #endregion
  929.  
  930. #region Implementation of IPhotonPeerListener
  931.  
  932. public void DebugReturn(DebugLevel level, string message)
  933. {
  934. this.externalListener.DebugReturn(level, message);
  935. }
  936.  
  937. public void OnOperationResponse(OperationResponse operationResponse)
  938. {
  939. if (PhotonNetwork.networkingPeer.State == global::PeerState.Disconnecting)
  940. {
  941. if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
  942. Debug.Log("OperationResponse ignored while disconnecting. Code: " + operationResponse.OperationCode);
  943.  
  944. return;
  945. }
  946.  
  947. // extra logging for error debugging (helping developers with a bit of automated analysis)
  948. if (operationResponse.ReturnCode == 0)
  949. {
  950. if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
  951. Debug.Log(operationResponse.ToString());
  952. }
  953. else
  954. {
  955. if (operationResponse.ReturnCode == ErrorCode.OperationNotAllowedInCurrentState)
  956. {
  957. Debug.LogError("Operation " + operationResponse.OperationCode + " could not be executed yet. Wait for state JoinedLobby or ConnectedToMaster and their respective callbacks before calling OPs.");
  958. }
  959. else if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
  960. {
  961. Debug.LogError("Operation failed: " + operationResponse.ToStringFull() + " Server: " + this.server);
  962. }
  963. }
  964.  
  965. // use the "secret" or "token" whenever we get it. doesn't really matter if it's in AuthResponse.
  966. if (operationResponse.Parameters.ContainsKey(ParameterCode.Secret))
  967. {
  968. if (this.CustomAuthenticationValues == null)
  969. {
  970. this.CustomAuthenticationValues = new AuthenticationValues();
  971. // this.DebugReturn(DebugLevel.ERROR, "Server returned secret. Created CustomAuthenticationValues.");
  972. }
  973.  
  974. this.CustomAuthenticationValues.Secret = operationResponse[ParameterCode.Secret] as string;
  975. }
  976.  
  977. switch (operationResponse.OperationCode)
  978. {
  979. case OperationCode.Authenticate:
  980. {
  981. // PeerState oldState = this.State;
  982.  
  983. if (operationResponse.ReturnCode != 0)
  984. {
  985. if (operationResponse.ReturnCode == ErrorCode.InvalidOperationCode)
  986. {
  987. Debug.LogError(string.Format("If you host Photon yourself, make sure to start the 'Instance LoadBalancing' "+ this.ServerAddress));
  988. }
  989. else if (operationResponse.ReturnCode == ErrorCode.InvalidAuthentication)
  990. {
  991. Debug.LogError(string.Format("The appId this client sent is unknown on the server (Cloud). Check settings. If using the Cloud, check account."));
  992. SendMonoMessage(PhotonNetworkingMessage.OnFailedToConnectToPhoton, DisconnectCause.InvalidAuthentication);
  993. }
  994. else if (operationResponse.ReturnCode == ErrorCode.CustomAuthenticationFailed)
  995. {
  996. Debug.LogError(string.Format("Custom Authentication failed (either due to user-input or configuration or AuthParameter string format). Calling: OnCustomAuthenticationFailed()"));
  997. SendMonoMessage(PhotonNetworkingMessage.OnCustomAuthenticationFailed, operationResponse.DebugMessage);
  998. }
  999. else
  1000. {
  1001. Debug.LogError(string.Format("Authentication failed: '{0}' Code: {1}", operationResponse.DebugMessage, operationResponse.ReturnCode));
  1002. }
  1003.  
  1004. this.State = global::PeerState.Disconnecting;
  1005. this.Disconnect();
  1006.  
  1007. if (operationResponse.ReturnCode == ErrorCode.MaxCcuReached)
  1008. {
  1009. if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
  1010. Debug.LogWarning(string.Format("Currently, the limit of users is reached for this title. Try again later. Disconnecting"));
  1011. SendMonoMessage(PhotonNetworkingMessage.OnPhotonMaxCccuReached);
  1012. SendMonoMessage(PhotonNetworkingMessage.OnConnectionFail, DisconnectCause.MaxCcuReached);
  1013. }
  1014. else if (operationResponse.ReturnCode == ErrorCode.InvalidRegion)
  1015. {
  1016. if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
  1017. Debug.LogError(string.Format("The used master server address is not available with the subscription currently used. Got to Photon Cloud Dashboard or change URL. Disconnecting."));
  1018. SendMonoMessage(PhotonNetworkingMessage.OnConnectionFail, DisconnectCause.InvalidRegion);
  1019. }
  1020. break;
  1021. }
  1022. else
  1023. {
  1024. if (this.server == ServerConnection.NameServer)
  1025. {
  1026. // on the NameServer, authenticate returns the MasterServer address for a region and we hop off to there
  1027. this.MasterServerAddress = operationResponse[ParameterCode.Address] as string;
  1028. this.DisconnectToReconnect();
  1029. this.Connect(this.MasterServerAddress, ServerConnection.MasterServer);
  1030. }
  1031. else if (this.server == ServerConnection.MasterServer)
  1032. {
  1033. if (PhotonNetwork.autoJoinLobby)
  1034. {
  1035. this.State = global::PeerState.Authenticated;
  1036. this.OpJoinLobby(this.lobby);
  1037. }
  1038. else
  1039. {
  1040. this.State = global::PeerState.ConnectedToMaster;
  1041. NetworkingPeer.SendMonoMessage(PhotonNetworkingMessage.OnConnectedToMaster);
  1042. }
  1043. }
  1044. else if (this.server == ServerConnection.GameServer)
  1045. {
  1046. this.State = global::PeerState.Joining;
  1047.  
  1048. if (this.mLastJoinType == JoinType.JoinGame || this.mLastJoinType == JoinType.JoinRandomGame || this.mLastJoinType == JoinType.JoinOrCreateOnDemand)
  1049. {
  1050. // if we just "join" the game, do so. if we wanted to "create the room on demand", we have to send this to the game server as well.
  1051. this.OpJoinRoom(this.mRoomToGetInto.name, this.mRoomOptionsForCreate, this.mRoomToEnterLobby, this.mLastJoinType == JoinType.JoinOrCreateOnDemand);
  1052. }
  1053. else if (this.mLastJoinType == JoinType.CreateGame)
  1054. {
  1055. // on the game server, we have to apply the room properties that were chosen for creation of the room, so we use this.mRoomToGetInto
  1056. this.OpCreateGame(this.mRoomToGetInto.name, this.mRoomOptionsForCreate, this.mRoomToEnterLobby);
  1057. }
  1058.  
  1059. break;
  1060. }
  1061. }
  1062. break;
  1063. }
  1064.  
  1065. case OperationCode.GetRegions:
  1066. this.AvailableRegions = operationResponse[ParameterCode.Region] as string[];
  1067. this.AvailableRegionsServers = operationResponse[ParameterCode.Address] as string[];
  1068. Debug.Log("GetRegions returned: " + operationResponse.ToStringFull());
  1069. break;
  1070.  
  1071. case OperationCode.CreateGame:
  1072. {
  1073. if (this.server == ServerConnection.GameServer)
  1074. {
  1075. this.GameEnteredOnGameServer(operationResponse);
  1076. }
  1077. else
  1078. {
  1079. if (operationResponse.ReturnCode != 0)
  1080. {
  1081. if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
  1082. Debug.LogWarning(string.Format("CreateRoom failed, client stays on masterserver: {0}.", operationResponse.ToStringFull()));
  1083.  
  1084. SendMonoMessage(PhotonNetworkingMessage.OnPhotonCreateRoomFailed);
  1085. break;
  1086. }
  1087.  
  1088. string gameID = (string) operationResponse[ParameterCode.RoomName];
  1089. if (!string.IsNullOrEmpty(gameID))
  1090. {
  1091. // is only sent by the server's response, if it has not been
  1092. // sent with the client's request before!
  1093. this.mRoomToGetInto.name = gameID;
  1094. }
  1095.  
  1096. this.mGameserver = (string)operationResponse[ParameterCode.Address];
  1097. this.DisconnectToReconnect();
  1098. }
  1099.  
  1100. break;
  1101. }
  1102.  
  1103. case OperationCode.JoinGame:
  1104. {
  1105. if (this.server != ServerConnection.GameServer)
  1106. {
  1107. if (operationResponse.ReturnCode != 0)
  1108. {
  1109. if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
  1110. Debug.Log(string.Format("JoinRoom failed (room maybe closed by now). Client stays on masterserver: {0}. State: {1}", operationResponse.ToStringFull(), this.State));
  1111.  
  1112. SendMonoMessage(PhotonNetworkingMessage.OnPhotonJoinRoomFailed);
  1113. break;
  1114. }
  1115.  
  1116. this.mGameserver = (string)operationResponse[ParameterCode.Address];
  1117. this.DisconnectToReconnect();
  1118. }
  1119. else
  1120. {
  1121. this.GameEnteredOnGameServer(operationResponse);
  1122. }
  1123.  
  1124. break;
  1125. }
  1126.  
  1127. case OperationCode.JoinRandomGame:
  1128. {
  1129. // happens only on master. on gameserver, this is a regular join (we don't need to find a random game again)
  1130. // the operation OpJoinRandom either fails (with returncode 8) or returns game-to-join information
  1131. if (operationResponse.ReturnCode != 0)
  1132. {
  1133. if (operationResponse.ReturnCode == ErrorCode.NoRandomMatchFound)
  1134. {
  1135. if (PhotonNetwork.logLevel >= PhotonLogLevel.Full)
  1136. Debug.Log("JoinRandom failed: No open game. Calling: OnPhotonRandomJoinFailed() and staying on master server.");
  1137. }
  1138. else if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
  1139. {
  1140. Debug.LogWarning(string.Format("JoinRandom failed: {0}.", operationResponse.ToStringFull()));
  1141. }
  1142.  
  1143. SendMonoMessage(PhotonNetworkingMessage.OnPhotonRandomJoinFailed);
  1144. break;
  1145. }
  1146.  
  1147. string roomName = (string)operationResponse[ParameterCode.RoomName];
  1148. this.mRoomToGetInto.name = roomName;
  1149. this.mGameserver = (string)operationResponse[ParameterCode.Address];
  1150. this.DisconnectToReconnect();
  1151. break;
  1152. }
  1153.  
  1154. case OperationCode.JoinLobby:
  1155. this.State = global::PeerState.JoinedLobby;
  1156. this.insideLobby = true;
  1157. SendMonoMessage(PhotonNetworkingMessage.OnJoinedLobby);
  1158.  
  1159. // this.mListener.joinLobbyReturn();
  1160. break;
  1161. case OperationCode.LeaveLobby:
  1162. this.State = global::PeerState.Authenticated;
  1163. this.LeftLobbyCleanup(); // will set insideLobby = false
  1164. break;
  1165.  
  1166. case OperationCode.Leave:
  1167. this.DisconnectToReconnect();
  1168. break;
  1169.  
  1170. case OperationCode.SetProperties:
  1171. // this.mListener.setPropertiesReturn(returnCode, debugMsg);
  1172. break;
  1173.  
  1174. case OperationCode.GetProperties:
  1175. {
  1176. Hashtable actorProperties = (Hashtable)operationResponse[ParameterCode.PlayerProperties];
  1177. Hashtable gameProperties = (Hashtable)operationResponse[ParameterCode.GameProperties];
  1178. this.ReadoutProperties(gameProperties, actorProperties, 0);
  1179.  
  1180. // RemoveByteTypedPropertyKeys(actorProperties, false);
  1181. // RemoveByteTypedPropertyKeys(gameProperties, false);
  1182. // this.mListener.getPropertiesReturn(gameProperties, actorProperties, returnCode, debugMsg);
  1183. break;
  1184. }
  1185.  
  1186. case OperationCode.RaiseEvent:
  1187. // this usually doesn't give us a result. only if the caching is affected the server will send one.
  1188. break;
  1189.  
  1190. case OperationCode.FindFriends:
  1191. bool[] onlineList = operationResponse[ParameterCode.FindFriendsResponseOnlineList] as bool[];
  1192. string[] roomList = operationResponse[ParameterCode.FindFriendsResponseRoomIdList] as string[];
  1193.  
  1194. if (onlineList != null && roomList != null && PhotonNetwork.Friends != null && onlineList.Length == PhotonNetwork.Friends.Count)
  1195. {
  1196. for (int index = 0; index < PhotonNetwork.Friends.Count; index++)
  1197. {
  1198. FriendInfo friend = PhotonNetwork.Friends[index];
  1199. friend.Room = roomList[index];
  1200. friend.IsOnline = onlineList[index];
  1201. }
  1202. }
  1203. else
  1204. {
  1205. // any of the lists is null and shouldn't. print a error
  1206. Debug.LogError("FindFriends failed to apply the result, as a required value wasn't provided or the friend list length differed from result.");
  1207. }
  1208.  
  1209. this.isFetchingFriends = false;
  1210. this.friendListTimestamp = Environment.TickCount;
  1211. if (this.friendListTimestamp == 0)
  1212. {
  1213. this.friendListTimestamp = 1; // makes sure the timestamp is not accidentally 0
  1214. }
  1215.  
  1216. SendMonoMessage(PhotonNetworkingMessage.OnUpdatedFriendList);
  1217. break;
  1218.  
  1219. default:
  1220. Debug.LogWarning(string.Format("OperationResponse unhandled: {0}", operationResponse.ToString()));
  1221. break;
  1222. }
  1223.  
  1224. this.externalListener.OnOperationResponse(operationResponse);
  1225. }
  1226.  
  1227. /// <summary>
  1228. /// Age of friend list info (in milliseconds). It's 0 until a friend list is fetched.
  1229. /// </summary>
  1230. protected internal int FriendsListAge { get { return (this.isFetchingFriends || this.friendListTimestamp == 0) ? 0 : Environment.TickCount - this.friendListTimestamp; } }
  1231.  
  1232. private int friendListTimestamp;
  1233.  
  1234. /// <summary>Internal flag to know if the client currently fetches a friend list.</summary>
  1235. private bool isFetchingFriends;
  1236.  
  1237. /// <summary>
  1238. /// Request the rooms and online status for a list of friends. All client must set a unique username via PlayerName property. The result is available in this.Friends.
  1239. /// </summary>
  1240. /// <remarks>
  1241. /// Used on Master Server to find the rooms played by a selected list of users.
  1242. /// The result will be mapped to LoadBalancingClient.Friends when available.
  1243. /// The list is initialized by OpFindFriends on first use (before that, it is null).
  1244. ///
  1245. /// Users identify themselves by setting a PlayerName in the LoadBalancingClient instance.
  1246. /// This in turn will send the name in OpAuthenticate after each connect (to master and game servers).
  1247. /// Note: Changing a player's name doesn't make sense when using a friend list.
  1248. ///
  1249. /// The list of usernames must be fetched from some other source (not provided by Photon).
  1250. ///
  1251. ///
  1252. /// Internal:
  1253. /// The server response includes 2 arrays of info (each index matching a friend from the request):
  1254. /// ParameterCode.FindFriendsResponseOnlineList = bool[] of online states
  1255. /// ParameterCode.FindFriendsResponseRoomIdList = string[] of room names (empty string if not in a room)
  1256. /// </remarks>
  1257. /// <param name="friendsToFind">Array of friend's names (make sure they are unique).</param>
  1258. /// <returns>If the operation could be sent (requires connection, only one request is allowed at any time). Always false in offline mode.</returns>
  1259. public override bool OpFindFriends(string[] friendsToFind)
  1260. {
  1261. if (this.isFetchingFriends)
  1262. {
  1263. return false; // fetching friends currently, so don't do it again (avoid changing the list while fetching friends)
  1264. }
  1265.  
  1266. this.isFetchingFriends = true;
  1267.  
  1268. PhotonNetwork.Friends = new List<FriendInfo>(friendsToFind.Length);
  1269. foreach (string name in friendsToFind)
  1270. {
  1271. PhotonNetwork.Friends.Add(new FriendInfo() { Name = name });
  1272. }
  1273.  
  1274. return base.OpFindFriends(friendsToFind);
  1275. }
  1276.  
  1277. public void OnStatusChanged(StatusCode statusCode)
  1278. {
  1279. if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
  1280. Debug.Log(string.Format("OnStatusChanged: {0}", statusCode.ToString()));
  1281.  
  1282. switch (statusCode)
  1283. {
  1284. case StatusCode.Connect:
  1285. if (this.State == global::PeerState.ConnectingToNameServer)
  1286. {
  1287. if (PhotonNetwork.logLevel >= PhotonLogLevel.Full)
  1288. Debug.Log("Connected to NameServer.");
  1289.  
  1290. this.server = ServerConnection.NameServer;
  1291. this.CustomAuthenticationValues.Secret = null; // when connecting to NameServer, invalidate any auth values
  1292. }
  1293.  
  1294. if (this.State == global::PeerState.ConnectingToGameserver)
  1295. {
  1296. if (PhotonNetwork.logLevel >= PhotonLogLevel.Full)
  1297. Debug.Log("Connected to gameserver.");
  1298.  
  1299. this.server = ServerConnection.GameServer;
  1300. this.State = global::PeerState.ConnectedToGameserver;
  1301. }
  1302.  
  1303. if (this.State == global::PeerState.ConnectingToMasterserver)
  1304. {
  1305. if (PhotonNetwork.logLevel >= PhotonLogLevel.Full)
  1306. Debug.Log("Connected to masterserver.");
  1307.  
  1308. this.server = ServerConnection.MasterServer;
  1309. this.State = global::PeerState.ConnectedToMaster;
  1310.  
  1311. if (this.IsInitialConnect)
  1312. {
  1313. SendMonoMessage(PhotonNetworkingMessage.OnConnectedToPhoton);
  1314. }
  1315. }
  1316.  
  1317. this.EstablishEncryption(); // always enable encryption
  1318. this.IsInitialConnect = false; // after handling potential initial-connect issues with special messages, we are now sure we can reach a server
  1319.  
  1320. if (this.IsAuthorizeSecretAvailable)
  1321. {
  1322. // if we have a token we don't have to wait for encryption (it is encrypted anyways, so encryption is just optional later on)
  1323. this.didAuthenticate = this.OpAuthenticate(this.mAppId, this.mAppVersion, this.PlayerName, this.CustomAuthenticationValues, this.CloudRegion);
  1324. if (this.didAuthenticate)
  1325. {
  1326. this.State = global::PeerState.Authenticating;
  1327. }
  1328. }
  1329. break;
  1330.  
  1331. case StatusCode.EncryptionEstablished:
  1332. // on nameserver, the "process" is stopped here, so the developer/game can either get regions or authenticate with a specific region
  1333. if (this.server == ServerConnection.NameServer)
  1334. {
  1335. this.State = global::PeerState.ConnectedToNameServer;
  1336.  
  1337. if (!this.didAuthenticate && string.IsNullOrEmpty(this.CloudRegion))
  1338. {
  1339. // this client is not setup to connect to a default region. find out which regions there are!
  1340. this.OpGetRegions(this.mAppId);
  1341. }
  1342. }
  1343.  
  1344. // we might need to authenticate automatically now, so the client can do anything at all
  1345. if (!this.didAuthenticate && (!this.IsUsingNameServer || this.CloudRegion != null))
  1346. {
  1347. // once encryption is availble, the client should send one (secure) authenticate. it includes the AppId (which identifies your app on the Photon Cloud)
  1348. this.didAuthenticate = this.OpAuthenticate(this.mAppId, this.mAppVersion, this.PlayerName, this.CustomAuthenticationValues, this.CloudRegion);
  1349. if (this.didAuthenticate)
  1350. {
  1351. this.State = global::PeerState.Authenticating;
  1352. }
  1353. }
  1354. break;
  1355.  
  1356. case StatusCode.EncryptionFailedToEstablish:
  1357. Debug.LogError("Encryption wasn't established: " + statusCode + ". Going to authenticate anyways.");
  1358. this.OpAuthenticate(this.mAppId, this.mAppVersion, this.PlayerName, this.CustomAuthenticationValues, this.CloudRegion); // TODO: check if there are alternatives
  1359. break;
  1360.  
  1361. case StatusCode.Disconnect:
  1362. this.didAuthenticate = false;
  1363. this.isFetchingFriends = false;
  1364. if (this.State == global::PeerState.DisconnectingFromMasterserver)
  1365. {
  1366. if (this.Connect(this.mGameserver, ServerConnection.GameServer))
  1367. {
  1368. this.State = global::PeerState.ConnectingToGameserver;
  1369. }
  1370. }
  1371. else if (this.State == global::PeerState.DisconnectingFromGameserver)
  1372. {
  1373. if (this.Connect(this.MasterServerAddress, ServerConnection.MasterServer))
  1374. {
  1375. this.State = global::PeerState.ConnectingToMasterserver;
  1376. }
  1377. }
  1378. else
  1379. {
  1380. this.LeftRoomCleanup();
  1381. if (this.CustomAuthenticationValues != null)
  1382. {
  1383. this.CustomAuthenticationValues.Secret = null; // invalidate any custom auth secrets
  1384. }
  1385. this.State = global::PeerState.PeerCreated;
  1386. SendMonoMessage(PhotonNetworkingMessage.OnDisconnectedFromPhoton);
  1387. }
  1388. break;
  1389.  
  1390. case StatusCode.SecurityExceptionOnConnect:
  1391. case StatusCode.ExceptionOnConnect:
  1392. this.State = global::PeerState.PeerCreated;
  1393. if (this.CustomAuthenticationValues != null)
  1394. {
  1395. this.CustomAuthenticationValues.Secret = null; // invalidate any custom auth secrets
  1396. }
  1397.  
  1398. DisconnectCause cause = (DisconnectCause)statusCode;
  1399. SendMonoMessage(PhotonNetworkingMessage.OnFailedToConnectToPhoton, cause);
  1400. break;
  1401.  
  1402. case StatusCode.Exception:
  1403. if (this.IsInitialConnect)
  1404. {
  1405. Debug.LogError("Exception while connecting to: " + this.ServerAddress + ". Check if the server is available.");
  1406. if (this.ServerAddress == null || this.ServerAddress.StartsWith("127.0.0.1"))
  1407. {
  1408. Debug.LogWarning("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.");
  1409. if (this.ServerAddress == this.mGameserver)
  1410. {
  1411. Debug.LogWarning("This might be a misconfiguration in the game server config. You need to edit it to a (public) address.");
  1412. }
  1413. }
  1414.  
  1415. this.State = global::PeerState.PeerCreated;
  1416. cause = (DisconnectCause)statusCode;
  1417. SendMonoMessage(PhotonNetworkingMessage.OnFailedToConnectToPhoton, cause);
  1418. }
  1419. else
  1420. {
  1421. this.State = global::PeerState.PeerCreated;
  1422.  
  1423. cause = (DisconnectCause)statusCode;
  1424. SendMonoMessage(PhotonNetworkingMessage.OnConnectionFail, cause);
  1425. }
  1426.  
  1427. this.Disconnect();
  1428. break;
  1429.  
  1430. case StatusCode.TimeoutDisconnect:
  1431. case StatusCode.ExceptionOnReceive:
  1432. case StatusCode.DisconnectByServer:
  1433. case StatusCode.DisconnectByServerLogic:
  1434. case StatusCode.DisconnectByServerUserLimit:
  1435. if (this.IsInitialConnect)
  1436. {
  1437. Debug.LogWarning(statusCode + " while connecting to: " + this.ServerAddress + ". Check if the server is available.");
  1438.  
  1439. cause = (DisconnectCause)statusCode;
  1440. SendMonoMessage(PhotonNetworkingMessage.OnFailedToConnectToPhoton, cause);
  1441. }
  1442. else
  1443. {
  1444. cause = (DisconnectCause)statusCode;
  1445. SendMonoMessage(PhotonNetworkingMessage.OnConnectionFail, cause);
  1446. }
  1447. if (this.CustomAuthenticationValues != null)
  1448. {
  1449. this.CustomAuthenticationValues.Secret = null; // invalidate any custom auth secrets
  1450. }
  1451.  
  1452. this.Disconnect();
  1453. break;
  1454.  
  1455. case StatusCode.SendError:
  1456. // this.mListener.clientErrorReturn(statusCode);
  1457. break;
  1458.  
  1459. case StatusCode.QueueOutgoingReliableWarning:
  1460. case StatusCode.QueueOutgoingUnreliableWarning:
  1461. case StatusCode.QueueOutgoingAcksWarning:
  1462. case StatusCode.QueueSentWarning:
  1463.  
  1464. // this.mListener.warningReturn(statusCode);
  1465. break;
  1466.  
  1467. // // TCP "routing" is an option of Photon that's not currently needed (or supported) by PUN
  1468. //case StatusCode.TcpRouterResponseOk:
  1469. // break;
  1470. //case StatusCode.TcpRouterResponseEndpointUnknown:
  1471. //case StatusCode.TcpRouterResponseNodeIdUnknown:
  1472. //case StatusCode.TcpRouterResponseNodeNotReady:
  1473.  
  1474. // this.DebugReturn(DebugLevel.ERROR, "Unexpected router response: " + statusCode);
  1475. // break;
  1476.  
  1477. default:
  1478.  
  1479. // this.mListener.serverErrorReturn(statusCode.value());
  1480. Debug.LogError("Received unknown status code: " + statusCode);
  1481. break;
  1482. }
  1483.  
  1484. this.externalListener.OnStatusChanged(statusCode);
  1485. }
  1486.  
  1487. public void OnEvent(EventData photonEvent)
  1488. {
  1489. if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
  1490. Debug.Log(string.Format("OnEvent: {0}", photonEvent.ToString()));
  1491.  
  1492. int actorNr = -1;
  1493. PhotonPlayer originatingPlayer = null;
  1494.  
  1495. if (photonEvent.Parameters.ContainsKey(ParameterCode.ActorNr))
  1496. {
  1497. actorNr = (int)photonEvent[ParameterCode.ActorNr];
  1498. if (this.mActors.ContainsKey(actorNr))
  1499. {
  1500. originatingPlayer = (PhotonPlayer)this.mActors[actorNr];
  1501. }
  1502. //else
  1503. //{
  1504. // // the actor sending this event is not in actorlist. this is usually no problem
  1505. // if (photonEvent.Code != (byte)LiteOpCode.Join)
  1506. // {
  1507. // Debug.LogWarning("Received event, but we do not have this actor: " + actorNr);
  1508. // }
  1509. //}
  1510. }
  1511.  
  1512. switch (photonEvent.Code)
  1513. {
  1514. case EventCode.GameList:
  1515. {
  1516. this.mGameList = new Dictionary<string, RoomInfo>();
  1517. Hashtable games = (Hashtable)photonEvent[ParameterCode.GameList];
  1518. foreach (DictionaryEntry game in games)
  1519. {
  1520. string gameName = (string)game.Key;
  1521. this.mGameList[gameName] = new RoomInfo(gameName, (Hashtable)game.Value);
  1522. }
  1523. mGameListCopy = new RoomInfo[mGameList.Count];
  1524. mGameList.Values.CopyTo(mGameListCopy, 0);
  1525. SendMonoMessage(PhotonNetworkingMessage.OnReceivedRoomListUpdate);
  1526. break;
  1527. }
  1528.  
  1529. case EventCode.GameListUpdate:
  1530. {
  1531. Hashtable games = (Hashtable)photonEvent[ParameterCode.GameList];
  1532. foreach (DictionaryEntry room in games)
  1533. {
  1534. string gameName = (string)room.Key;
  1535. RoomInfo game = new RoomInfo(gameName, (Hashtable)room.Value);
  1536. if (game.removedFromList)
  1537. {
  1538. this.mGameList.Remove(gameName);
  1539. }
  1540. else
  1541. {
  1542. this.mGameList[gameName] = game;
  1543. }
  1544. }
  1545. this.mGameListCopy = new RoomInfo[this.mGameList.Count];
  1546. this.mGameList.Values.CopyTo(this.mGameListCopy, 0);
  1547. SendMonoMessage(PhotonNetworkingMessage.OnReceivedRoomListUpdate);
  1548. break;
  1549. }
  1550.  
  1551. case EventCode.QueueState:
  1552. if (photonEvent.Parameters.ContainsKey(ParameterCode.Position))
  1553. {
  1554. this.mQueuePosition = (int)photonEvent[ParameterCode.Position];
  1555. }
  1556. else
  1557. {
  1558. Debug.LogError("Event QueueState must contain position!");
  1559. }
  1560.  
  1561. if (this.mQueuePosition == 0)
  1562. {
  1563. // once we're un-queued, let's join the lobby or simply be "connected to master"
  1564. if (PhotonNetwork.autoJoinLobby)
  1565. {
  1566. this.State = global::PeerState.Authenticated;
  1567. this.OpJoinLobby(this.lobby);
  1568. }
  1569. else
  1570. {
  1571. this.State = global::PeerState.ConnectedToMaster;
  1572. NetworkingPeer.SendMonoMessage(PhotonNetworkingMessage.OnConnectedToMaster);
  1573. }
  1574. }
  1575.  
  1576. break;
  1577.  
  1578. case EventCode.AppStats:
  1579. // Debug.LogInfo("Received stats!");
  1580. this.mPlayersInRoomsCount = (int)photonEvent[ParameterCode.PeerCount];
  1581. this.mPlayersOnMasterCount = (int)photonEvent[ParameterCode.MasterPeerCount];
  1582. this.mGameCount = (int)photonEvent[ParameterCode.GameCount];
  1583. break;
  1584.  
  1585. case EventCode.Join:
  1586. // actorNr is fetched out of event above
  1587. Hashtable actorProperties = (Hashtable)photonEvent[ParameterCode.PlayerProperties];
  1588. if (originatingPlayer == null)
  1589. {
  1590. bool isLocal = this.mLocalActor.ID == actorNr;
  1591. this.AddNewPlayer(actorNr, new PhotonPlayer(isLocal, actorNr, actorProperties));
  1592. this.ResetPhotonViewsOnSerialize(); // This sets the correct OnSerializeState for Reliable OnSerialize
  1593. }
  1594.  
  1595. if (actorNr == this.mLocalActor.ID)
  1596. {
  1597. // in this player's 'own' join event, we get a complete list of players in the room, so check if we know all players
  1598. int[] actorsInRoom = (int[])photonEvent[ParameterCode.ActorList];
  1599. foreach (int actorNrToCheck in actorsInRoom)
  1600. {
  1601. if (this.mLocalActor.ID != actorNrToCheck && !this.mActors.ContainsKey(actorNrToCheck))
  1602. {
  1603. this.AddNewPlayer(actorNrToCheck, new PhotonPlayer(false, actorNrToCheck, string.Empty));
  1604. }
  1605. }
  1606.  
  1607. // joinWithCreateOnDemand can turn an OpJoin into creating the room. Then actorNumber is 1 and callback: OnCreatedRoom()
  1608. if (this.mLastJoinType == JoinType.JoinOrCreateOnDemand && this.mLocalActor.ID == 1)
  1609. {
  1610. SendMonoMessage(PhotonNetworkingMessage.OnCreatedRoom);
  1611. }
  1612. SendMonoMessage(PhotonNetworkingMessage.OnJoinedRoom); //Always send OnJoinedRoom
  1613.  
  1614. }
  1615. else
  1616. {
  1617. SendMonoMessage(PhotonNetworkingMessage.OnPhotonPlayerConnected, this.mActors[actorNr]);
  1618. }
  1619. break;
  1620.  
  1621. case EventCode.Leave:
  1622. this.HandleEventLeave(actorNr);
  1623. break;
  1624.  
  1625. case EventCode.PropertiesChanged:
  1626. int targetActorNr = (int)photonEvent[ParameterCode.TargetActorNr];
  1627. Hashtable gameProperties = null;
  1628. Hashtable actorProps = null;
  1629. if (targetActorNr == 0)
  1630. {
  1631. gameProperties = (Hashtable)photonEvent[ParameterCode.Properties];
  1632. }
  1633. else
  1634. {
  1635. actorProps = (Hashtable)photonEvent[ParameterCode.Properties];
  1636. }
  1637.  
  1638. this.ReadoutProperties(gameProperties, actorProps, targetActorNr);
  1639. break;
  1640.  
  1641. case PunEvent.RPC:
  1642. //ts: each event now contains a single RPC. execute this
  1643. this.ExecuteRPC(photonEvent[ParameterCode.Data] as Hashtable, originatingPlayer);
  1644. break;
  1645.  
  1646. case PunEvent.SendSerialize:
  1647. case PunEvent.SendSerializeReliable:
  1648. Hashtable serializeData = (Hashtable)photonEvent[ParameterCode.Data];
  1649. //Debug.Log(serializeData.ToStringFull());
  1650.  
  1651. int remoteUpdateServerTimestamp = (int)serializeData[(byte)0];
  1652. short remoteLevelPrefix = -1;
  1653. short initialDataIndex = 1;
  1654. if (serializeData.ContainsKey((byte)1))
  1655. {
  1656. remoteLevelPrefix = (short)serializeData[(byte)1];
  1657. initialDataIndex = 2;
  1658. }
  1659.  
  1660. for (short s = initialDataIndex; s < serializeData.Count; s++)
  1661. {
  1662. this.OnSerializeRead(serializeData[s] as Hashtable, originatingPlayer, remoteUpdateServerTimestamp, remoteLevelPrefix);
  1663. }
  1664. break;
  1665.  
  1666. case PunEvent.Instantiation:
  1667. this.DoInstantiate((Hashtable)photonEvent[ParameterCode.Data], originatingPlayer, null);
  1668. break;
  1669.  
  1670. case PunEvent.CloseConnection:
  1671. // MasterClient "requests" a disconnection from us
  1672. if (originatingPlayer == null || !originatingPlayer.isMasterClient)
  1673. {
  1674. Debug.LogError("Error: Someone else(" + originatingPlayer + ") then the masterserver requests a disconnect!");
  1675. }
  1676. else
  1677. {
  1678. PhotonNetwork.LeaveRoom();
  1679. }
  1680.  
  1681. break;
  1682.  
  1683. case PunEvent.DestroyPlayer:
  1684. Hashtable evData = (Hashtable)photonEvent[ParameterCode.Data];
  1685. int targetPlayerId = (int)evData[(byte)0];
  1686. if (targetPlayerId >= 0)
  1687. {
  1688. this.DestroyPlayerObjects(targetPlayerId, true);
  1689. }
  1690. else
  1691. {
  1692. if (this.DebugOut >= DebugLevel.INFO) Debug.Log("Ev DestroyAll! By PlayerId: " + actorNr);
  1693. this.DestroyAll(true);
  1694. }
  1695. break;
  1696.  
  1697. case PunEvent.Destroy:
  1698. evData = (Hashtable)photonEvent[ParameterCode.Data];
  1699. int instantiationId = (int)evData[(byte)0];
  1700. // Debug.Log("Ev Destroy for viewId: " + instantiationId + " sent by owner: " + (instantiationId / PhotonNetwork.MAX_VIEW_IDS == actorNr) + " this client is owner: " + (instantiationId / PhotonNetwork.MAX_VIEW_IDS == this.mLocalActor.ID));
  1701.  
  1702. GameObject goToDestroyLocally = null;
  1703. this.instantiatedObjects.TryGetValue(instantiationId, out goToDestroyLocally);
  1704.  
  1705. if (goToDestroyLocally == null || originatingPlayer == null)
  1706. {
  1707. if (this.DebugOut >= DebugLevel.ERROR) Debug.LogError("Can't execute received Destroy request for view ID=" + instantiationId + " as GO can't be found. From player/actorNr: " + actorNr + " GO to destroy=" + goToDestroyLocally + " originating Player=" + originatingPlayer);
  1708. }
  1709. else
  1710. {
  1711. this.RemoveInstantiatedGO(goToDestroyLocally, true);
  1712. }
  1713.  
  1714. break;
  1715.  
  1716. case PunEvent.AssignMaster:
  1717. evData = (Hashtable)photonEvent[ParameterCode.Data];
  1718. int newMaster = (int)evData[(byte)1];
  1719. this.SetMasterClient(newMaster, false);
  1720. break;
  1721.  
  1722. default:
  1723. if (photonEvent.Code > 0 && photonEvent.Code < 200 && PhotonNetwork.OnEventCall != null)
  1724. {
  1725. object content = photonEvent[ParameterCode.Data];
  1726. PhotonNetwork.OnEventCall(photonEvent.Code, content, actorNr);
  1727. }
  1728. else
  1729. {
  1730. // actorNr might be null. it is fetched out of event on top of method
  1731. // Hashtable eventContent = (Hashtable) photonEvent[ParameterCode.Data];
  1732. // this.mListener.customEventAction(actorNr, eventCode, eventContent);
  1733. Debug.LogError("Error. Unhandled event: " + photonEvent);
  1734. }
  1735. break;
  1736. }
  1737.  
  1738. this.externalListener.OnEvent(photonEvent);
  1739. }
  1740.  
  1741. #endregion
  1742.  
  1743. public static void SendMonoMessage(PhotonNetworkingMessage methodString, params object[] parameters)
  1744. {
  1745. HashSet<GameObject> objectsToCall;
  1746. if (PhotonNetwork.SendMonoMessageTargets != null)
  1747. {
  1748. objectsToCall = PhotonNetwork.SendMonoMessageTargets;
  1749. }
  1750. else
  1751. {
  1752. objectsToCall = new HashSet<GameObject>();
  1753. Component[] targetComponents = (Component[])GameObject.FindObjectsOfType(typeof(MonoBehaviour));
  1754. for (int index = 0; index < targetComponents.Length; index++)
  1755. {
  1756. objectsToCall.Add(targetComponents[index].gameObject);
  1757. }
  1758. }
  1759.  
  1760. string methodName = methodString.ToString();
  1761. foreach (GameObject gameObject in objectsToCall)
  1762. {
  1763. if (parameters != null && parameters.Length == 1)
  1764. {
  1765. gameObject.SendMessage(methodName, parameters[0], SendMessageOptions.DontRequireReceiver);
  1766. }
  1767. else
  1768. {
  1769. gameObject.SendMessage(methodName, parameters, SendMessageOptions.DontRequireReceiver);
  1770. }
  1771. }
  1772. }
  1773.  
  1774. // PHOTONVIEW/RPC related
  1775.  
  1776. /// <summary>
  1777. /// Executes a received RPC event
  1778. /// </summary>
  1779. public void ExecuteRPC(Hashtable rpcData, PhotonPlayer sender)
  1780. {
  1781. if (rpcData == null || !rpcData.ContainsKey((byte)0))
  1782. {
  1783. Debug.LogError("Malformed RPC; this should never occur. Content: " + SupportClass.DictionaryToString(rpcData));
  1784. return;
  1785. }
  1786.  
  1787. // ts: updated with "flat" event data
  1788. int netViewID = (int)rpcData[(byte)0]; // LIMITS PHOTONVIEWS&PLAYERS
  1789. int otherSidePrefix = 0; // by default, the prefix is 0 (and this is not being sent)
  1790. if (rpcData.ContainsKey((byte)1))
  1791. {
  1792. otherSidePrefix = (short)rpcData[(byte)1];
  1793. }
  1794.  
  1795. string inMethodName;
  1796. if (rpcData.ContainsKey((byte)5))
  1797. {
  1798. int rpcIndex = (byte)rpcData[(byte)5]; // LIMITS RPC COUNT
  1799. if (rpcIndex > PhotonNetwork.PhotonServerSettings.RpcList.Count - 1)
  1800. {
  1801. Debug.LogError("Could not find RPC with index: " + rpcIndex + ". Going to ignore! Check PhotonServerSettings.RpcList");
  1802. return;
  1803. }
  1804. else
  1805. {
  1806. inMethodName = PhotonNetwork.PhotonServerSettings.RpcList[rpcIndex];
  1807. }
  1808. }
  1809. else
  1810. {
  1811. inMethodName = (string)rpcData[(byte)3];
  1812. }
  1813.  
  1814. object[] inMethodParameters = null;
  1815. if (rpcData.ContainsKey((byte)4))
  1816. {
  1817. inMethodParameters = (object[])rpcData[(byte)4];
  1818. }
  1819.  
  1820. if (inMethodParameters == null)
  1821. {
  1822. inMethodParameters = new object[0];
  1823. }
  1824.  
  1825. PhotonView photonNetview = this.GetPhotonView(netViewID);
  1826. if (photonNetview == null)
  1827. {
  1828. int viewOwnerId = netViewID/PhotonNetwork.MAX_VIEW_IDS;
  1829. bool owningPv = (viewOwnerId == this.mLocalActor.ID);
  1830. bool ownerSent = (viewOwnerId == sender.ID);
  1831.  
  1832. if (owningPv)
  1833. {
  1834. Debug.LogWarning("Received RPC \"" + inMethodName + "\" for viewID " + netViewID + " but this PhotonView does not exist! View was/is ours." + (ownerSent ? " Owner called." : " Remote called."));
  1835. }
  1836. else
  1837. {
  1838. Debug.LogError("Received RPC \"" + inMethodName + "\" for viewID " + netViewID + " but this PhotonView does not exist! Was remote PV." + (ownerSent ? " Owner called." : " Remote called."));
  1839. }
  1840. return;
  1841. }
  1842.  
  1843. if (photonNetview.prefix != otherSidePrefix)
  1844. {
  1845. Debug.LogError(
  1846. "Received RPC \"" + inMethodName + "\" on viewID " + netViewID + " with a prefix of " + otherSidePrefix
  1847. + ", our prefix is " + photonNetview.prefix + ". The RPC has been ignored.");
  1848. return;
  1849. }
  1850.  
  1851. // Get method name
  1852. if (inMethodName == string.Empty)
  1853. {
  1854. Debug.LogError("Malformed RPC; this should never occur. Content: " + SupportClass.DictionaryToString(rpcData));
  1855. return;
  1856. }
  1857.  
  1858. if (PhotonNetwork.logLevel >= PhotonLogLevel.Full)
  1859. Debug.Log("Received RPC: " + inMethodName);
  1860.  
  1861.  
  1862. // SetReceiving filtering
  1863. if (photonNetview.group != 0 && !allowedReceivingGroups.Contains(photonNetview.group))
  1864. {
  1865. return; // Ignore group
  1866. }
  1867.  
  1868. Type[] argTypes = new Type[0];
  1869. if (inMethodParameters.Length > 0)
  1870. {
  1871. argTypes = new Type[inMethodParameters.Length];
  1872. int i = 0;
  1873. for (int index = 0; index < inMethodParameters.Length; index++)
  1874. {
  1875. object objX = inMethodParameters[index];
  1876. if (objX == null)
  1877. {
  1878. argTypes[i] = null;
  1879. }
  1880. else
  1881. {
  1882. argTypes[i] = objX.GetType();
  1883. }
  1884.  
  1885. i++;
  1886. }
  1887. }
  1888.  
  1889. int receivers = 0;
  1890. int foundMethods = 0;
  1891. MonoBehaviour[] mbComponents = photonNetview.GetComponents<MonoBehaviour>(); // NOTE: we could possibly also cache MonoBehaviours per view?!
  1892. for (int componentsIndex = 0; componentsIndex < mbComponents.Length; componentsIndex++)
  1893. {
  1894. MonoBehaviour monob = mbComponents[componentsIndex];
  1895. if (monob == null)
  1896. {
  1897. Debug.LogError("ERROR You have missing MonoBehaviours on your gameobjects!");
  1898. continue;
  1899. }
  1900.  
  1901. Type type = monob.GetType();
  1902.  
  1903. // Get [RPC] methods from cache
  1904. List<MethodInfo> cachedRPCMethods = null;
  1905. if (this.monoRPCMethodsCache.ContainsKey(type))
  1906. {
  1907. cachedRPCMethods = this.monoRPCMethodsCache[type];
  1908. }
  1909.  
  1910. if (cachedRPCMethods == null)
  1911. {
  1912. List<MethodInfo> entries = SupportClass.GetMethods(type, typeof(RPC));
  1913.  
  1914. this.monoRPCMethodsCache[type] = entries;
  1915. cachedRPCMethods = entries;
  1916. }
  1917.  
  1918. if (cachedRPCMethods == null)
  1919. {
  1920. continue;
  1921. }
  1922.  
  1923. // Check cache for valid methodname+arguments
  1924. for (int index = 0; index < cachedRPCMethods.Count; index++)
  1925. {
  1926. MethodInfo mInfo = cachedRPCMethods[index];
  1927. if (mInfo.Name == inMethodName)
  1928. {
  1929. foundMethods++;
  1930. ParameterInfo[] pArray = mInfo.GetParameters();
  1931. if (pArray.Length == argTypes.Length)
  1932. {
  1933. // Normal, PhotonNetworkMessage left out
  1934. if (this.CheckTypeMatch(pArray, argTypes))
  1935. {
  1936. receivers++;
  1937. object result = mInfo.Invoke((object)monob, inMethodParameters);
  1938. if (mInfo.ReturnType == typeof(IEnumerator))
  1939. {
  1940. monob.StartCoroutine((IEnumerator)result);
  1941. }
  1942. }
  1943. }
  1944. else if ((pArray.Length - 1) == argTypes.Length)
  1945. {
  1946. // Check for PhotonNetworkMessage being the last
  1947. if (this.CheckTypeMatch(pArray, argTypes))
  1948. {
  1949. if (pArray[pArray.Length - 1].ParameterType == typeof(PhotonMessageInfo))
  1950. {
  1951. receivers++;
  1952.  
  1953. int sendTime = (int)rpcData[(byte)2];
  1954. object[] deParamsWithInfo = new object[inMethodParameters.Length + 1];
  1955. inMethodParameters.CopyTo(deParamsWithInfo, 0);
  1956. deParamsWithInfo[deParamsWithInfo.Length - 1] = new PhotonMessageInfo(sender, sendTime, photonNetview);
  1957.  
  1958. object result = mInfo.Invoke((object)monob, deParamsWithInfo);
  1959. if (mInfo.ReturnType == typeof(IEnumerator))
  1960. {
  1961. monob.StartCoroutine((IEnumerator)result);
  1962. }
  1963. }
  1964. }
  1965. }
  1966. else if (pArray.Length == 1 && pArray[0].ParameterType.IsArray)
  1967. {
  1968. receivers++;
  1969. object result = mInfo.Invoke((object)monob, new object[] { inMethodParameters });
  1970. if (mInfo.ReturnType == typeof(IEnumerator))
  1971. {
  1972. monob.StartCoroutine((IEnumerator)result);
  1973. }
  1974. }
  1975. }
  1976. }
  1977. }
  1978.  
  1979. // Error handling
  1980. if (receivers != 1)
  1981. {
  1982. string argsString = string.Empty;
  1983. for (int index = 0; index < argTypes.Length; index++)
  1984. {
  1985. Type ty = argTypes[index];
  1986. if (argsString != string.Empty)
  1987. {
  1988. argsString += ", ";
  1989. }
  1990.  
  1991. if (ty == null)
  1992. {
  1993. argsString += "null";
  1994. }
  1995. else
  1996. {
  1997. argsString += ty.Name;
  1998. }
  1999. }
  2000.  
  2001. if (receivers == 0)
  2002. {
  2003. if (foundMethods == 0)
  2004. {
  2005. Debug.LogError("PhotonView with ID " + netViewID + " has no method \"" + inMethodName + "\" marked with the [RPC](C#) or @RPC(JS) property! Args: " + argsString);
  2006. }
  2007. else
  2008. {
  2009. Debug.LogError("PhotonView with ID " + netViewID + " has no method \"" + inMethodName + "\" that takes " + argTypes.Length + " argument(s): " + argsString);
  2010. }
  2011. }
  2012. else
  2013. {
  2014. Debug.LogError("PhotonView with ID " + netViewID + " has " + receivers + " methods \"" + inMethodName + "\" that takes " + argTypes.Length + " argument(s): " + argsString + ". Should be just one?");
  2015. }
  2016. }
  2017. }
  2018.  
  2019. /// <summary>
  2020. /// Check if all types match with parameters. We can have more paramters then types (allow last RPC type to be different).
  2021. /// </summary>
  2022. /// <param name="methodParameters"></param>
  2023. /// <param name="callParameterTypes"></param>
  2024. /// <returns>If the types-array has matching parameters (of method) in the parameters array (which may be longer).</returns>
  2025. private bool CheckTypeMatch(ParameterInfo[] methodParameters, Type[] callParameterTypes)
  2026. {
  2027. if (methodParameters.Length < callParameterTypes.Length)
  2028. {
  2029. return false;
  2030. }
  2031.  
  2032. for (int index = 0; index < callParameterTypes.Length; index++)
  2033. {
  2034. Type type = methodParameters[index].ParameterType;
  2035. //todo: check metro type usage
  2036. if (callParameterTypes[index] != null && !type.Equals(callParameterTypes[index]))
  2037. {
  2038. return false;
  2039. }
  2040. }
  2041.  
  2042. return true;
  2043. }
  2044.  
  2045. internal Hashtable SendInstantiate(string prefabName, Vector3 position, Quaternion rotation, int group, int[] viewIDs, object[] data, bool isGlobalObject)
  2046. {
  2047. // first viewID is now also the gameobject's instantiateId
  2048. int instantiateId = viewIDs[0]; // LIMITS PHOTONVIEWS&PLAYERS
  2049.  
  2050. //TODO: reduce hashtable key usage by using a parameter array for the various values
  2051. Hashtable instantiateEvent = new Hashtable(); // This players info is sent via ActorID
  2052. instantiateEvent[(byte)0] = prefabName;
  2053.  
  2054. if (position != Vector3.zero)
  2055. {
  2056. instantiateEvent[(byte)1] = position;
  2057. }
  2058.  
  2059. if (rotation != Quaternion.identity)
  2060. {
  2061. instantiateEvent[(byte)2] = rotation;
  2062. }
  2063.  
  2064. if (group != 0)
  2065. {
  2066. instantiateEvent[(byte)3] = group;
  2067. }
  2068.  
  2069. // send the list of viewIDs only if there are more than one. else the instantiateId is the viewID
  2070. if (viewIDs.Length > 1)
  2071. {
  2072. instantiateEvent[(byte)4] = viewIDs; // LIMITS PHOTONVIEWS&PLAYERS
  2073. }
  2074.  
  2075. if (data != null)
  2076. {
  2077. instantiateEvent[(byte)5] = data;
  2078. }
  2079.  
  2080. if (this.currentLevelPrefix > 0)
  2081. {
  2082. instantiateEvent[(byte)8] = this.currentLevelPrefix; // photonview's / object's level prefix
  2083. }
  2084.  
  2085. instantiateEvent[(byte)6] = this.ServerTimeInMilliSeconds;
  2086. instantiateEvent[(byte)7] = instantiateId;
  2087.  
  2088.  
  2089. RaiseEventOptions options = new RaiseEventOptions();
  2090. options.CachingOption = (isGlobalObject) ? EventCaching.AddToRoomCacheGlobal : EventCaching.AddToRoomCache;
  2091.  
  2092. this.OpRaiseEvent(PunEvent.Instantiation, instantiateEvent, true, options);
  2093. return instantiateEvent;
  2094. }
  2095.  
  2096. internal GameObject DoInstantiate(Hashtable evData, PhotonPlayer photonPlayer, GameObject resourceGameObject)
  2097. {
  2098. // some values always present:
  2099. string prefabName = (string)evData[(byte)0];
  2100. int serverTime = (int)evData[(byte)6];
  2101. int instantiationId = (int)evData[(byte)7];
  2102.  
  2103. Vector3 position;
  2104. if (evData.ContainsKey((byte)1))
  2105. {
  2106. position = (Vector3)evData[(byte)1];
  2107. }
  2108. else
  2109. {
  2110. position = Vector3.zero;
  2111. }
  2112.  
  2113. Quaternion rotation = Quaternion.identity;
  2114. if (evData.ContainsKey((byte)2))
  2115. {
  2116. rotation = (Quaternion)evData[(byte)2];
  2117. }
  2118.  
  2119. int group = 0;
  2120. if (evData.ContainsKey((byte)3))
  2121. {
  2122. group = (int)evData[(byte)3];
  2123. }
  2124.  
  2125. short objLevelPrefix = 0;
  2126. if (evData.ContainsKey((byte)8))
  2127. {
  2128. objLevelPrefix = (short)evData[(byte)8];
  2129. }
  2130.  
  2131. int[] viewsIDs;
  2132. if (evData.ContainsKey((byte)4))
  2133. {
  2134. viewsIDs = (int[])evData[(byte)4];
  2135. }
  2136. else
  2137. {
  2138. viewsIDs = new int[1] { instantiationId };
  2139. }
  2140.  
  2141. object[] incomingInstantiationData;
  2142. if (evData.ContainsKey((byte)5))
  2143. {
  2144. incomingInstantiationData = (object[])evData[(byte)5];
  2145. }
  2146. else
  2147. {
  2148. incomingInstantiationData = null;
  2149. }
  2150.  
  2151. // SetReceiving filtering
  2152. if (group != 0 && !this.allowedReceivingGroups.Contains(group))
  2153. {
  2154. return null; // Ignore group
  2155. }
  2156.  
  2157. // load prefab, if it wasn't loaded before (calling methods might do this)
  2158. if (resourceGameObject == null)
  2159. {
  2160. if (!NetworkingPeer.UsePrefabCache || !NetworkingPeer.PrefabCache.TryGetValue(prefabName, out resourceGameObject))
  2161. {
  2162. resourceGameObject = (GameObject)Resources.Load(prefabName, typeof(GameObject));
  2163. if (NetworkingPeer.UsePrefabCache)
  2164. {
  2165. NetworkingPeer.PrefabCache.Add(prefabName, resourceGameObject);
  2166. }
  2167. }
  2168.  
  2169. if (resourceGameObject == null)
  2170. {
  2171. Debug.LogError("PhotonNetwork error: Could not Instantiate the prefab [" + prefabName + "]. Please verify you have this gameobject in a Resources folder.");
  2172. return null;
  2173. }
  2174. }
  2175.  
  2176. // now modify the loaded "blueprint" object before it becomes a part of the scene (by instantiating it)
  2177. PhotonView[] resourcePVs = resourceGameObject.GetPhotonViewsInChildren();
  2178. if (resourcePVs.Length != viewsIDs.Length)
  2179. {
  2180. throw new Exception("Error in Instantiation! The resource's PhotonView count is not the same as in incoming data.");
  2181. }
  2182.  
  2183. for (int i = 0; i < viewsIDs.Length; i++)
  2184. {
  2185. // NOTE instantiating the loaded resource will keep the viewID but would not copy instantiation data, so it's set below
  2186. // so we only set the viewID and instantiationId now. the instantiationData can be fetched
  2187. resourcePVs[i].viewID = viewsIDs[i];
  2188. resourcePVs[i].prefix = objLevelPrefix;
  2189. resourcePVs[i].instantiationId = instantiationId;
  2190. }
  2191.  
  2192. this.StoreInstantiationData(instantiationId, incomingInstantiationData);
  2193.  
  2194. // load the resource and set it's values before instantiating it:
  2195. GameObject go = (GameObject)GameObject.Instantiate(resourceGameObject, position, rotation);
  2196.  
  2197. for (int i = 0; i < viewsIDs.Length; i++)
  2198. {
  2199. // NOTE instantiating the loaded resource will keep the viewID but would not copy instantiation data, so it's set below
  2200. // so we only set the viewID and instantiationId now. the instantiationData can be fetched
  2201. resourcePVs[i].viewID = 0;
  2202. resourcePVs[i].prefix = -1;
  2203. resourcePVs[i].prefixBackup = -1;
  2204. resourcePVs[i].instantiationId = -1;
  2205. }
  2206.  
  2207. this.RemoveInstantiationData(instantiationId);
  2208.  
  2209. //TODO: remove this debug check
  2210. if (this.instantiatedObjects.ContainsKey(instantiationId))
  2211. {
  2212. GameObject knownGo = this.instantiatedObjects[instantiationId];
  2213. string pvaInfo = "";
  2214. PhotonView[] pva;
  2215.  
  2216. if (knownGo != null)
  2217. {
  2218. pva = knownGo.GetPhotonViewsInChildren();
  2219. foreach (PhotonView view in pva)
  2220. {
  2221. if (view == null) continue;
  2222. pvaInfo += view.ToString() + ", ";
  2223. }
  2224. }
  2225.  
  2226. Debug.LogError(string.Format("DoInstantiate re-defines a GameObject. Destroying old entry! New: '{0}' (instantiationID: {1}) Old: {3}. PhotonViews on old: {4}. instantiatedObjects.Count: {2}. PhotonNetwork.lastUsedViewSubId: {5} PhotonNetwork.lastUsedViewSubIdStatic: {6} this.photonViewList.Count {7}.)", go, instantiationId, this.instantiatedObjects.Count, knownGo, pvaInfo, PhotonNetwork.lastUsedViewSubId, PhotonNetwork.lastUsedViewSubIdStatic, this.photonViewList.Count));
  2227. //this.instantiatedObjects.Remove(instantiationId); // TODO: check if simple remove is ok in all cases. Maybe better Destroy!?
  2228. this.RemoveInstantiatedGO(knownGo, true);
  2229. }
  2230.  
  2231. this.instantiatedObjects.Add(instantiationId, go); //TODO check if instantiatedObjects is (still) needed
  2232.  
  2233. // Send mono event
  2234. // TOD move this callback and script-caching into a method! there should be one already...
  2235. object[] messageInfoParam = new object[1];
  2236. messageInfoParam[0] = new PhotonMessageInfo(photonPlayer, serverTime, null);
  2237.  
  2238. MonoBehaviour[] monos = go.GetComponentsInChildren<MonoBehaviour>();
  2239. for (int index = 0; index < monos.Length; index++)
  2240. {
  2241. MonoBehaviour mono = monos[index];
  2242. MethodInfo methodI;
  2243. if (NetworkingPeer.GetMethod(mono, PhotonNetworkingMessage.OnPhotonInstantiate.ToString(), out methodI))
  2244. {
  2245. object result = methodI.Invoke((object)mono, messageInfoParam);
  2246. if (methodI.ReturnType == typeof(System.Collections.IEnumerator))
  2247. {
  2248. mono.StartCoroutine((IEnumerator)result);
  2249. }
  2250. }
  2251. }
  2252.  
  2253. return go;
  2254. }
  2255.  
  2256. private Dictionary<int, object[]> tempInstantiationData = new Dictionary<int, object[]>();
  2257.  
  2258. private void StoreInstantiationData(int instantiationId, object[] instantiationData)
  2259. {
  2260. // Debug.Log("StoreInstantiationData() instantiationId: " + instantiationId + " tempInstantiationData.Count: " + tempInstantiationData.Count);
  2261. tempInstantiationData[instantiationId] = instantiationData;
  2262. }
  2263.  
  2264. public object[] FetchInstantiationData(int instantiationId)
  2265. {
  2266. object[] data = null;
  2267. if (instantiationId == 0)
  2268. {
  2269. return null;
  2270. }
  2271.  
  2272. tempInstantiationData.TryGetValue(instantiationId, out data);
  2273. // Debug.Log("FetchInstantiationData() instantiationId: " + instantiationId + " tempInstantiationData.Count: " + tempInstantiationData.Count);
  2274. return data;
  2275. }
  2276.  
  2277. private void RemoveInstantiationData(int instantiationId)
  2278. {
  2279. tempInstantiationData.Remove(instantiationId);
  2280. }
  2281.  
  2282.  
  2283. // Removes PhotonNetwork.Instantiate-ed objects.
  2284. // Removes all associated RPCs.
  2285. // Does not remove any manually assigned PhotonViews.
  2286. public void RemoveAllInstantiatedObjects()
  2287. {
  2288. GameObject[] instantiatedGoArray = new GameObject[this.instantiatedObjects.Count];
  2289. this.instantiatedObjects.Values.CopyTo(instantiatedGoArray, 0);
  2290.  
  2291. for (int index = 0; index < instantiatedGoArray.Length; index++)
  2292. {
  2293. GameObject go = instantiatedGoArray[index];
  2294. if (go == null)
  2295. {
  2296. continue;
  2297. }
  2298.  
  2299. this.RemoveInstantiatedGO(go, false);
  2300. }
  2301.  
  2302. if (this.instantiatedObjects.Count > 0)
  2303. {
  2304. Debug.LogError("RemoveAllInstantiatedObjects() this.instantiatedObjects.Count should be 0 by now.");
  2305. }
  2306.  
  2307. this.instantiatedObjects = new Dictionary<int, GameObject>();
  2308. }
  2309.  
  2310.  
  2311. /// <summary>
  2312. /// Destroys all Instantiates and RPCs locally and (if not localOnly) sends EvDestroy(player) and clears related events in the server buffer.
  2313. /// </summary>
  2314. public void DestroyPlayerObjects(int playerId, bool localOnly)
  2315. {
  2316. if (playerId <= 0)
  2317. {
  2318. Debug.LogError("Failed to Destroy objects of playerId: " + playerId);
  2319. return;
  2320. }
  2321.  
  2322. if (!localOnly)
  2323. {
  2324. // clean server's Instantiate and RPC buffers
  2325. this.OpRemoveFromServerInstantiationsOfPlayer(playerId);
  2326. this.OpCleanRpcBuffer(playerId);
  2327.  
  2328. // send Destroy(player) to anyone else
  2329. this.SendDestroyOfPlayer(playerId);
  2330. }
  2331.  
  2332. // locally cleaning up that player's objects
  2333. Queue<GameObject> playersGameObjects = new Queue<GameObject>();
  2334. int minPlayerInstantiateId = playerId * PhotonNetwork.MAX_VIEW_IDS;
  2335. int maxPlayerInstantiateId = minPlayerInstantiateId + PhotonNetwork.MAX_VIEW_IDS;
  2336.  
  2337. // find anything that's instantiated by affected player
  2338. foreach (var instantiateEntry in this.instantiatedObjects)
  2339. {
  2340. if (instantiateEntry.Key > minPlayerInstantiateId && instantiateEntry.Key < maxPlayerInstantiateId)
  2341. {
  2342. playersGameObjects.Enqueue(instantiateEntry.Value);
  2343. }
  2344. }
  2345.  
  2346. // any non-local work is already done, so with the list of that player's objects, we can clean up (locally only)
  2347. foreach (GameObject gameObject in playersGameObjects)
  2348. {
  2349. this.RemoveInstantiatedGO(gameObject, true);
  2350. }
  2351. }
  2352.  
  2353. public void DestroyAll(bool localOnly)
  2354. {
  2355. if (!localOnly)
  2356. {
  2357. this.OpRemoveCompleteCache();
  2358. this.SendDestroyOfAll();
  2359. }
  2360.  
  2361. this.LocalCleanupAnythingInstantiated(true);
  2362. }
  2363.  
  2364. /// <summary>Removes GameObject and the PhotonViews on it from local lists and optionally updates remotes. GameObject gets destroyed at end.</summary>
  2365. /// <remarks>
  2366. /// This method might fail and quit early due to several tests.
  2367. /// </remarks>
  2368. /// <param name="go">GameObject to cleanup.</param>
  2369. /// <param name="localOnly">For localOnly, tests of control are skipped and the server is not updated.</param>
  2370. public void RemoveInstantiatedGO(GameObject go, bool localOnly)
  2371. {
  2372. if (go == null)
  2373. {
  2374. Debug.LogError("Failed to 'network-remove' GameObject because it's null.");
  2375. return;
  2376. }
  2377.  
  2378. // Don't remove the GO if it doesn't have any PhotonView
  2379. PhotonView[] views = go.GetComponentsInChildren<PhotonView>();
  2380. if (views == null || views.Length <= 0)
  2381. {
  2382. Debug.LogError("Failed to 'network-remove' GameObject because has no PhotonView components: " + go);
  2383. return;
  2384. }
  2385.  
  2386. PhotonView viewZero = views[0];
  2387. int ownerActorNr = viewZero.OwnerActorNr; // owner is being checked via IsMine
  2388. int instantiationId = viewZero.instantiationId; // actual, live InstantiationIds start with 1 and go up
  2389.  
  2390.  
  2391. // Don't remove GOs that are owned by others (unless this is the master and the remote player left)
  2392. if (!localOnly)
  2393. {
  2394. if (!viewZero.isMine && (!this.mLocalActor.isMasterClient || mActors.ContainsKey(ownerActorNr)))
  2395. {
  2396. Debug.LogError("Failed to 'network-remove' GameObject. Client is neither owner nor masterClient taking over for owner who left: " + viewZero);
  2397. return;
  2398. }
  2399.  
  2400. // Don't remove the Instantiation from the server, if it doesn't have a proper ID
  2401. if (instantiationId < 1)
  2402. {
  2403. Debug.LogError("Failed to 'network-remove' GameObject because it is missing a valid InstantiationId on view: " + viewZero + ". Not Destroying GameObject or PhotonViews!");
  2404. return;
  2405. }
  2406. }
  2407.  
  2408.  
  2409. // cleanup instantiation (event and local list)
  2410. if (!localOnly)
  2411. {
  2412. this.ServerCleanInstantiateAndDestroy(instantiationId, ownerActorNr); // server cleaning
  2413. }
  2414. this.instantiatedObjects.Remove(instantiationId); // local
  2415.  
  2416.  
  2417. // cleanup PhotonViews and their RPCs events (if not localOnly)
  2418. for (int j = views.Length - 1; j >= 0; j--)
  2419. {
  2420. PhotonView view = views[j];
  2421. if (view == null)
  2422. {
  2423. continue;
  2424. }
  2425.  
  2426. // we only destroy/clean PhotonViews that were created by PhotonNetwork.Instantiate (and those have an instantiationId!)
  2427. if (view.instantiationId >= 1)
  2428. {
  2429. this.LocalCleanPhotonView(view);
  2430. }
  2431. if (!localOnly)
  2432. {
  2433. this.OpCleanRpcBuffer(view);
  2434. }
  2435. }
  2436.  
  2437. if (PhotonNetwork.logLevel >= PhotonLogLevel.Full)
  2438. Debug.Log("Network destroy Instantiated GO: " + go.name);
  2439.  
  2440. GameObject.Destroy(go);
  2441. }
  2442.  
  2443. /// <summary>
  2444. /// This returns -1 if the GO could not be found in list of instantiatedObjects.
  2445. /// </summary>
  2446. public int GetInstantiatedObjectsId(GameObject go)
  2447. {
  2448. int id = -1;
  2449. if (go == null)
  2450. {
  2451. Debug.LogError("GetInstantiatedObjectsId() for GO == null.");
  2452. return id;
  2453. }
  2454.  
  2455. PhotonView[] pvs = go.GetPhotonViewsInChildren();
  2456. if (pvs != null && pvs.Length > 0 && pvs[0] != null)
  2457. {
  2458. return pvs[0].instantiationId;
  2459. }
  2460.  
  2461. if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
  2462. UnityEngine.Debug.Log("GetInstantiatedObjectsId failed for GO: " + go);
  2463.  
  2464.  
  2465. return id;
  2466. }
  2467.  
  2468. /// <summary>
  2469. /// Removes an instantiation event from the server's cache. Needs id and actorNr of player who instantiated.
  2470. /// </summary>
  2471. private void ServerCleanInstantiateAndDestroy(int instantiateId, int actorNr)
  2472. {
  2473. Hashtable removeFilter = new Hashtable();
  2474. removeFilter[(byte)7] = instantiateId;
  2475.  
  2476. RaiseEventOptions options = new RaiseEventOptions() { CachingOption = EventCaching.RemoveFromRoomCache, TargetActors = new int[] { actorNr } };
  2477. this.OpRaiseEvent(PunEvent.Instantiation, removeFilter, true, options);
  2478. //this.OpRaiseEvent(PunEvent.Instantiation, removeFilter, true, 0, new int[] { actorNr }, EventCaching.RemoveFromRoomCache);
  2479.  
  2480. Hashtable evData = new Hashtable();
  2481. evData[(byte)0] = instantiateId;
  2482. this.OpRaiseEvent(PunEvent.Destroy, evData, true, null);
  2483. }
  2484.  
  2485. private void SendDestroyOfPlayer(int actorNr)
  2486. {
  2487. Hashtable evData = new Hashtable();
  2488. evData[(byte)0] = actorNr;
  2489.  
  2490. this.OpRaiseEvent(PunEvent.DestroyPlayer, evData, true, null);
  2491. //this.OpRaiseEvent(PunEvent.DestroyPlayer, evData, true, 0, EventCaching.DoNotCache, ReceiverGroup.Others);
  2492. }
  2493.  
  2494. private void SendDestroyOfAll()
  2495. {
  2496. Hashtable evData = new Hashtable();
  2497. evData[(byte)0] = -1;
  2498.  
  2499.  
  2500. this.OpRaiseEvent(PunEvent.DestroyPlayer, evData, true, null);
  2501. //this.OpRaiseEvent(PunEvent.DestroyPlayer, evData, true, 0, EventCaching.DoNotCache, ReceiverGroup.Others);
  2502. }
  2503.  
  2504. private void OpRemoveFromServerInstantiationsOfPlayer(int actorNr)
  2505. {
  2506. // removes all "Instantiation" events of player actorNr. this is not an event for anyone else
  2507. RaiseEventOptions options = new RaiseEventOptions() { CachingOption = EventCaching.RemoveFromRoomCache, TargetActors = new int[] { actorNr } };
  2508. this.OpRaiseEvent(PunEvent.Instantiation, null, true, options);
  2509. //this.OpRaiseEvent(PunEvent.Instantiation, null, true, 0, new int[] { actorNr }, EventCaching.RemoveFromRoomCache);
  2510. }
  2511.  
  2512. public void LocalCleanPhotonView(PhotonView view)
  2513. {
  2514. view.destroyedByPhotonNetworkOrQuit = true;
  2515. this.photonViewList.Remove(view.viewID);
  2516. }
  2517.  
  2518.  
  2519. public PhotonView GetPhotonView(int viewID)
  2520. {
  2521. PhotonView result = null;
  2522. this.photonViewList.TryGetValue(viewID, out result);
  2523.  
  2524. if (result == null)
  2525. {
  2526. PhotonView[] views = GameObject.FindObjectsOfType(typeof(PhotonView)) as PhotonView[];
  2527.  
  2528. foreach (PhotonView view in views)
  2529. {
  2530. if (view.viewID == viewID)
  2531. {
  2532. Debug.LogWarning("Had to lookup view that wasn't in dict: " + view);
  2533. return view;
  2534. }
  2535. }
  2536. }
  2537.  
  2538. return result;
  2539. }
  2540.  
  2541. public void RegisterPhotonView(PhotonView netView)
  2542. {
  2543. if (!Application.isPlaying)
  2544. {
  2545. this.photonViewList = new Dictionary<int, PhotonView>();
  2546. return;
  2547. }
  2548.  
  2549. if (netView.subId == 0)
  2550. {
  2551. // don't register views with subId 0 (not initialized). they register when a ID is assigned later on
  2552. // Debug.Log("PhotonView register is ignored, because subId is 0. No id assigned yet to: " + netView);
  2553. return;
  2554. }
  2555.  
  2556. if (this.photonViewList.ContainsKey(netView.viewID))
  2557. {
  2558. // if some other view is in the list already, we got a problem. it might be undestructible. print out error
  2559. if (netView != photonViewList[netView.viewID])
  2560. {
  2561. Debug.LogError(string.Format("PhotonView ID duplicate found: {0}. New: {1} old: {2}. Maybe one wasn't destroyed on scene load?! Check for 'DontDestroyOnLoad'. Destroying old entry, adding new.", netView.viewID, netView, photonViewList[netView.viewID]));
  2562. }
  2563.  
  2564. //this.photonViewList.Remove(netView.viewID); // TODO check if we chould Destroy the GO of this view?!
  2565. this.RemoveInstantiatedGO(photonViewList[netView.viewID].gameObject, true);
  2566. }
  2567.  
  2568. // Debug.Log("adding view to known list: " + netView);
  2569. this.photonViewList.Add(netView.viewID, netView);
  2570. //Debug.LogError("view being added. " + netView); // Exit Games internal log
  2571.  
  2572. if (PhotonNetwork.logLevel >= PhotonLogLevel.Full)
  2573. Debug.Log("Registered PhotonView: " + netView.viewID);
  2574. }
  2575.  
  2576. ///// <summary>
  2577. ///// Will remove the view from list of views (by its ID).
  2578. ///// </summary>
  2579. //public void RemovePhotonView(PhotonView netView)
  2580. //{
  2581. // if (!Application.isPlaying)
  2582. // {
  2583. // this.photonViewList = new Dictionary<int, PhotonView>();
  2584. // return;
  2585. // }
  2586.  
  2587. // //PhotonView removedView = null;
  2588. // //this.photonViewList.TryGetValue(netView.viewID, out removedView);
  2589. // //if (removedView != netView)
  2590. // //{
  2591. // // Debug.LogError("Detected two differing PhotonViews with same viewID: " + netView.viewID);
  2592. // //}
  2593.  
  2594. // this.photonViewList.Remove(netView.viewID);
  2595.  
  2596. // //if (this.DebugOut >= DebugLevel.ALL)
  2597. // //{
  2598. // // this.DebugReturn(DebugLevel.ALL, "Removed PhotonView: " + netView.viewID);
  2599. // //}
  2600. //}
  2601.  
  2602. /// <summary>
  2603. /// Removes the RPCs of someone else (to be used as master).
  2604. /// This won't clean any local caches. It just tells the server to forget a player's RPCs and instantiates.
  2605. /// </summary>
  2606. /// <param name="actorNumber"></param>
  2607. public void OpCleanRpcBuffer(int actorNumber)
  2608. {
  2609. RaiseEventOptions options = new RaiseEventOptions() { CachingOption = EventCaching.RemoveFromRoomCache, TargetActors = new int[] { actorNumber } };
  2610. this.OpRaiseEvent(PunEvent.RPC, null, true, options);
  2611. //this.OpRaiseEvent(PunEvent.RPC, null, true, 0, new int[] { actorNumber }, EventCaching.RemoveFromRoomCache);
  2612. }
  2613.  
  2614. /// <summary>
  2615. /// Instead removing RPCs or Instantiates, this removed everything cached by the actor.
  2616. /// </summary>
  2617. /// <param name="actorNumber"></param>
  2618. public void OpRemoveCompleteCacheOfPlayer(int actorNumber)
  2619. {
  2620. RaiseEventOptions options = new RaiseEventOptions() { CachingOption = EventCaching.RemoveFromRoomCache, TargetActors = new int[] { actorNumber } };
  2621. this.OpRaiseEvent(0, null, true, options);
  2622. //this.OpRaiseEvent(0, null, true, 0, new int[] { actorNumber }, EventCaching.RemoveFromRoomCache);
  2623. }
  2624.  
  2625.  
  2626. public void OpRemoveCompleteCache()
  2627. {
  2628. RaiseEventOptions options = new RaiseEventOptions() { CachingOption = EventCaching.RemoveFromRoomCache, Receivers = ReceiverGroup.MasterClient };
  2629. this.OpRaiseEvent(0, null, true, options);
  2630. //this.OpRaiseEvent(0, null, true, 0, EventCaching.RemoveFromRoomCache, ReceiverGroup.MasterClient); // TODO: check who gets this event?
  2631. }
  2632.  
  2633. /// 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)
  2634. private void RemoveCacheOfLeftPlayers()
  2635. {
  2636. Dictionary<byte, object> opParameters = new Dictionary<byte, object>();
  2637. opParameters[ParameterCode.Code] = (byte)0; // any event
  2638. opParameters[ParameterCode.Cache] = (byte)EventCaching.RemoveFromRoomCacheForActorsLeft; // option to clear the room cache of all events of players who left
  2639.  
  2640. this.OpCustom((byte)OperationCode.RaiseEvent, opParameters, true, 0);
  2641. }
  2642.  
  2643. // Remove RPCs of view (if they are local player's RPCs)
  2644. public void CleanRpcBufferIfMine(PhotonView view)
  2645. {
  2646. if (view.ownerId != this.mLocalActor.ID && !mLocalActor.isMasterClient)
  2647. {
  2648. Debug.LogError("Cannot remove cached RPCs on a PhotonView thats not ours! " + view.owner + " scene: " + view.isSceneView);
  2649. return;
  2650. }
  2651.  
  2652. this.OpCleanRpcBuffer(view);
  2653. }
  2654.  
  2655. /// <summary>Cleans server RPCs for PhotonView (without any further checks).</summary>
  2656. public void OpCleanRpcBuffer(PhotonView view)
  2657. {
  2658. Hashtable rpcFilterByViewId = new Hashtable();
  2659. rpcFilterByViewId[(byte)0] = view.viewID;
  2660.  
  2661. RaiseEventOptions options = new RaiseEventOptions() { CachingOption = EventCaching.RemoveFromRoomCache };
  2662. this.OpRaiseEvent(PunEvent.RPC, rpcFilterByViewId, true, options);
  2663. //this.OpRaiseEvent(PunEvent.RPC, rpcFilterByViewId, true, 0, EventCaching.RemoveFromRoomCache, ReceiverGroup.Others);
  2664. }
  2665.  
  2666. public void RemoveRPCsInGroup(int group)
  2667. {
  2668. foreach (KeyValuePair<int, PhotonView> kvp in this.photonViewList)
  2669. {
  2670. PhotonView view = kvp.Value;
  2671. if (view.group == group)
  2672. {
  2673. this.CleanRpcBufferIfMine(view);
  2674. }
  2675. }
  2676. }
  2677.  
  2678. public void SetLevelPrefix(short prefix)
  2679. {
  2680. this.currentLevelPrefix = prefix;
  2681. // TODO: should we really change the prefix for existing PVs?! better keep it!
  2682. //foreach (PhotonView view in this.photonViewList.Values)
  2683. //{
  2684. // view.prefix = prefix;
  2685. //}
  2686. }
  2687.  
  2688. internal void RPC(PhotonView view, string methodName, PhotonPlayer player, params object[] parameters)
  2689. {
  2690. if (this.blockSendingGroups.Contains(view.group))
  2691. {
  2692. return; // Block sending on this group
  2693. }
  2694.  
  2695. if (view.viewID < 1) //TODO: check why 0 should be illegal
  2696. {
  2697. Debug.LogError("Illegal view ID:" + view.viewID + " method: " + methodName + " GO:" + view.gameObject.name);
  2698. }
  2699.  
  2700. if (PhotonNetwork.logLevel >= PhotonLogLevel.Full)
  2701. Debug.Log("Sending RPC \"" + methodName + "\" to player[" + player + "]");
  2702.  
  2703.  
  2704. //ts: changed RPCs to a one-level hashtable as described in internal.txt
  2705. Hashtable rpcEvent = new Hashtable();
  2706. rpcEvent[(byte)0] = (int)view.viewID; // LIMITS PHOTONVIEWS&PLAYERS
  2707. if (view.prefix > 0)
  2708. {
  2709. rpcEvent[(byte)1] = (short)view.prefix;
  2710. }
  2711. rpcEvent[(byte)2] = this.ServerTimeInMilliSeconds;
  2712.  
  2713. // send name or shortcut (if available)
  2714. int shortcut = 0;
  2715. if (rpcShortcuts.TryGetValue(methodName, out shortcut))
  2716. {
  2717. rpcEvent[(byte)5] = (byte)shortcut; // LIMITS RPC COUNT
  2718. }
  2719. else
  2720. {
  2721. rpcEvent[(byte)3] = methodName;
  2722. }
  2723.  
  2724. if (parameters != null && parameters.Length > 0)
  2725. {
  2726. rpcEvent[(byte) 4] = (object[]) parameters;
  2727. }
  2728.  
  2729. if (this.mLocalActor == player)
  2730. {
  2731. this.ExecuteRPC(rpcEvent, player);
  2732. }
  2733. else
  2734. {
  2735. RaiseEventOptions options = new RaiseEventOptions() { TargetActors = new int[] { player.ID } };
  2736. this.OpRaiseEvent(PunEvent.RPC, rpcEvent, true, options);
  2737. //int[] targetActors = new int[] { player.ID };
  2738. //this.OpRaiseEvent(PunEvent.RPC, rpcEvent, true, 0, targetActors);
  2739. }
  2740. }
  2741.  
  2742. /// RPC Hashtable Structure
  2743. /// (byte)0 -> (int) ViewId (combined from actorNr and actor-unique-id)
  2744. /// (byte)1 -> (short) prefix (level)
  2745. /// (byte)2 -> (int) server timestamp
  2746. /// (byte)3 -> (string) methodname
  2747. /// (byte)4 -> (object[]) parameters
  2748. /// (byte)5 -> (byte) method shortcut (alternative to name)
  2749. ///
  2750. /// This is sent as event (code: 200) which will contain a sender (origin of this RPC).
  2751.  
  2752. internal void RPC(PhotonView view, string methodName, PhotonTargets target, params object[] parameters)
  2753. {
  2754. if (this.blockSendingGroups.Contains(view.group))
  2755. {
  2756. return; // Block sending on this group
  2757. }
  2758.  
  2759. if (view.viewID < 1)
  2760. {
  2761. Debug.LogError("Illegal view ID:" + view.viewID + " method: " + methodName + " GO:" + view.gameObject.name);
  2762. }
  2763.  
  2764. if (PhotonNetwork.logLevel >= PhotonLogLevel.Full)
  2765. Debug.Log("Sending RPC \"" + methodName + "\" to " + target);
  2766.  
  2767. //ts: changed RPCs to a one-level hashtable as described in internal.txt
  2768. Hashtable rpcEvent = new Hashtable();
  2769. rpcEvent[(byte)0] = (int)view.viewID; // LIMITS NETWORKVIEWS&PLAYERS
  2770. if (view.prefix > 0)
  2771. {
  2772. rpcEvent[(byte)1] = (short)view.prefix;
  2773. }
  2774. rpcEvent[(byte)2] = this.ServerTimeInMilliSeconds;
  2775.  
  2776.  
  2777. // send name or shortcut (if available)
  2778. int shortcut = 0;
  2779. if (rpcShortcuts.TryGetValue(methodName, out shortcut))
  2780. {
  2781. rpcEvent[(byte)5] = (byte)shortcut; // LIMITS RPC COUNT
  2782. }
  2783. else
  2784. {
  2785. rpcEvent[(byte)3] = methodName;
  2786. }
  2787.  
  2788. if (parameters != null && parameters.Length > 0)
  2789. {
  2790. rpcEvent[(byte)4] = (object[])parameters;
  2791. }
  2792.  
  2793. // Check scoping
  2794. if (target == PhotonTargets.All)
  2795. {
  2796. RaiseEventOptions options = new RaiseEventOptions() { InterestGroup = (byte)view.group };
  2797. this.OpRaiseEvent(PunEvent.RPC, rpcEvent, true, options);
  2798. //this.OpRaiseEvent(PunEvent.RPC, (byte)view.group, rpcEvent, true, 0);
  2799.  
  2800. // Execute local
  2801. this.ExecuteRPC(rpcEvent, this.mLocalActor);
  2802. }
  2803. else if (target == PhotonTargets.Others)
  2804. {
  2805. RaiseEventOptions options = new RaiseEventOptions() { InterestGroup = (byte)view.group };
  2806. this.OpRaiseEvent(PunEvent.RPC, rpcEvent, true, options);
  2807. //this.OpRaiseEvent(PunEvent.RPC, (byte)view.group, rpcEvent, true, 0);
  2808. }
  2809. else if (target == PhotonTargets.AllBuffered)
  2810. {
  2811. RaiseEventOptions options = new RaiseEventOptions() { CachingOption = EventCaching.AddToRoomCache};
  2812. this.OpRaiseEvent(PunEvent.RPC, rpcEvent, true, options);
  2813. //this.OpRaiseEvent(PunEvent.RPC, rpcEvent, true, 0, EventCaching.AddToRoomCache, ReceiverGroup.Others);
  2814.  
  2815. // Execute local
  2816. this.ExecuteRPC(rpcEvent, this.mLocalActor);
  2817. }
  2818. else if (target == PhotonTargets.OthersBuffered)
  2819. {
  2820. RaiseEventOptions options = new RaiseEventOptions() { CachingOption = EventCaching.AddToRoomCache };
  2821. this.OpRaiseEvent(PunEvent.RPC, rpcEvent, true, options);
  2822. //this.OpRaiseEvent(PunEvent.RPC, rpcEvent, true, 0, EventCaching.AddToRoomCache, ReceiverGroup.Others);
  2823. }
  2824. else if (target == PhotonTargets.MasterClient)
  2825. {
  2826. if (this.mMasterClient == this.mLocalActor)
  2827. {
  2828. this.ExecuteRPC(rpcEvent, this.mLocalActor);
  2829. }
  2830. else
  2831. {
  2832. RaiseEventOptions options = new RaiseEventOptions() { Receivers = ReceiverGroup.MasterClient };
  2833. this.OpRaiseEvent(PunEvent.RPC, rpcEvent, true, options);
  2834. //this.OpRaiseEvent(PunEvent.RPC, rpcEvent, true, 0, EventCaching.DoNotCache, ReceiverGroup.MasterClient);//TS: changed from caching to non-cached. this goes to master only
  2835. }
  2836. }
  2837. else if (target == PhotonTargets.AllViaServer)
  2838. {
  2839. RaiseEventOptions options = new RaiseEventOptions() { InterestGroup = (byte)view.group, Receivers = ReceiverGroup.All };
  2840. this.OpRaiseEvent(PunEvent.RPC, rpcEvent, true, options);
  2841. //this.OpRaiseEvent(PunEvent.RPC, true, rpcEvent, 0, EventCaching.DoNotCache, null, ReceiverGroup.All, (byte)view.group);
  2842. }
  2843. else if (target == PhotonTargets.AllBufferedViaServer)
  2844. {
  2845. RaiseEventOptions options = new RaiseEventOptions() { InterestGroup = (byte)view.group, Receivers = ReceiverGroup.All, CachingOption = EventCaching.AddToRoomCache };
  2846. this.OpRaiseEvent(PunEvent.RPC, rpcEvent, true, options);
  2847. //this.OpRaiseEvent(PunEvent.RPC, true, rpcEvent, 0, EventCaching.AddToRoomCache, null, ReceiverGroup.All, (byte)view.group);
  2848. }
  2849. else
  2850. {
  2851. Debug.LogError("Unsupported target enum: " + target);
  2852. }
  2853. }
  2854.  
  2855. // SetReceiving
  2856. public void SetReceivingEnabled(int group, bool enabled)
  2857. {
  2858. if (group <= 0)
  2859. {
  2860. Debug.LogError("Error: PhotonNetwork.SetReceivingEnabled was called with an illegal group number: " + group + ". The group number should be at least 1.");
  2861. return;
  2862. }
  2863.  
  2864. if (enabled)
  2865. {
  2866. if (!this.allowedReceivingGroups.Contains(group))
  2867. {
  2868. this.allowedReceivingGroups.Add(group);
  2869. byte[] groups = new byte[1] { (byte)group };
  2870. this.OpChangeGroups(null, groups);
  2871. }
  2872. }
  2873. else
  2874. {
  2875. if (this.allowedReceivingGroups.Contains(group))
  2876. {
  2877. this.allowedReceivingGroups.Remove(group);
  2878. byte[] groups = new byte[1] { (byte)group };
  2879. this.OpChangeGroups(groups, null);
  2880. }
  2881. }
  2882. }
  2883.  
  2884. // SetSending
  2885. public void SetSendingEnabled(int group, bool enabled)
  2886. {
  2887. if (!enabled)
  2888. {
  2889. this.blockSendingGroups.Add(group); // can be added to HashSet no matter if already in it
  2890. }
  2891. else
  2892. {
  2893. this.blockSendingGroups.Remove(group);
  2894. }
  2895. }
  2896.  
  2897. public void NewSceneLoaded()
  2898. {
  2899. if (this.loadingLevelAndPausedNetwork)
  2900. {
  2901. this.loadingLevelAndPausedNetwork = false;
  2902. PhotonNetwork.isMessageQueueRunning = true;
  2903. }
  2904. // Debug.Log("OnLevelWasLoaded photonViewList.Count: " + photonViewList.Count); // Exit Games internal log
  2905.  
  2906. List<int> removeKeys = new List<int>();
  2907. foreach (KeyValuePair<int, PhotonView> kvp in this.photonViewList)
  2908. {
  2909. PhotonView view = kvp.Value;
  2910. if (view == null)
  2911. {
  2912. removeKeys.Add(kvp.Key);
  2913. }
  2914. }
  2915.  
  2916. for (int index = 0; index < removeKeys.Count; index++)
  2917. {
  2918. int key = removeKeys[index];
  2919. this.photonViewList.Remove(key);
  2920. }
  2921.  
  2922. if (removeKeys.Count > 0)
  2923. {
  2924. if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
  2925. Debug.Log("New level loaded. Removed " + removeKeys.Count + " scene view IDs from last level.");
  2926. }
  2927. }
  2928.  
  2929. // this is called by Update() and in Unity that means it's single threaded.
  2930. public void RunViewUpdate()
  2931. {
  2932. if (!PhotonNetwork.connected || PhotonNetwork.offlineMode)
  2933. {
  2934. return;
  2935. }
  2936.  
  2937. if (this.mActors == null || this.mActors.Count <= 1)
  2938. {
  2939. return; // No need to send OnSerialize messages (these are never buffered anyway)
  2940. }
  2941.  
  2942. Dictionary<int, Hashtable> dataPerGroupReliable = new Dictionary<int, Hashtable>();
  2943. Dictionary<int, Hashtable> dataPerGroupUnreliable = new Dictionary<int, Hashtable>();
  2944.  
  2945. /* Format of the data hashtable:
  2946. * Hasthable dataPergroup*
  2947. * [(byte)0] = this.ServerTimeInMilliSeconds;
  2948. * OPTIONAL: [(byte)1] = currentLevelPrefix;
  2949. * + data
  2950. */
  2951.  
  2952. foreach (KeyValuePair<int, PhotonView> kvp in this.photonViewList)
  2953. {
  2954. PhotonView view = kvp.Value;
  2955.  
  2956. if (view.observed != null && view.synchronization != ViewSynchronization.Off)
  2957. {
  2958. // Fetch all sending photonViews
  2959. if (view.ownerId == this.mLocalActor.ID || (view.isSceneView && this.mMasterClient == this.mLocalActor))
  2960. {
  2961. #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
  2962. if (!view.gameObject.active)
  2963. {
  2964. continue; // Only on actives
  2965. }
  2966. #else
  2967. if (!view.gameObject.activeInHierarchy)
  2968. {
  2969. continue; // Only on actives
  2970. }
  2971. #endif
  2972.  
  2973. if (this.blockSendingGroups.Contains(view.group))
  2974. {
  2975. continue; // Block sending on this group
  2976. }
  2977.  
  2978. // Run it trough its OnSerialize
  2979. Hashtable evData = this.OnSerializeWrite(view);
  2980. if (evData == null)
  2981. {
  2982. continue;
  2983. }
  2984.  
  2985. if (view.synchronization == ViewSynchronization.ReliableDeltaCompressed || view.mixedModeIsReliable)
  2986. {
  2987. if (!evData.ContainsKey((byte)1) && !evData.ContainsKey((byte)2))
  2988. {
  2989. // Everything has been removed by compression, nothing to send
  2990. }
  2991. else
  2992. {
  2993. if (!dataPerGroupReliable.ContainsKey(view.group))
  2994. {
  2995. dataPerGroupReliable[view.group] = new Hashtable();
  2996. dataPerGroupReliable[view.group][(byte)0] = this.ServerTimeInMilliSeconds;
  2997. if (currentLevelPrefix >= 0)
  2998. {
  2999. dataPerGroupReliable[view.group][(byte)1] = this.currentLevelPrefix;
  3000. }
  3001. }
  3002. Hashtable groupHashtable = dataPerGroupReliable[view.group];
  3003. groupHashtable.Add((short)groupHashtable.Count, evData);
  3004. }
  3005. }
  3006. else
  3007. {
  3008. if (!dataPerGroupUnreliable.ContainsKey(view.group))
  3009. {
  3010. dataPerGroupUnreliable[view.group] = new Hashtable();
  3011. dataPerGroupUnreliable[view.group][(byte)0] = this.ServerTimeInMilliSeconds;
  3012. if (currentLevelPrefix >= 0)
  3013. {
  3014. dataPerGroupUnreliable[view.group][(byte)1] = this.currentLevelPrefix;
  3015. }
  3016. }
  3017. Hashtable groupHashtable = dataPerGroupUnreliable[view.group];
  3018. groupHashtable.Add((short)groupHashtable.Count, evData);
  3019. }
  3020. }
  3021. else
  3022. {
  3023. // Debug.Log(" NO OBS on " + view.name + " " + view.owner);
  3024. }
  3025. }
  3026. else
  3027. {
  3028. }
  3029. }
  3030.  
  3031. //Send the messages: every group is send in it's own message and unreliable and reliable are split as well
  3032. RaiseEventOptions options = new RaiseEventOptions();
  3033. foreach (KeyValuePair<int, Hashtable> kvp in dataPerGroupReliable)
  3034. {
  3035. options.InterestGroup = (byte)kvp.Key;
  3036. this.OpRaiseEvent(PunEvent.SendSerializeReliable, kvp.Value, true, options);
  3037. //this.OpRaiseEvent(PunEvent.SendSerializeReliable, (byte)kvp.Key, kvp.Value, true, 0);
  3038. }
  3039. foreach (KeyValuePair<int, Hashtable> kvp in dataPerGroupUnreliable)
  3040. {
  3041. options.InterestGroup = (byte)kvp.Key;
  3042. this.OpRaiseEvent(PunEvent.SendSerialize, kvp.Value, false, options);
  3043. //this.OpRaiseEvent(PunEvent.SendSerialize, (byte)kvp.Key, kvp.Value, false, 0);
  3044. }
  3045. }
  3046.  
  3047. // calls OnPhotonSerializeView (through ExecuteOnSerialize)
  3048. // the content created here is consumed by receivers in: ReadOnSerialize
  3049. private Hashtable OnSerializeWrite(PhotonView view)
  3050. {
  3051. // each view creates a list of values that should be sent
  3052. List<object> data = new List<object>();
  3053.  
  3054. // 1=Specific data
  3055. if (view.observed is MonoBehaviour)
  3056. {
  3057. PhotonStream pStream = new PhotonStream(true, null);
  3058. PhotonMessageInfo info = new PhotonMessageInfo(this.mLocalActor, this.ServerTimeInMilliSeconds, view);
  3059.  
  3060. view.ExecuteOnSerialize(pStream, info);
  3061. if (pStream.Count == 0)
  3062. {
  3063. // if an observed script didn't write any data, we don't send anything
  3064. return null;
  3065. }
  3066.  
  3067. // we want to use the content of the stream (filled in by user scripts)
  3068. data = pStream.data;
  3069. }
  3070. else if (view.observed is Transform)
  3071. {
  3072. Transform trans = (Transform)view.observed;
  3073.  
  3074. if (view.onSerializeTransformOption == OnSerializeTransform.OnlyPosition
  3075. || view.onSerializeTransformOption == OnSerializeTransform.PositionAndRotation
  3076. || view.onSerializeTransformOption == OnSerializeTransform.All)
  3077. data.Add(trans.localPosition);
  3078. else
  3079. data.Add(null);
  3080.  
  3081. if (view.onSerializeTransformOption == OnSerializeTransform.OnlyRotation
  3082. || view.onSerializeTransformOption == OnSerializeTransform.PositionAndRotation
  3083. || view.onSerializeTransformOption == OnSerializeTransform.All)
  3084. data.Add(trans.localRotation);
  3085. else
  3086. data.Add(null);
  3087.  
  3088. if (view.onSerializeTransformOption == OnSerializeTransform.OnlyScale
  3089. || view.onSerializeTransformOption == OnSerializeTransform.All)
  3090. data.Add(trans.localScale);
  3091. }
  3092. else if (view.observed is Rigidbody)
  3093. {
  3094. Rigidbody rigidB = (Rigidbody)view.observed;
  3095.  
  3096. if (view.onSerializeRigidBodyOption != OnSerializeRigidBody.OnlyAngularVelocity)
  3097. data.Add(rigidB.velocity);
  3098. else
  3099. data.Add(null);
  3100.  
  3101. if (view.onSerializeRigidBodyOption != OnSerializeRigidBody.OnlyVelocity)
  3102. data.Add(rigidB.angularVelocity);
  3103. }
  3104. else
  3105. {
  3106. Debug.LogError("Observed type is not serializable: " + view.observed.GetType());
  3107. return null;
  3108. }
  3109.  
  3110. object[] dataArray = data.ToArray();
  3111.  
  3112. if (view.synchronization == ViewSynchronization.UnreliableOnChange)
  3113. {
  3114. if (AlmostEquals(dataArray, view.lastOnSerializeDataSent))
  3115. {
  3116. if (view.mixedModeIsReliable)
  3117. {
  3118. return null;
  3119. }
  3120.  
  3121. view.mixedModeIsReliable = true;
  3122. view.lastOnSerializeDataSent = dataArray;
  3123. }
  3124. else
  3125. {
  3126. view.mixedModeIsReliable = false;
  3127. view.lastOnSerializeDataSent = dataArray;
  3128. }
  3129. }
  3130.  
  3131. // EVDATA:
  3132. // 0=View ID (an int, never compressed cause it's not in the data)
  3133. // 1=data of observed type (different per type of observed object)
  3134. // 2=compressed data (in this case, key 1 is empty)
  3135. // 3=list of values that are actually null (if something was changed but actually IS null)
  3136. Hashtable evData = new Hashtable();
  3137. evData[(byte)0] = (int)view.viewID;
  3138. evData[(byte)1] = dataArray; // this is the actual data (script or observed object)
  3139.  
  3140.  
  3141. if (view.synchronization == ViewSynchronization.ReliableDeltaCompressed)
  3142. {
  3143. // compress content of data set (by comparing to view.lastOnSerializeDataSent)
  3144. // the "original" dataArray is NOT modified by DeltaCompressionWrite
  3145. // if something was compressed, the evData key 2 and 3 are used (see above)
  3146. bool somethingLeftToSend = this.DeltaCompressionWrite(view, evData);
  3147.  
  3148. // buffer the full data set (for next compression)
  3149. view.lastOnSerializeDataSent = dataArray;
  3150.  
  3151. if (!somethingLeftToSend)
  3152. {
  3153. return null;
  3154. }
  3155. }
  3156.  
  3157. return evData;
  3158. }
  3159.  
  3160. /// <summary>
  3161. /// Reads updates created by OnSerializeWrite
  3162. /// </summary>
  3163. private void OnSerializeRead(Hashtable data, PhotonPlayer sender, int networkTime, short correctPrefix)
  3164. {
  3165. // read view ID from key (byte)0: a int-array (PUN 1.17++)
  3166. int viewID = (int)data[(byte)0];
  3167.  
  3168.  
  3169. PhotonView view = this.GetPhotonView(viewID);
  3170. if (view == null)
  3171. {
  3172. Debug.LogWarning("Received OnSerialization for view ID " + viewID + ". We have no such PhotonView! Ignored this if you're leaving a room. State: " + this.State);
  3173. return;
  3174. }
  3175.  
  3176. if (view.prefix > 0 && correctPrefix != view.prefix)
  3177. {
  3178. Debug.LogError("Received OnSerialization for view ID " + viewID + " with prefix " + correctPrefix + ". Our prefix is " + view.prefix);
  3179. return;
  3180. }
  3181.  
  3182. // SetReceiving filtering
  3183. if (view.group != 0 && !this.allowedReceivingGroups.Contains(view.group))
  3184. {
  3185. return; // Ignore group
  3186. }
  3187.  
  3188.  
  3189. if (view.synchronization == ViewSynchronization.ReliableDeltaCompressed)
  3190. {
  3191. if (!this.DeltaCompressionRead(view, data))
  3192. {
  3193. // Skip this packet as we haven't got received complete-copy of this view yet.
  3194. if (PhotonNetwork.logLevel >= PhotonLogLevel.Informational)
  3195. Debug.Log("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.");
  3196. return;
  3197. }
  3198.  
  3199. // store last received for delta-compression usage
  3200. view.lastOnSerializeDataReceived = data[(byte)1] as object[];
  3201. }
  3202.  
  3203. // Use incoming data according to observed type
  3204. if (view.observed is MonoBehaviour)
  3205. {
  3206. object[] contents = data[(byte)1] as object[];
  3207. PhotonStream pStream = new PhotonStream(false, contents);
  3208. PhotonMessageInfo info = new PhotonMessageInfo(sender, networkTime, view);
  3209.  
  3210. view.ExecuteOnSerialize(pStream, info);
  3211. }
  3212. else if (view.observed is Transform)
  3213. {
  3214. object[] contents = data[(byte)1] as object[];
  3215. Transform trans = (Transform)view.observed;
  3216. if (contents.Length >= 1 && contents[0] != null)
  3217. trans.localPosition = (Vector3)contents[0];
  3218. if (contents.Length >= 2 && contents[1] != null)
  3219. trans.localRotation = (Quaternion)contents[1];
  3220. if (contents.Length >= 3 && contents[2] != null)
  3221. trans.localScale = (Vector3)contents[2];
  3222.  
  3223. }
  3224. else if (view.observed is Rigidbody)
  3225. {
  3226. object[] contents = data[(byte)1] as object[];
  3227. Rigidbody rigidB = (Rigidbody)view.observed;
  3228. if (contents.Length >= 1 && contents[0] != null)
  3229. rigidB.velocity = (Vector3)contents[0];
  3230. if (contents.Length >= 2 && contents[1] != null)
  3231. rigidB.angularVelocity = (Vector3)contents[1];
  3232. }
  3233. else
  3234. {
  3235. Debug.LogError("Type of observed is unknown when receiving.");
  3236. }
  3237. }
  3238.  
  3239. private bool AlmostEquals(object[] lastData, object[] currentContent)
  3240. {
  3241. if (lastData == null && currentContent == null)
  3242. {
  3243. return true;
  3244. }
  3245.  
  3246. if (lastData == null || currentContent == null || (lastData.Length != currentContent.Length))
  3247. {
  3248. return false;
  3249. }
  3250.  
  3251. for (int index = 0; index < currentContent.Length; index++)
  3252. {
  3253. object newObj = currentContent[index];
  3254. object oldObj = lastData[index];
  3255. if (!this.ObjectIsSameWithInprecision(newObj, oldObj))
  3256. {
  3257. return false;
  3258. }
  3259. }
  3260.  
  3261. return true;
  3262. }
  3263.  
  3264. /// <summary>
  3265. /// Compares the new data with previously sent data and skips values that didn't change.
  3266. /// </summary>
  3267. /// <returns>True if anything has to be sent, false if nothing new or no data</returns>
  3268. private bool DeltaCompressionWrite(PhotonView view, Hashtable data)
  3269. {
  3270. if (view.lastOnSerializeDataSent == null)
  3271. {
  3272. return true; // all has to be sent
  3273. }
  3274.  
  3275. // We can compress as we sent a full update previously (readers can re-use previous values)
  3276. object[] lastData = view.lastOnSerializeDataSent;
  3277. object[] currentContent = data[(byte)1] as object[];
  3278.  
  3279. if (currentContent == null)
  3280. {
  3281. // no data to be sent
  3282. return false;
  3283. }
  3284.  
  3285. if (lastData.Length != currentContent.Length)
  3286. {
  3287. // if new data isn't same length as before, we send the complete data-set uncompressed
  3288. return true;
  3289. }
  3290.  
  3291. object[] compressedContent = new object[currentContent.Length];
  3292. int compressedValues = 0;
  3293.  
  3294. List<int> valuesThatAreChangedToNull = new List<int>();
  3295. for (int index = 0; index < compressedContent.Length; index++)
  3296. {
  3297. object newObj = currentContent[index];
  3298. object oldObj = lastData[index];
  3299. if (this.ObjectIsSameWithInprecision(newObj, oldObj))
  3300. {
  3301. // compress (by using null, instead of value, which is same as before)
  3302. compressedValues++;
  3303. // compressedContent[index] is already null (initialized)
  3304. }
  3305. else
  3306. {
  3307. compressedContent[index] = currentContent[index];
  3308.  
  3309. // value changed, we don't replace it with null
  3310. // new value is null (like a compressed value): we have to mark it so it STAYS null instead of being replaced with previous value
  3311. if (newObj == null)
  3312. {
  3313. valuesThatAreChangedToNull.Add(index);
  3314. }
  3315. }
  3316. }
  3317.  
  3318. // Only send the list of compressed fields if we actually compressed 1 or more fields.
  3319. if (compressedValues > 0)
  3320. {
  3321. data.Remove((byte)1); // remove the original data (we only send compressed data)
  3322.  
  3323. if (compressedValues == currentContent.Length)
  3324. {
  3325. // all values are compressed to null, we have nothing to send
  3326. return false;
  3327. }
  3328.  
  3329. data[(byte)2] = compressedContent; // current, compressted data is moved to key 2 to mark it as compressed
  3330. if (valuesThatAreChangedToNull.Count > 0)
  3331. {
  3332. data[(byte)3] = valuesThatAreChangedToNull.ToArray(); // data that is actually null (not just cause we didn't want to send it)
  3333. }
  3334. }
  3335.  
  3336. return true; // some data was compressed but we need to send something
  3337. }
  3338.  
  3339. /// <summary>
  3340. /// reads incoming messages created by "OnSerialize"
  3341. /// </summary>
  3342. private bool DeltaCompressionRead(PhotonView view, Hashtable data)
  3343. {
  3344. if (data.ContainsKey((byte)1))
  3345. {
  3346. // we have a full list of data (cause key 1 is used), so return "we have uncompressed all"
  3347. return true;
  3348. }
  3349.  
  3350. // Compression was applied as data[(byte)2] exists (this is the data with some fields being compressed to null)
  3351. // now we also need a previous "full" list of values to restore values that are null in this msg
  3352. if (view.lastOnSerializeDataReceived == null)
  3353. {
  3354. return false; // We dont have a full match yet, we cannot work with missing values: skip this message
  3355. }
  3356.  
  3357. object[] compressedContents = data[(byte)2] as object[];
  3358. if (compressedContents == null)
  3359. {
  3360. // despite expectation, there is no compressed data in this msg. shouldn't happen. just a null check
  3361. return false;
  3362. }
  3363.  
  3364. int[] indexesThatAreChangedToNull = data[(byte)3] as int[];
  3365. if (indexesThatAreChangedToNull == null)
  3366. {
  3367. indexesThatAreChangedToNull = new int[0];
  3368. }
  3369.  
  3370. object[] lastReceivedData = view.lastOnSerializeDataReceived;
  3371. for (int index = 0; index < compressedContents.Length; index++)
  3372. {
  3373. if (compressedContents[index] == null && !indexesThatAreChangedToNull.Contains(index))
  3374. {
  3375. // we replace null values in this received msg unless a index is in the "changed to null" list
  3376. object lastValue = lastReceivedData[index];
  3377. compressedContents[index] = lastValue;
  3378. }
  3379. }
  3380.  
  3381. data[(byte)1] = compressedContents; // compressedContents are now uncompressed...
  3382. return true;
  3383. }
  3384.  
  3385. /// <summary>
  3386. /// Returns true if both objects are almost identical.
  3387. /// Used to check whether two objects are similar enough to skip an update.
  3388. /// </summary>
  3389. bool ObjectIsSameWithInprecision(object one, object two)
  3390. {
  3391. if (one == null || two == null)
  3392. {
  3393. return one == null && two == null;
  3394. }
  3395.  
  3396. if (!one.Equals(two))
  3397. {
  3398. // if A is not B, lets check if A is almost B
  3399. if (one is Vector3)
  3400. {
  3401. Vector3 a = (Vector3)one;
  3402. Vector3 b = (Vector3)two;
  3403. if (a.AlmostEquals(b, PhotonNetwork.precisionForVectorSynchronization))
  3404. {
  3405. return true;
  3406. }
  3407. }
  3408. else if (one is Vector2)
  3409. {
  3410. Vector2 a = (Vector2)one;
  3411. Vector2 b = (Vector2)two;
  3412. if (a.AlmostEquals(b, PhotonNetwork.precisionForVectorSynchronization))
  3413. {
  3414. return true;
  3415. }
  3416. }
  3417. else if (one is Quaternion)
  3418. {
  3419. Quaternion a = (Quaternion)one;
  3420. Quaternion b = (Quaternion)two;
  3421. if (a.AlmostEquals(b, PhotonNetwork.precisionForQuaternionSynchronization))
  3422. {
  3423. return true;
  3424. }
  3425. }
  3426. else if (one is float)
  3427. {
  3428. float a = (float)one;
  3429. float b = (float)two;
  3430. if (a.AlmostEquals(b, PhotonNetwork.precisionForFloatSynchronization))
  3431. {
  3432. return true;
  3433. }
  3434. }
  3435.  
  3436. // one does not equal two
  3437. return false;
  3438. }
  3439.  
  3440. return true;
  3441. }
  3442.  
  3443. internal protected static bool GetMethod(MonoBehaviour monob, string methodType, out MethodInfo mi)
  3444. {
  3445. mi = null;
  3446.  
  3447. if (monob == null || string.IsNullOrEmpty(methodType))
  3448. {
  3449. return false;
  3450. }
  3451.  
  3452. List<MethodInfo> methods = SupportClass.GetMethods(monob.GetType(), null);
  3453. for (int index = 0; index < methods.Count; index++)
  3454. {
  3455. MethodInfo methodInfo = methods[index];
  3456. if (methodInfo.Name.Equals(methodType))
  3457. {
  3458. mi = methodInfo;
  3459. return true;
  3460. }
  3461. }
  3462.  
  3463. return false;
  3464. }
  3465.  
  3466. /// <summary>Internally used to flag if the message queue was disabled by a "scene sync" situation (to re-enable it).</summary>
  3467. internal protected bool loadingLevelAndPausedNetwork = false;
  3468.  
  3469. /// <summary>For automatic scene syncing, the loaded scene is put into a room property. This is the name of said prop.</summary>
  3470. internal protected const string CurrentSceneProperty = "curScn";
  3471.  
  3472. /// <summary>Internally used to detect the current scene and load it if PhotonNetwork.automaticallySyncScene is enabled.</summary>
  3473. internal protected void LoadLevelIfSynced()
  3474. {
  3475. if (!PhotonNetwork.automaticallySyncScene || PhotonNetwork.isMasterClient || PhotonNetwork.room == null)
  3476. {
  3477. return;
  3478. }
  3479.  
  3480. // check if "current level" is set in props
  3481. if (!PhotonNetwork.room.customProperties.ContainsKey(NetworkingPeer.CurrentSceneProperty))
  3482. {
  3483. return;
  3484. }
  3485.  
  3486. // if loaded level is not the one defined my master in props, load that level
  3487. object sceneId = PhotonNetwork.room.customProperties[NetworkingPeer.CurrentSceneProperty];
  3488. if (sceneId is int)
  3489. {
  3490. if (Application.loadedLevel != (int)sceneId)
  3491. PhotonNetwork.LoadLevel((int)sceneId);
  3492. }
  3493. else if (sceneId is string)
  3494. {
  3495. if (Application.loadedLevelName != (string)sceneId)
  3496. PhotonNetwork.LoadLevel((string)sceneId);
  3497. }
  3498. }
  3499.  
  3500. protected internal void SetLevelInPropsIfSynced(object levelId)
  3501. {
  3502. if (!PhotonNetwork.automaticallySyncScene || !PhotonNetwork.isMasterClient || PhotonNetwork.room == null)
  3503. {
  3504. return;
  3505. }
  3506. if (levelId == null)
  3507. {
  3508. Debug.LogError("Parameter levelId can't be null!");
  3509. return;
  3510. }
  3511.  
  3512. // check if "current level" is already set in props
  3513. if (PhotonNetwork.room.customProperties.ContainsKey(NetworkingPeer.CurrentSceneProperty))
  3514. {
  3515. object levelIdInProps = PhotonNetwork.room.customProperties[NetworkingPeer.CurrentSceneProperty];
  3516. if (levelIdInProps is int && Application.loadedLevel == (int)levelIdInProps)
  3517. {
  3518. return;
  3519. }
  3520. if (levelIdInProps is string && Application.loadedLevelName.Equals((string)levelIdInProps))
  3521. {
  3522. return;
  3523. }
  3524. }
  3525.  
  3526. // current level is not yet in props, so this client has to set it
  3527. Hashtable setScene = new Hashtable();
  3528. if (levelId is int) setScene[NetworkingPeer.CurrentSceneProperty] = (int)levelId;
  3529. else if (levelId is string) setScene[NetworkingPeer.CurrentSceneProperty] = (string)levelId;
  3530. else Debug.LogError("Parameter levelId must be int or string!");
  3531.  
  3532. PhotonNetwork.room.SetCustomProperties(setScene);
  3533. this.SendOutgoingCommands(); // send immediately! because: in most cases the client will begin to load and not send for a while
  3534. }
  3535.  
  3536. public void SetApp(string appId, string gameVersion)
  3537. {
  3538. this.mAppId = appId.Trim();
  3539. this.mAppVersion = gameVersion.Trim() + "_" + PhotonNetwork.versionPUN;
  3540. }
  3541. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement