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

https://github.com/Reinisch/Darkest-Dungeon-Unity · C# · 4649 lines · 3336 code · 735 blank · 578 comment · 887 complexity · 33c00de226e937f6cd28680101909926 MD5 · raw file

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