// ---------------------------------------------------------------------------- // // PhotonNetwork Framework for Unity - Copyright (C) 2011 Exit Games GmbH // // // Represents a room/game on the server and caches the properties of that. // // developer@exitgames.com // ---------------------------------------------------------------------------- using System; using ExitGames.Client.Photon; using UnityEngine; /// /// This class resembles a room that PUN joins (or joined). /// The properties are settable as opposed to those of a RoomInfo and you can close or hide "your" room. /// /// \ingroup publicApi public class Room : RoomInfo { /// The name of a room. Unique identifier (per Loadbalancing group) for a room/match. public new string Name { get { return this.nameField; } internal set { this.nameField = value; } } /// /// Defines if the room can be joined. /// This does not affect listing in a lobby but joining the room will fail if not open. /// If not open, the room is excluded from random matchmaking. /// Due to racing conditions, found matches might become closed before they are joined. /// Simply re-connect to master and find another. /// Use property "visible" to not list the room. /// public new bool IsOpen { get { return this.openField; } set { if (!this.Equals(PhotonNetwork.room)) { UnityEngine.Debug.LogWarning("Can't set open when not in that room."); } if (value != this.openField && !PhotonNetwork.offlineMode) { PhotonNetwork.networkingPeer.OpSetPropertiesOfRoom(new Hashtable() { { GamePropertyKey.IsOpen, value } }, expectedProperties: null, webForward: false); } this.openField = value; } } /// /// Defines if the room is listed in its lobby. /// Rooms can be created invisible, or changed to invisible. /// To change if a room can be joined, use property: open. /// public new bool IsVisible { get { return this.visibleField; } set { if (!this.Equals(PhotonNetwork.room)) { UnityEngine.Debug.LogWarning("Can't set visible when not in that room."); } if (value != this.visibleField && !PhotonNetwork.offlineMode) { PhotonNetwork.networkingPeer.OpSetPropertiesOfRoom(new Hashtable() { { GamePropertyKey.IsVisible, value } }, expectedProperties: null, webForward: false); } this.visibleField = value; } } /// /// A list of custom properties that should be forwarded to the lobby and listed there. /// public string[] PropertiesListedInLobby { get; private set; } /// /// Gets if this room uses autoCleanUp to remove all (buffered) RPCs and instantiated GameObjects when a player leaves. /// public bool AutoCleanUp { get { return this.autoCleanUpField; } } /// /// Sets a limit of players to this room. This property is shown in lobby, too. /// If the room is full (players count == maxplayers), joining this room will fail. /// public new int MaxPlayers { get { return (int)this.maxPlayersField; } set { if (!this.Equals(PhotonNetwork.room)) { UnityEngine.Debug.LogWarning("Can't set MaxPlayers when not in that room."); } if (value > 255) { UnityEngine.Debug.LogWarning("Can't set Room.MaxPlayers to: " + value + ". Using max value: 255."); value = 255; } if (value != this.maxPlayersField && !PhotonNetwork.offlineMode) { PhotonNetwork.networkingPeer.OpSetPropertiesOfRoom(new Hashtable() { { GamePropertyKey.MaxPlayers, (byte)value } }, expectedProperties: null, webForward: false); } this.maxPlayersField = (byte)value; } } /// Count of players in this room. public new int PlayerCount { get { if (PhotonNetwork.playerList != null) { return PhotonNetwork.playerList.Length; } else { return 0; } } } /// /// 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. /// /// /// The corresponding feature in Photon is called "Slot Reservation" and can be found in the doc pages. /// Define expected players in the PhotonNetwork methods: CreateRoom, JoinRoom and JoinOrCreateRoom. /// public string[] ExpectedUsers { get { return this.expectedUsersField; } } /// The ID (actorNumber) of the current Master Client of this room. /// See also: PhotonNetwork.masterClient. protected internal int MasterClientId { get { return this.masterClientIdField; } set { this.masterClientIdField = value; } } internal Room(string roomName, RoomOptions options) : base(roomName, null) { if (options == null) { options = new RoomOptions(); } this.visibleField = options.IsVisible; this.openField = options.IsOpen; this.maxPlayersField = (byte)options.MaxPlayers; this.autoCleanUpField = false; // defaults to false, unless set to true when room gets created. this.InternalCacheProperties(options.CustomRoomProperties); this.PropertiesListedInLobby = options.CustomRoomPropertiesForLobby; } /// /// Updates the current room's Custom Properties with new/updated key-values. /// /// /// Custom Properties are a key-value set (Hashtable) which is available to all players in a room. /// They can relate to the room or individual players and are useful when only the current value /// of something is of interest. For example: The map of a room. /// All keys must be strings. /// /// The Room and the PhotonPlayer class both have SetCustomProperties methods. /// Also, both classes offer access to current key-values by: customProperties. /// /// Always use SetCustomProperties to change values. /// To reduce network traffic, set only values that actually changed. /// New properties are added, existing values are updated. /// Other values will not be changed, so only provide values that changed or are new. /// /// To delete a named (custom) property of this room, use null as value. /// /// Locally, SetCustomProperties will update it's cache without delay. /// Other clients are updated through Photon (the server) with a fitting operation. /// /// Check and Swap /// /// SetCustomProperties have the option to do a server-side Check-And-Swap (CAS): /// Values only get updated if the expected values are correct. /// The expectedValues can be different key/values than the propertiesToSet. So you can /// check some key and set another key's value (if the check succeeds). /// /// If the client's knowledge of properties is wrong or outdated, it can't set values with CAS. /// This can be useful to keep players from concurrently setting values. For example: If all players /// try to pickup some card or item, only one should get it. With CAS, only the first SetProperties /// gets executed server-side and any other (sent at the same time) fails. /// /// The server will broadcast successfully changed values and the local "cache" of customProperties /// only gets updated after a roundtrip (if anything changed). /// /// You can do a "webForward": Photon will send the changed properties to a WebHook defined /// for your application. /// /// OfflineMode /// /// While PhotonNetwork.offlineMode is true, the expectedValues and webForward parameters are ignored. /// In OfflineMode, the local customProperties values are immediately updated (without the roundtrip). /// /// The new properties to be set. /// At least one property key/value set to check server-side. Key and value must be correct. Ignored in OfflineMode. /// Set to true, to forward the set properties to a WebHook, defined for this app (in Dashboard). Ignored in OfflineMode. public void SetCustomProperties(Hashtable propertiesToSet, Hashtable expectedValues = null, bool webForward = false) { if (propertiesToSet == null) { return; } Hashtable customProps = propertiesToSet.StripToStringKeys() as Hashtable; Hashtable customPropsToCheck = expectedValues.StripToStringKeys() as Hashtable; // no expected values -> set and callback bool noCas = customPropsToCheck == null || customPropsToCheck.Count == 0; if (noCas) { this.CustomProperties.Merge(customProps); this.CustomProperties.StripKeysWithNullValues(); } if (!PhotonNetwork.offlineMode) { PhotonNetwork.networkingPeer.OpSetPropertiesOfRoom(customProps, customPropsToCheck, webForward); } if (PhotonNetwork.offlineMode || noCas) { this.InternalCacheProperties(customProps); NetworkingPeer.SendMonoMessage(PhotonNetworkingMessage.OnPhotonCustomRoomPropertiesChanged, customProps); } } /// /// Enables you to define the properties available in the lobby if not all properties are needed to pick a room. /// /// /// It makes sense to limit the amount of properties sent to users in the lobby as this improves speed and stability. /// /// An array of custom room property names to forward to the lobby. public void SetPropertiesListedInLobby(string[] propsListedInLobby) { Hashtable customProps = new Hashtable(); customProps[GamePropertyKey.PropsListedInLobby] = propsListedInLobby; PhotonNetwork.networkingPeer.OpSetPropertiesOfRoom(customProps, expectedProperties: null, webForward: false); this.PropertiesListedInLobby = propsListedInLobby; } /// /// Attempts to remove all current expected users from the server's Slot Reservation list. /// /// /// Note that this operation can conflict with new/other users joining. They might be /// adding users to the list of expected users before or after this client called ClearExpectedUsers. /// /// This room's expectedUsers value will update, when the server sends a successful update. /// /// Internals: This methods wraps up setting the ExpectedUsers property of a room. /// public void ClearExpectedUsers() { Hashtable props = new Hashtable(); props[GamePropertyKey.ExpectedUsers] = new string[0]; Hashtable expected = new Hashtable(); expected[GamePropertyKey.ExpectedUsers] = this.ExpectedUsers; PhotonNetwork.networkingPeer.OpSetPropertiesOfRoom(props, expected, webForward: false); } /// Returns a summary of this Room instance as string. /// Summary of this Room instance. public override string ToString() { return string.Format("Room: '{0}' {1},{2} {4}/{3} players.", this.nameField, this.visibleField ? "visible" : "hidden", this.openField ? "open" : "closed", this.maxPlayersField, this.PlayerCount); } /// Returns a summary of this Room instance as longer string, including Custom Properties. /// Summary of this Room instance. public new string ToStringFull() { return string.Format("Room: '{0}' {1},{2} {4}/{3} players.\ncustomProps: {5}", this.nameField, this.visibleField ? "visible" : "hidden", this.openField ? "open" : "closed", this.maxPlayersField, this.PlayerCount, this.CustomProperties.ToStringFull()); } #region Obsoleted variable names [Obsolete("Please use Name (updated case for naming).")] public new string name { get { return this.Name; } internal set { this.Name = value; } } [Obsolete("Please use IsOpen (updated case for naming).")] public new bool open { get { return this.IsOpen; } set { this.IsOpen = value; } } [Obsolete("Please use IsVisible (updated case for naming).")] public new bool visible { get { return this.IsVisible; } set { this.IsVisible = value; } } [Obsolete("Please use PropertiesListedInLobby (updated case for naming).")] public string[] propertiesListedInLobby { get { return this.PropertiesListedInLobby; } private set { this.PropertiesListedInLobby = value; } } [Obsolete("Please use AutoCleanUp (updated case for naming).")] public bool autoCleanUp { get { return this.AutoCleanUp; } } [Obsolete("Please use MaxPlayers (updated case for naming).")] public new int maxPlayers { get { return this.MaxPlayers; } set { this.MaxPlayers = value; } } [Obsolete("Please use PlayerCount (updated case for naming).")] public new int playerCount { get { return this.PlayerCount; } } [Obsolete("Please use ExpectedUsers (updated case for naming).")] public string[] expectedUsers { get { return this.ExpectedUsers; } } [Obsolete("Please use MasterClientId (updated case for naming).")] protected internal int masterClientId { get { return this.MasterClientId; } set { this.MasterClientId = value; } } #endregion }