Room.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. // ----------------------------------------------------------------------------
  2. // <copyright file="Room.cs" company="Exit Games GmbH">
  3. // Loadbalancing Framework for Photon - Copyright (C) 2018 Exit Games GmbH
  4. // </copyright>
  5. // <summary>
  6. // The Room class resembles the properties known about the room in which
  7. // a game/match happens.
  8. // </summary>
  9. // <author>developer@photonengine.com</author>
  10. // ----------------------------------------------------------------------------
  11. #if UNITY_4_7 || UNITY_5 || UNITY_5_3_OR_NEWER
  12. #define SUPPORTED_UNITY
  13. #endif
  14. namespace Photon.Realtime
  15. {
  16. using System;
  17. using System.Collections;
  18. using System.Collections.Generic;
  19. using ExitGames.Client.Photon;
  20. #if SUPPORTED_UNITY || NETFX_CORE
  21. using Hashtable = ExitGames.Client.Photon.Hashtable;
  22. using SupportClass = ExitGames.Client.Photon.SupportClass;
  23. #endif
  24. /// <summary>
  25. /// This class represents a room a client joins/joined.
  26. /// </summary>
  27. /// <remarks>
  28. /// Contains a list of current players, their properties and those of this room, too.
  29. /// A room instance has a number of "well known" properties like IsOpen, MaxPlayers which can be changed.
  30. /// Your own, custom properties can be set via SetCustomProperties() while being in the room.
  31. ///
  32. /// Typically, this class should be extended by a game-specific implementation with logic and extra features.
  33. /// </remarks>
  34. public class Room : RoomInfo
  35. {
  36. /// <summary>
  37. /// A reference to the LoadBalancingClient which is currently keeping the connection and state.
  38. /// </summary>
  39. public LoadBalancingClient LoadBalancingClient { get; set; }
  40. /// <summary>The name of a room. Unique identifier (per region and virtual appid) for a room/match.</summary>
  41. /// <remarks>The name can't be changed once it's set by the server.</remarks>
  42. public new string Name
  43. {
  44. get
  45. {
  46. return this.name;
  47. }
  48. internal set
  49. {
  50. this.name = value;
  51. }
  52. }
  53. private bool isOffline;
  54. public bool IsOffline
  55. {
  56. get
  57. {
  58. return isOffline;
  59. }
  60. private set
  61. {
  62. isOffline = value;
  63. }
  64. }
  65. /// <summary>
  66. /// Defines if the room can be joined.
  67. /// </summary>
  68. /// <remarks>
  69. /// This does not affect listing in a lobby but joining the room will fail if not open.
  70. /// If not open, the room is excluded from random matchmaking.
  71. /// Due to racing conditions, found matches might become closed while users are trying to join.
  72. /// Simply re-connect to master and find another.
  73. /// Use property "IsVisible" to not list the room.
  74. ///
  75. /// As part of RoomInfo this can't be set.
  76. /// As part of a Room (which the player joined), the setter will update the server and all clients.
  77. /// </remarks>
  78. public new bool IsOpen
  79. {
  80. get
  81. {
  82. return this.isOpen;
  83. }
  84. set
  85. {
  86. if (value != this.isOpen)
  87. {
  88. if (!this.isOffline)
  89. {
  90. this.LoadBalancingClient.OpSetPropertiesOfRoom(new Hashtable() { { GamePropertyKey.IsOpen, value } });
  91. }
  92. }
  93. this.isOpen = value;
  94. }
  95. }
  96. /// <summary>
  97. /// Defines if the room is listed in its lobby.
  98. /// </summary>
  99. /// <remarks>
  100. /// Rooms can be created invisible, or changed to invisible.
  101. /// To change if a room can be joined, use property: open.
  102. ///
  103. /// As part of RoomInfo this can't be set.
  104. /// As part of a Room (which the player joined), the setter will update the server and all clients.
  105. /// </remarks>
  106. public new bool IsVisible
  107. {
  108. get
  109. {
  110. return this.isVisible;
  111. }
  112. set
  113. {
  114. if (value != this.isVisible)
  115. {
  116. if (!this.isOffline)
  117. {
  118. this.LoadBalancingClient.OpSetPropertiesOfRoom(new Hashtable() { { GamePropertyKey.IsVisible, value } });
  119. }
  120. }
  121. this.isVisible = value;
  122. }
  123. }
  124. /// <summary>
  125. /// Sets a limit of players to this room. This property is synced and shown in lobby, too.
  126. /// If the room is full (players count == maxplayers), joining this room will fail.
  127. /// </summary>
  128. /// <remarks>
  129. /// As part of RoomInfo this can't be set.
  130. /// As part of a Room (which the player joined), the setter will update the server and all clients.
  131. /// </remarks>
  132. public new int MaxPlayers
  133. {
  134. get
  135. {
  136. return this.maxPlayers;
  137. }
  138. set
  139. {
  140. if (value != this.maxPlayers)
  141. {
  142. byte maxPlayersPropValue = (byte)value; // TODO: when the server accepts int typed MaxPlayers, remove this
  143. if (!this.isOffline)
  144. {
  145. this.LoadBalancingClient.OpSetPropertiesOfRoom(new Hashtable() { { GamePropertyKey.MaxPlayers, maxPlayersPropValue } });
  146. }
  147. this.maxPlayers = maxPlayersPropValue;
  148. }
  149. }
  150. }
  151. /// <summary>The count of players in this Room (using this.Players.Count).</summary>
  152. public new int PlayerCount
  153. {
  154. get
  155. {
  156. if (this.Players == null)
  157. {
  158. return 0;
  159. }
  160. return (byte)this.Players.Count;
  161. }
  162. }
  163. /// <summary>While inside a Room, this is the list of players who are also in that room.</summary>
  164. private Dictionary<int, Player> players = new Dictionary<int, Player>();
  165. /// <summary>While inside a Room, this is the list of players who are also in that room.</summary>
  166. public Dictionary<int, Player> Players
  167. {
  168. get
  169. {
  170. return this.players;
  171. }
  172. private set
  173. {
  174. this.players = value;
  175. }
  176. }
  177. /// <summary>
  178. /// List of users who are expected to join this room. In matchmaking, Photon blocks a slot for each of these UserIDs out of the MaxPlayers.
  179. /// </summary>
  180. /// <remarks>
  181. /// The corresponding feature in Photon is called "Slot Reservation" and can be found in the doc pages.
  182. /// Define expected players in the methods: <see cref="LoadBalancingClient.OpCreateRoom"/>, <see cref="LoadBalancingClient.OpJoinRoom"/> and <see cref="LoadBalancingClient.OpJoinRandomRoom"/>.
  183. /// </remarks>
  184. public string[] ExpectedUsers
  185. {
  186. get { return this.expectedUsers; }
  187. }
  188. /// <summary>Player Time To Live. How long any player can be inactive (due to disconnect or leave) before the user gets removed from the playerlist (freeing a slot).</summary>
  189. public int PlayerTtl
  190. {
  191. get { return this.playerTtl; }
  192. set
  193. {
  194. if (value != this.playerTtl)
  195. {
  196. if (!this.isOffline)
  197. {
  198. this.LoadBalancingClient.OpSetPropertyOfRoom(GamePropertyKey.PlayerTtl, value); // TODO: implement Offline Mode
  199. }
  200. }
  201. this.playerTtl = value;
  202. }
  203. }
  204. /// <summary>Room Time To Live. How long a room stays available (and in server-memory), after the last player becomes inactive. After this time, the room gets persisted or destroyed.</summary>
  205. public int EmptyRoomTtl
  206. {
  207. get { return this.emptyRoomTtl; }
  208. set
  209. {
  210. if (value != this.emptyRoomTtl)
  211. {
  212. if (!this.isOffline)
  213. {
  214. this.LoadBalancingClient.OpSetPropertyOfRoom(GamePropertyKey.EmptyRoomTtl, value); // TODO: implement Offline Mode
  215. }
  216. }
  217. this.emptyRoomTtl = value;
  218. }
  219. }
  220. /// <summary>
  221. /// The ID (actorNumber, actorNumber) of the player who's the master of this Room.
  222. /// Note: This changes when the current master leaves the room.
  223. /// </summary>
  224. public int MasterClientId { get { return this.masterClientId; } }
  225. /// <summary>
  226. /// Gets a list of custom properties that are in the RoomInfo of the Lobby.
  227. /// This list is defined when creating the room and can't be changed afterwards. Compare: LoadBalancingClient.OpCreateRoom()
  228. /// </summary>
  229. /// <remarks>You could name properties that are not set from the beginning. Those will be synced with the lobby when added later on.</remarks>
  230. public string[] PropertiesListedInLobby
  231. {
  232. get
  233. {
  234. return this.propertiesListedInLobby;
  235. }
  236. private set
  237. {
  238. this.propertiesListedInLobby = value;
  239. }
  240. }
  241. /// <summary>
  242. /// Gets if this room cleans up the event cache when a player (actor) leaves.
  243. /// </summary>
  244. /// <remarks>
  245. /// This affects which events joining players get.
  246. ///
  247. /// Set in room creation via RoomOptions.CleanupCacheOnLeave.
  248. ///
  249. /// Within PUN, auto cleanup of events means that cached RPCs and instantiated networked objects are deleted from the room.
  250. /// </remarks>
  251. public bool AutoCleanUp
  252. {
  253. get
  254. {
  255. return this.autoCleanUp;
  256. }
  257. }
  258. /// <summary>Define if the client who calls SetProperties should receive the properties update event or not. </summary>
  259. public bool BroadcastPropertiesChangeToAll { get; private set; }
  260. /// <summary>Define if Join and Leave events should not be sent to clients in the room. </summary>
  261. public bool SuppressRoomEvents { get; private set; }
  262. /// <summary>Extends SuppressRoomEvents: Define if Join and Leave events but also the actors' list and their respective properties should not be sent to clients. </summary>
  263. public bool SuppressPlayerInfo { get; private set; }
  264. /// <summary>Define if UserIds of the players are broadcast in the room. Useful for FindFriends and reserving slots for expected users.</summary>
  265. public bool PublishUserId { get; private set; }
  266. /// <summary>Define if actor or room properties with null values are removed on the server or kept.</summary>
  267. public bool DeleteNullProperties { get; private set; }
  268. #if SERVERSDK
  269. /// <summary>Define if rooms should have unique UserId per actor and that UserIds are used instead of actor number in rejoin.</summary>
  270. public bool CheckUserOnJoin { get; private set; }
  271. #endif
  272. /// <summary>Creates a Room (representation) with given name and properties and the "listing options" as provided by parameters.</summary>
  273. /// <param name="roomName">Name of the room (can be null until it's actually created on server).</param>
  274. /// <param name="options">Room options.</param>
  275. public Room(string roomName, RoomOptions options, bool isOffline = false) : base(roomName, options != null ? options.CustomRoomProperties : null)
  276. {
  277. // base() sets name and (custom)properties. here we set "well known" properties
  278. if (options != null)
  279. {
  280. this.isVisible = options.IsVisible;
  281. this.isOpen = options.IsOpen;
  282. this.maxPlayers = options.MaxPlayers;
  283. this.propertiesListedInLobby = options.CustomRoomPropertiesForLobby;
  284. //this.playerTtl = options.PlayerTtl; // set via well known properties
  285. //this.emptyRoomTtl = options.EmptyRoomTtl; // set via well known properties
  286. }
  287. this.isOffline = isOffline;
  288. }
  289. /// <summary>Read (received) room option flags into related bool parameters.</summary>
  290. /// <remarks>This is for internal use. The operation response for join and create room operations is read this way.</remarks>
  291. /// <param name="roomFlags"></param>
  292. internal void InternalCacheRoomFlags(int roomFlags)
  293. {
  294. this.BroadcastPropertiesChangeToAll = (roomFlags & (int)RoomOptionBit.BroadcastPropsChangeToAll) != 0;
  295. this.SuppressRoomEvents = (roomFlags & (int)RoomOptionBit.SuppressRoomEvents) != 0;
  296. this.SuppressPlayerInfo = (roomFlags & (int)RoomOptionBit.SuppressPlayerInfo) != 0;
  297. this.PublishUserId = (roomFlags & (int)RoomOptionBit.PublishUserId) != 0;
  298. this.DeleteNullProperties = (roomFlags & (int)RoomOptionBit.DeleteNullProps) != 0;
  299. #if SERVERSDK
  300. this.CheckUserOnJoin = (roomFlags & (int)RoomOptionBit.CheckUserOnJoin) != 0;
  301. #endif
  302. this.autoCleanUp = (roomFlags & (int)RoomOptionBit.DeleteCacheOnLeave) != 0;
  303. }
  304. protected internal override void InternalCacheProperties(Hashtable propertiesToCache)
  305. {
  306. int oldMasterId = this.masterClientId;
  307. base.InternalCacheProperties(propertiesToCache); // important: updating the properties fields has no way to do callbacks on change
  308. if (oldMasterId != 0 && this.masterClientId != oldMasterId)
  309. {
  310. this.LoadBalancingClient.InRoomCallbackTargets.OnMasterClientSwitched(this.GetPlayer(this.masterClientId));
  311. }
  312. }
  313. /// <summary>
  314. /// Updates and synchronizes this Room's Custom Properties. Optionally, expectedProperties can be provided as condition.
  315. /// </summary>
  316. /// <remarks>
  317. /// Custom Properties are a set of string keys and arbitrary values which is synchronized
  318. /// for the players in a Room. They are available when the client enters the room, as
  319. /// they are in the response of OpJoin and OpCreate.
  320. ///
  321. /// Custom Properties either relate to the (current) Room or a Player (in that Room).
  322. ///
  323. /// Both classes locally cache the current key/values and make them available as
  324. /// property: CustomProperties. This is provided only to read them.
  325. /// You must use the method SetCustomProperties to set/modify them.
  326. ///
  327. /// Any client can set any Custom Properties anytime (when in a room).
  328. /// It's up to the game logic to organize how they are best used.
  329. ///
  330. /// You should call SetCustomProperties only with key/values that are new or changed. This reduces
  331. /// traffic and performance.
  332. ///
  333. /// Unless you define some expectedProperties, setting key/values is always permitted.
  334. /// In this case, the property-setting client will not receive the new values from the server but
  335. /// instead update its local cache in SetCustomProperties.
  336. ///
  337. /// If you define expectedProperties, the server will skip updates if the server property-cache
  338. /// does not contain all expectedProperties with the same values.
  339. /// In this case, the property-setting client will get an update from the server and update it's
  340. /// cached key/values at about the same time as everyone else.
  341. ///
  342. /// The benefit of using expectedProperties can be only one client successfully sets a key from
  343. /// one known value to another.
  344. /// As example: Store who owns an item in a Custom Property "ownedBy". It's 0 initally.
  345. /// When multiple players reach the item, they all attempt to change "ownedBy" from 0 to their
  346. /// actorNumber. If you use expectedProperties {"ownedBy", 0} as condition, the first player to
  347. /// take the item will have it (and the others fail to set the ownership).
  348. ///
  349. /// Properties get saved with the game state for Turnbased games (which use IsPersistent = true).
  350. /// </remarks>
  351. /// <param name="propertiesToSet">Hashtable of Custom Properties that changes.</param>
  352. /// <param name="expectedProperties">Provide some keys/values to use as condition for setting the new values. Client must be in room.</param>
  353. /// <param name="webFlags">Defines if this SetCustomProperties-operation gets forwarded to your WebHooks. Client must be in room.</param>
  354. /// <returns>
  355. /// False if propertiesToSet is null or empty or have zero string keys.
  356. /// True in offline mode even if expectedProperties or webFlags are used.
  357. /// Otherwise, returns if this operation could be sent to the server.
  358. /// </returns>
  359. public virtual bool SetCustomProperties(Hashtable propertiesToSet, Hashtable expectedProperties = null, WebFlags webFlags = null)
  360. {
  361. if (propertiesToSet == null || propertiesToSet.Count == 0)
  362. {
  363. return false;
  364. }
  365. Hashtable customProps = propertiesToSet.StripToStringKeys() as Hashtable;
  366. if (this.isOffline)
  367. {
  368. if (customProps.Count == 0)
  369. {
  370. return false;
  371. }
  372. // Merge and delete values.
  373. this.CustomProperties.Merge(customProps);
  374. this.CustomProperties.StripKeysWithNullValues();
  375. // invoking callbacks
  376. this.LoadBalancingClient.InRoomCallbackTargets.OnRoomPropertiesUpdate(propertiesToSet);
  377. }
  378. else
  379. {
  380. // send (sync) these new values if in online room
  381. return this.LoadBalancingClient.OpSetPropertiesOfRoom(customProps, expectedProperties, webFlags);
  382. }
  383. return true;
  384. }
  385. /// <summary>
  386. /// Enables you to define the properties available in the lobby if not all properties are needed to pick a room.
  387. /// </summary>
  388. /// <remarks>
  389. /// Limit the amount of properties sent to users in the lobby to improve speed and stability.
  390. /// </remarks>
  391. /// <param name="lobbyProps">An array of custom room property names to forward to the lobby.</param>
  392. /// <returns>If the operation could be sent to the server.</returns>
  393. public bool SetPropertiesListedInLobby(string[] lobbyProps)
  394. {
  395. if (this.isOffline)
  396. {
  397. return false;
  398. }
  399. Hashtable customProps = new Hashtable();
  400. customProps[GamePropertyKey.PropsListedInLobby] = lobbyProps;
  401. return this.LoadBalancingClient.OpSetPropertiesOfRoom(customProps);
  402. }
  403. /// <summary>
  404. /// Removes a player from this room's Players Dictionary.
  405. /// This is internally used by the LoadBalancing API. There is usually no need to remove players yourself.
  406. /// This is not a way to "kick" players.
  407. /// </summary>
  408. protected internal virtual void RemovePlayer(Player player)
  409. {
  410. this.Players.Remove(player.ActorNumber);
  411. player.RoomReference = null;
  412. }
  413. /// <summary>
  414. /// Removes a player from this room's Players Dictionary.
  415. /// </summary>
  416. protected internal virtual void RemovePlayer(int id)
  417. {
  418. this.RemovePlayer(this.GetPlayer(id));
  419. }
  420. /// <summary>
  421. /// Asks the server to assign another player as Master Client of your current room.
  422. /// </summary>
  423. /// <remarks>
  424. /// RaiseEvent has the option to send messages only to the Master Client of a room.
  425. /// SetMasterClient affects which client gets those messages.
  426. ///
  427. /// This method calls an operation on the server to set a new Master Client, which takes a roundtrip.
  428. /// In case of success, this client and the others get the new Master Client from the server.
  429. ///
  430. /// SetMasterClient tells the server which current Master Client should be replaced with the new one.
  431. /// It will fail, if anything switches the Master Client moments earlier. There is no callback for this
  432. /// error. All clients should get the new Master Client assigned by the server anyways.
  433. ///
  434. /// See also: MasterClientId
  435. /// </remarks>
  436. /// <param name="masterClientPlayer">The player to become the next Master Client.</param>
  437. /// <returns>False when this operation couldn't be done currently. Requires a v4 Photon Server.</returns>
  438. public bool SetMasterClient(Player masterClientPlayer)
  439. {
  440. if (this.isOffline)
  441. {
  442. return false;
  443. }
  444. Hashtable newProps = new Hashtable() { { GamePropertyKey.MasterClientId, masterClientPlayer.ActorNumber } };
  445. Hashtable prevProps = new Hashtable() { { GamePropertyKey.MasterClientId, this.MasterClientId } };
  446. return this.LoadBalancingClient.OpSetPropertiesOfRoom(newProps, prevProps);
  447. }
  448. /// <summary>
  449. /// Checks if the player is in the room's list already and calls StorePlayer() if not.
  450. /// </summary>
  451. /// <param name="player">The new player - identified by ID.</param>
  452. /// <returns>False if the player could not be added (cause it was in the list already).</returns>
  453. public virtual bool AddPlayer(Player player)
  454. {
  455. if (!this.Players.ContainsKey(player.ActorNumber))
  456. {
  457. this.StorePlayer(player);
  458. return true;
  459. }
  460. return false;
  461. }
  462. /// <summary>
  463. /// Updates a player reference in the Players dictionary (no matter if it existed before or not).
  464. /// </summary>
  465. /// <param name="player">The Player instance to insert into the room.</param>
  466. public virtual Player StorePlayer(Player player)
  467. {
  468. this.Players[player.ActorNumber] = player;
  469. player.RoomReference = this;
  470. //// while initializing the room, the players are not guaranteed to be added in-order
  471. //if (this.MasterClientId == 0 || player.ActorNumber < this.MasterClientId)
  472. //{
  473. // this.masterClientId = player.ActorNumber;
  474. //}
  475. return player;
  476. }
  477. /// <summary>
  478. /// Tries to find the player with given actorNumber (a.k.a. ID).
  479. /// Only useful when in a Room, as IDs are only valid per Room.
  480. /// </summary>
  481. /// <param name="id">ID to look for.</param>
  482. /// <param name="findMaster">If true, the Master Client is returned for ID == 0.</param>
  483. /// <returns>The player with the ID or null.</returns>
  484. public virtual Player GetPlayer(int id, bool findMaster = false)
  485. {
  486. int idToFind = (findMaster && id == 0) ? this.MasterClientId : id;
  487. Player result = null;
  488. this.Players.TryGetValue(idToFind, out result);
  489. return result;
  490. }
  491. /// <summary>
  492. /// Attempts to remove all current expected users from the server's Slot Reservation list.
  493. /// </summary>
  494. /// <remarks>
  495. /// Note that this operation can conflict with new/other users joining. They might be
  496. /// adding users to the list of expected users before or after this client called ClearExpectedUsers.
  497. ///
  498. /// This room's expectedUsers value will update, when the server sends a successful update.
  499. ///
  500. /// Internals: This methods wraps up setting the ExpectedUsers property of a room.
  501. /// </remarks>
  502. /// <returns>If the operation could be sent to the server.</returns>
  503. public bool ClearExpectedUsers()
  504. {
  505. if (this.ExpectedUsers == null || this.ExpectedUsers.Length == 0)
  506. {
  507. return false;
  508. }
  509. return this.SetExpectedUsers(new string[0], this.ExpectedUsers);
  510. }
  511. /// <summary>
  512. /// Attempts to update the expected users from the server's Slot Reservation list.
  513. /// </summary>
  514. /// <remarks>
  515. /// Note that this operation can conflict with new/other users joining. They might be
  516. /// adding users to the list of expected users before or after this client called SetExpectedUsers.
  517. ///
  518. /// This room's expectedUsers value will update, when the server sends a successful update.
  519. ///
  520. /// Internals: This methods wraps up setting the ExpectedUsers property of a room.
  521. /// </remarks>
  522. /// <param name="newExpectedUsers">The new array of UserIDs to be reserved in the room.</param>
  523. /// <returns>If the operation could be sent to the server.</returns>
  524. public bool SetExpectedUsers(string[] newExpectedUsers)
  525. {
  526. if (newExpectedUsers == null || newExpectedUsers.Length == 0)
  527. {
  528. this.LoadBalancingClient.DebugReturn(DebugLevel.ERROR, "newExpectedUsers array is null or empty, call Room.ClearExpectedUsers() instead if this is what you want.");
  529. return false;
  530. }
  531. return this.SetExpectedUsers(newExpectedUsers, this.ExpectedUsers);
  532. }
  533. private bool SetExpectedUsers(string[] newExpectedUsers, string[] oldExpectedUsers)
  534. {
  535. if (this.isOffline)
  536. {
  537. return false;
  538. }
  539. Hashtable gameProperties = new Hashtable(1);
  540. gameProperties.Add(GamePropertyKey.ExpectedUsers, newExpectedUsers);
  541. Hashtable expectedProperties = null;
  542. if (oldExpectedUsers != null)
  543. {
  544. expectedProperties = new Hashtable(1);
  545. expectedProperties.Add(GamePropertyKey.ExpectedUsers, oldExpectedUsers);
  546. }
  547. return this.LoadBalancingClient.OpSetPropertiesOfRoom(gameProperties, expectedProperties);
  548. }
  549. /// <summary>Returns a summary of this Room instance as string.</summary>
  550. /// <returns>Summary of this Room instance.</returns>
  551. public override string ToString()
  552. {
  553. return string.Format("Room: '{0}' {1},{2} {4}/{3} players.", this.name, this.isVisible ? "visible" : "hidden", this.isOpen ? "open" : "closed", this.maxPlayers, this.PlayerCount);
  554. }
  555. /// <summary>Returns a summary of this Room instance as longer string, including Custom Properties.</summary>
  556. /// <returns>Summary of this Room instance.</returns>
  557. public new string ToStringFull()
  558. {
  559. return string.Format("Room: '{0}' {1},{2} {4}/{3} players.\ncustomProps: {5}", this.name, this.isVisible ? "visible" : "hidden", this.isOpen ? "open" : "closed", this.maxPlayers, this.PlayerCount, this.CustomProperties.ToStringFull());
  560. }
  561. }
  562. }