using System; using System.Collections.Generic; using System.Text; using SteamNative; namespace Facepunch.Steamworks { public partial class Client : IDisposable { Lobby _lobby; public Lobby Lobby { get { if ( _lobby == null ) _lobby = new Steamworks.Lobby( this ); return _lobby; } } } public partial class Lobby : IDisposable { //The type of lobby you are creating public enum Type : int { Private = SteamNative.LobbyType.Private, FriendsOnly = SteamNative.LobbyType.FriendsOnly, Public = SteamNative.LobbyType.Public, Invisible = SteamNative.LobbyType.Invisible, Error //happens if you try to get this when you aren't in a valid lobby } internal Client client; public Lobby( Client c ) { client = c; // For backwards compatibility OnLobbyJoinRequested = Join; client.RegisterCallback( OnLobbyDataUpdatedAPI ); client.RegisterCallback( OnLobbyChatMessageRecievedAPI ); client.RegisterCallback( OnLobbyStateUpdatedAPI ); client.RegisterCallback( OnLobbyJoinRequestedAPI ); client.RegisterCallback( OnUserInvitedToLobbyAPI ); client.RegisterCallback( OnLobbyMemberPersonaChangeAPI ); } /// /// The CSteamID of the lobby we're currently in. /// public ulong CurrentLobby { get; private set; } /// /// The LobbyData of the CurrentLobby. Note this is the global data for the lobby. Use SetMemberData to set specific member data. /// public LobbyData CurrentLobbyData { get; private set; } /// /// Returns true if this lobby is valid, ie, we've succesffuly created and/or joined a lobby. /// public bool IsValid => CurrentLobby != 0; /// /// Join a Lobby through its LobbyID. OnLobbyJoined is called with the result of the Join attempt. /// /// CSteamID of lobby to join public void Join( ulong lobbyID ) { Leave(); client.native.matchmaking.JoinLobby( lobbyID, OnLobbyJoinedAPI ); } void OnLobbyJoinedAPI( LobbyEnter_t callback, bool error ) { if ( error || (callback.EChatRoomEnterResponse != (uint)(SteamNative.ChatRoomEnterResponse.Success)) ) { if ( OnLobbyJoined != null ) { OnLobbyJoined( false ); } return; } CurrentLobby = callback.SteamIDLobby; UpdateLobbyData(); if ( OnLobbyJoined != null ) { OnLobbyJoined( true ); } } /// /// Called when a lobby has been attempted joined. Returns true if lobby was successfuly joined, false if not. /// public Action OnLobbyJoined; /// /// Creates a lobby and returns the created lobby. You auto join the created lobby. The lobby is stored in Client.Lobby.CurrentLobby if successful. /// /// The Lobby.Type of Lobby to be created /// The maximum amount of people you want to be able to be in this lobby, including yourself public void Create( Lobby.Type lobbyType, int maxMembers ) { client.native.matchmaking.CreateLobby( (SteamNative.LobbyType)lobbyType, maxMembers, OnLobbyCreatedAPI ); createdLobbyType = lobbyType; } internal Type createdLobbyType; internal void OnLobbyCreatedAPI( LobbyCreated_t callback, bool error ) { //from SpaceWarClient.cpp 793 if ( error || (callback.Result != Result.OK) ) { if ( OnLobbyCreated != null ) { OnLobbyCreated( false ); } return; } //set owner specific properties Owner = client.SteamId; CurrentLobby = callback.SteamIDLobby; CurrentLobbyData = new LobbyData( client, CurrentLobby ); Name = client.Username + "'s Lobby"; CurrentLobbyData.SetData( "appid", client.AppId.ToString() ); LobbyType = createdLobbyType; CurrentLobbyData.SetData( "lobbytype", LobbyType.ToString() ); Joinable = true; if ( OnLobbyCreated != null ) { OnLobbyCreated( true ); } } /// /// Callback for when lobby is created. Parameter resolves true when the Lobby was successfully created /// public Action OnLobbyCreated; /// /// Sets user data for the Lobby. Things like Character, Skin, Ready, etc. Can only set your own member data /// public void SetMemberData( string key, string value ) { if ( CurrentLobby == 0 ) { return; } client.native.matchmaking.SetLobbyMemberData( CurrentLobby, key, value ); } /// /// Get the per-user metadata from this lobby. Can get data from any user /// /// ulong SteamID of the user you want to get data from /// String key of the type of data you want to get /// public string GetMemberData( ulong steamID, string key ) { if ( CurrentLobby == 0 ) { return "ERROR: NOT IN ANY LOBBY"; } return client.native.matchmaking.GetLobbyMemberData( CurrentLobby, steamID, key ); } internal void OnLobbyDataUpdatedAPI( LobbyDataUpdate_t callback ) { if ( callback.SteamIDLobby != CurrentLobby ) return; if ( callback.SteamIDLobby == CurrentLobby ) //actual lobby data was updated by owner { UpdateLobbyData(); } if ( UserIsInCurrentLobby( callback.SteamIDMember ) ) //some member of this lobby updated their information { if ( OnLobbyMemberDataUpdated != null ) { OnLobbyMemberDataUpdated( callback.SteamIDMember ); } } } /// /// Updates the LobbyData property to have the data for the current lobby, if any /// internal void UpdateLobbyData() { int dataCount = client.native.matchmaking.GetLobbyDataCount( CurrentLobby ); CurrentLobbyData = new LobbyData( client, CurrentLobby ); for ( int i = 0; i < dataCount; i++ ) { if ( client.native.matchmaking.GetLobbyDataByIndex( CurrentLobby, i, out string key, out string value ) ) { CurrentLobbyData.SetData( key, value ); } } if ( OnLobbyDataUpdated != null ) { OnLobbyDataUpdated(); } } /// /// Called when the lobby data itself has been updated. Called when someone has joined/left, Owner has updated data, etc. /// public Action OnLobbyDataUpdated; /// /// Called when a member of the lobby has updated either their personal Lobby metadata or someone's global steam state has changed (like a display name). Parameter is the user who changed. /// public Action OnLobbyMemberDataUpdated; public Type LobbyType { get { if ( !IsValid ) { return Type.Error; } //if we're currently in a valid server //we know that we've set the lobby type via the lobbydata in the creation function //ps this is important because steam doesn't have an easy way to get lobby type (why idk) string lobbyType = CurrentLobbyData.GetData( "lobbytype" ); switch ( lobbyType ) { case "Private": return Type.Private; case "FriendsOnly": return Type.FriendsOnly; case "Invisible": return Type.Invisible; case "Public": return Type.Public; default: return Type.Error; } } set { if ( !IsValid ) { return; } if ( client.native.matchmaking.SetLobbyType( CurrentLobby, (SteamNative.LobbyType)value ) ) { CurrentLobbyData.SetData( "lobbytype", value.ToString() ); } } } private static byte[] chatMessageData = new byte[1024 * 4]; private unsafe void OnLobbyChatMessageRecievedAPI( LobbyChatMsg_t callback ) { //from Client.Networking if ( callback.SteamIDLobby != CurrentLobby ) return; SteamNative.CSteamID steamid = 1; ChatEntryType chatEntryType; // "If set then this will just always return k_EChatEntryTypeChatMsg. This can usually just be set to NULL." int readData = 0; fixed ( byte* p = chatMessageData ) { readData = client.native.matchmaking.GetLobbyChatEntry( CurrentLobby, (int)callback.ChatID, out steamid, (IntPtr)p, chatMessageData.Length, out chatEntryType ); } OnChatMessageRecieved?.Invoke( steamid, chatMessageData, readData ); if ( readData > 0 ) { OnChatStringRecieved?.Invoke( steamid, Encoding.UTF8.GetString( chatMessageData, 0, readData ) ); } } /// /// Callback to get chat messages. Use Encoding.UTF8.GetString to retrive the message. /// public Action OnChatMessageRecieved; /// /// Like OnChatMessageRecieved but the data is converted to a string /// public Action OnChatStringRecieved; /// /// Broadcasts a chat message to the all the users in the lobby users in the lobby (including the local user) will receive a LobbyChatMsg_t callback. /// /// True if message successfully sent public unsafe bool SendChatMessage( string message ) { var data = Encoding.UTF8.GetBytes( message ); fixed ( byte* p = data ) { // pvMsgBody can be binary or text data, up to 4k // if pvMsgBody is text, cubMsgBody should be strlen( text ) + 1, to include the null terminator return client.native.matchmaking.SendLobbyChatMsg( CurrentLobby, (IntPtr)p, data.Length ); } } /// /// Enums to catch the state of a user when their state has changed /// public enum MemberStateChange { Entered = ChatMemberStateChange.Entered, Left = ChatMemberStateChange.Left, Disconnected = ChatMemberStateChange.Disconnected, Kicked = ChatMemberStateChange.Kicked, Banned = ChatMemberStateChange.Banned, } internal void OnLobbyStateUpdatedAPI( LobbyChatUpdate_t callback ) { if ( callback.SteamIDLobby != CurrentLobby ) return; MemberStateChange change = (MemberStateChange)callback.GfChatMemberStateChange; ulong initiator = callback.SteamIDMakingChange; ulong affected = callback.SteamIDUserChanged; OnLobbyStateChanged?.Invoke( change, initiator, affected ); } /// /// Called when the state of the Lobby is somehow shifted. Usually when someone joins or leaves the lobby. /// The first ulong is the SteamID of the user that initiated the change. /// The second ulong is the person that was affected /// public Action OnLobbyStateChanged; /// /// The name of the lobby as a property for easy getting/setting. Note that this is setting LobbyData, which you cannot do unless you are the Owner of the lobby /// public string Name { get { if ( !IsValid ) { return ""; } return CurrentLobbyData.GetData( "name" ); } set { if ( !IsValid ) { return; } CurrentLobbyData.SetData( "name", value ); } } /// /// returns true if we're the current owner /// public bool IsOwner { get { return Owner == client.SteamId; } } /// /// The Owner of the current lobby. Returns 0 if you are not in a valid lobby. /// public ulong Owner { get { if ( IsValid ) { return client.native.matchmaking.GetLobbyOwner( CurrentLobby ); } return 0; } set { if ( Owner == value ) return; client.native.matchmaking.SetLobbyOwner( CurrentLobby, value ); } } /// /// Is the Lobby joinable by other people? Defaults to true; /// public bool Joinable { get { if ( !IsValid ) { return false; } string joinable = CurrentLobbyData.GetData( "joinable" ); switch ( joinable ) { case "true": return true; case "false": return false; default: return false; } } set { if ( !IsValid ) { return; } if ( client.native.matchmaking.SetLobbyJoinable( CurrentLobby, value ) ) { CurrentLobbyData.SetData( "joinable", value.ToString() ); } } } /// /// How many people can be in the Lobby /// public int MaxMembers { get { if ( !IsValid ) { return 0; } //0 is default, but value is inited when lobby is created. return client.native.matchmaking.GetLobbyMemberLimit( CurrentLobby ); } set { if ( !IsValid ) { return; } client.native.matchmaking.SetLobbyMemberLimit( CurrentLobby, value ); } } /// /// How many people are currently in the Lobby /// public int NumMembers { get { return client.native.matchmaking.GetNumLobbyMembers( CurrentLobby ); } } /// /// Leave the CurrentLobby. /// public void Leave() { if ( CurrentLobby != 0 ) { client.native.matchmaking.LeaveLobby( CurrentLobby ); } CurrentLobby = 0; CurrentLobbyData = null; } public void Dispose() { client = null; } /// /// Get an array of all the CSteamIDs in the CurrentLobby. /// Note that you must be in the Lobby you are trying to request the MemberIDs from. /// Returns an empty array if you aren't in a lobby. /// /// Array of member SteamIDs public ulong[] GetMemberIDs() { ulong[] memIDs = new ulong[NumMembers]; for ( int i = 0; i < NumMembers; i++ ) { memIDs[i] = client.native.matchmaking.GetLobbyMemberByIndex( CurrentLobby, i ); } return memIDs; } /// /// Check to see if a user is in your CurrentLobby /// /// SteamID of the user to check for /// public bool UserIsInCurrentLobby( ulong steamID ) { if ( CurrentLobby == 0 ) return false; ulong[] mems = GetMemberIDs(); for ( int i = 0; i < mems.Length; i++ ) { if ( mems[i] == steamID ) return true; } return false; } /// /// Invites the specified user to the CurrentLobby the user is in. /// /// ulong ID of person to invite public bool InviteUserToLobby( ulong friendID ) { return client.native.matchmaking.InviteUserToLobby( CurrentLobby, friendID ); } internal void OnUserInvitedToLobbyAPI( LobbyInvite_t callback ) { if ( callback.GameID != client.AppId ) return; if ( OnUserInvitedToLobby != null ) { OnUserInvitedToLobby( callback.SteamIDLobby, callback.SteamIDUser ); } } /// /// Activates the steam overlay to invite friends to the CurrentLobby the user is in. /// public void OpenFriendInviteOverlay() { client.native.friends.ActivateGameOverlayInviteDialog(CurrentLobby); } /// /// Called when a user invites the current user to a lobby. The first parameter is the lobby the user was invited to, the second is the CSteamID of the person who invited this user /// public Action OnUserInvitedToLobby; /// /// Called when a user accepts an invitation to a lobby while the game is running. The parameter is a lobby id. /// public Action OnLobbyJoinRequested; /// /// Joins a lobby if a request was made to join the lobby through the friends list or an invite /// internal void OnLobbyJoinRequestedAPI( GameLobbyJoinRequested_t callback ) { if (OnLobbyJoinRequested != null) { OnLobbyJoinRequested(callback.SteamIDLobby); } } /// /// Makes sure we send an update callback if a Lobby user updates their information /// internal void OnLobbyMemberPersonaChangeAPI( PersonaStateChange_t callback ) { if ( !UserIsInCurrentLobby( callback.SteamID ) ) return; if ( OnLobbyMemberDataUpdated != null ) { OnLobbyMemberDataUpdated( callback.SteamID ); } } /// /// Sets the game server associated with the lobby. /// This can only be set by the owner of the lobby. /// Either the IP/Port or the Steam ID of the game server must be valid, depending on how you want the clients to be able to connect. /// public bool SetGameServer( System.Net.IPAddress ip, int port, ulong serverSteamId = 0 ) { if ( !IsValid || !IsOwner ) return false; var ipint = System.Net.IPAddress.NetworkToHostOrder( ip.Address ); client.native.matchmaking.SetLobbyGameServer( CurrentLobby, (uint)ipint, (ushort)port, serverSteamId ); return true; } /// /// Gets the details of a game server set in a lobby. /// public System.Net.IPAddress GameServerIp { get { uint ip; ushort port; CSteamID steamid; if ( !client.native.matchmaking.GetLobbyGameServer( CurrentLobby, out ip, out port, out steamid ) || ip == 0 ) return null; return new System.Net.IPAddress( System.Net.IPAddress.HostToNetworkOrder( ip ) ); } } /// /// Gets the details of a game server set in a lobby. /// public int GameServerPort { get { uint ip; ushort port; CSteamID steamid; if ( !client.native.matchmaking.GetLobbyGameServer( CurrentLobby, out ip, out port, out steamid ) ) return 0; return (int)port; } } /// /// Gets the details of a game server set in a lobby. /// public ulong GameServerSteamId { get { uint ip; ushort port; CSteamID steamid; if ( !client.native.matchmaking.GetLobbyGameServer( CurrentLobby, out ip, out port, out steamid ) ) return 0; return steamid; } } /*not implemented //set the game server of the lobby client.native.matchmaking.GetLobbyGameServer; client.native.matchmaking.SetLobbyGameServer; //used with game server stuff SteamNative.LobbyGameCreated_t */ } }