diff --git a/Facepunch.Steamworks.Test/Client/Lobby.cs b/Facepunch.Steamworks.Test/Client/Lobby.cs index 8f3e7b7..3319edb 100644 --- a/Facepunch.Steamworks.Test/Client/Lobby.cs +++ b/Facepunch.Steamworks.Test/Client/Lobby.cs @@ -1,32 +1,41 @@ using System; +using System.Diagnostics; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Facepunch.Steamworks.Test { + [TestClass] [DeploymentItem("steam_api.dll")] [DeploymentItem("steam_api64.dll")] [DeploymentItem("steam_appid.txt")] - [TestClass] - class Lobby + public class Lobby { [TestMethod] - public void FriendList() + public void CreateLobby() { - /* using (var client = new Facepunch.Steamworks.Client(252490)) { Assert.IsTrue(client.IsValid); - client.Friends.Refresh(); + client.Lobby.Create(Steamworks.Lobby.Type.Public, 10); - Assert.IsNotNull(client.Friends.All); - - foreach (var friend in client.Friends.All) + client.Lobby.OnLobbyCreated = (success) => { - Console.WriteLine("{0}: {1} (Friend:{2}) (Blocked:{3})", friend.Id, friend.Name, friend.IsFriend, friend.IsBlocked); + Assert.IsTrue(success); + Assert.IsTrue(client.Lobby.IsValid); + Console.WriteLine(client.Lobby.CurrentLobby); + client.Lobby.Leave(); + }; + + var sw = Stopwatch.StartNew(); + + while (sw.Elapsed.TotalSeconds < 3) + { + client.Update(); + System.Threading.Thread.Sleep(10); } + } - */ } } } diff --git a/Facepunch.Steamworks/Client/Lobby.cs b/Facepunch.Steamworks/Client/Lobby.cs index 5748672..0db6f79 100644 --- a/Facepunch.Steamworks/Client/Lobby.cs +++ b/Facepunch.Steamworks/Client/Lobby.cs @@ -8,22 +8,16 @@ namespace Facepunch.Steamworks { public partial class Client : IDisposable { - public void JoinLobby(ulong LobbyID) - { - native.matchmaking.JoinLobby(LobbyID, OnLobbyJoined); - } + Lobby _lobby; - void OnLobbyJoined(LobbyEnter_t callback, bool error) + public Lobby Lobby { - //TODO: - } - - // Create a lobby, auto joins the created lobby - public Lobby CreateLobby(Lobby.Type lobbyType, int maxMembers) - { - var lobby = new Lobby(this, lobbyType); - native.matchmaking.CreateLobby((SteamNative.LobbyType)lobbyType, maxMembers, lobby.OnLobbyCreatedAPI); - return lobby; + get + { + if (_lobby == null) + _lobby = new Steamworks.Lobby(this); + return _lobby; + } } } public class Lobby : IDisposable @@ -34,47 +28,63 @@ namespace Facepunch.Steamworks Private = SteamNative.LobbyType.Private, FriendsOnly = SteamNative.LobbyType.FriendsOnly, Public = SteamNative.LobbyType.Public, - Invisible = SteamNative.LobbyType.Invisible + Invisible = SteamNative.LobbyType.Invisible, + None } internal Client client; - /// - /// Returns true if we tried to create this lobby but it returned - /// an error. - /// - public bool IsError { get; private set; } - - /// - /// Returns true if this lobby is valid, ie, we've received - /// a positive response from Steam about it. - /// - public bool IsValid => LobbyID != 0; - - /// - /// The CSteamID of the lobby that was created - /// - internal ulong LobbyID { get; private set; } - - /// - /// The name of the lobby as a property for easy getting/setting - /// - public string Name - { - get { return _name; } - set { if (_name == value) return; SetLobbyData("name", value); } - } - string _name = ""; - - /// - /// Callback for when lobby is created - /// - public Action OnLobbyCreated; - - public Lobby(Client c, Type type) + public Lobby(Client c) { client = c; - LobbyType = type; + SteamNative.LobbyDataUpdate_t.RegisterCallback(client, OnLobbyDataUpdated); + } + + /// + /// The CSteamID of the lobby we're currently in. + /// + public ulong CurrentLobby { get; private set; } + + 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. LobbyJoined is called when the lobby has successfully been joined. + /// + /// CSteamID of lobby to join + public void Join(ulong lobbyID) + { + 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); } + } + + public Action OnLobbyJoined; + + /// + /// Creates a lobby and returns the created lobby. You auto join the created lobby. The lobby is stored in Client.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); + LobbyType = lobbyType; } internal void OnLobbyCreatedAPI(LobbyCreated_t callback, bool error) @@ -82,105 +92,239 @@ namespace Facepunch.Steamworks //from SpaceWarClient.cpp 793 if (error || (callback.Result != Result.OK)) { - IsError = true; + if ( OnLobbyCreated != null) { OnLobbyCreated(false); } return; } - Owner = client.SteamId; //this is implicitly set on creation but need to cache it here - LobbyID = callback.SteamIDLobby; - MaxMembers = client.native.matchmaking.GetLobbyMemberLimit(LobbyID); - SetLobbyData("appid", client.AppId.ToString()); - - if (OnLobbyCreated != null) { OnLobbyCreated(); } + //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()); + CurrentLobbyData.SetData("lobbytype", LobbyType.ToString()); + if (OnLobbyCreated != null) { OnLobbyCreated(true); } } - Dictionary LobbyData = new Dictionary(); - public void SetLobbyData(string key, string value) + /// + /// Callback for when lobby is created + /// + public Action OnLobbyCreated; + + public class LobbyData { - if (LobbyData.ContainsKey(key)) - { - if (LobbyData[key] == value) - return; + internal Client client; + internal ulong lobby; + internal Dictionary data; - LobbyData[key] = value; - } - else + public LobbyData(Client c, ulong l) { - LobbyData.Add(key, value); + client = c; + lobby = l; + data = new Dictionary(); + } + + public string GetData(string k) + { + if (data.ContainsKey(k)) + { + return data[k]; + } + + return "ERROR: key not found"; + } + + public bool SetData(string k, string v) + { + if (data.ContainsKey(k)) + { + if (data[k] == v) { return true; } + if (client.native.matchmaking.SetLobbyData(lobby, k, v)) + { + data[k] = v; + return true; + } + } + else + { + if (client.native.matchmaking.SetLobbyData(lobby, k, v)) + { + data.Add(k, v); + return true; + } + } + + return false; + } + + public bool RemoveData(string k) + { + if (data.ContainsKey(k)) + { + if (client.native.matchmaking.DeleteLobbyData(lobby, k)) + { + data.Remove(k); + return true; + } + } + + return false; } - client.native.matchmaking.SetLobbyData(LobbyID, key, value); } - public void RemoveLobbyData(string key) + internal void OnLobbyDataUpdated(LobbyDataUpdate_t callback, bool error) { - if (LobbyData.ContainsKey(key)) + if(error) { return; } + if(callback.SteamIDLobby == CurrentLobby) //actual lobby data was updated by owner { - LobbyData.Remove(key); + UpdateLobbyData(); } - client.native.matchmaking.DeleteLobbyData(LobbyID, key); + //TODO: need to check and see if the updated member is in this lobby + } + + /// + /// 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); + } + } } public Type LobbyType { - get { return _lobbyType; } + get + { + if (!IsValid) { return Type.None; } //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.None; + } + } set { - if (_lobbyType == value) return; - //only call the proper method if the lobby is valid, otherwise cache the value - if(IsValid) + if(!IsValid) { return; } + if(client.native.matchmaking.SetLobbyType(CurrentLobby, (SteamNative.LobbyType)value)) { - client.native.matchmaking.SetLobbyType(LobbyID, (SteamNative.LobbyType)value); //returns bool? + CurrentLobbyData.SetData("lobbytype", value.ToString()); } - _lobbyType = value; } } - Type _lobbyType; + /// + /// 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); + } + } - //Must be the owner to change the owner + /// + /// The Owner of the current lobby. Returns 0 if you are not in a valid lobby. + /// public ulong Owner { get { - if (_owner == 0) + if (_owner == 0 && IsValid) { - _owner = client.native.matchmaking.GetLobbyOwner(LobbyID); - return _owner; + _owner = client.native.matchmaking.GetLobbyOwner(CurrentLobby); } return _owner; } - private set { if (_owner == value) return; client.native.matchmaking.SetLobbyOwner(LobbyID, value); _owner = value; } + private set + { + if (_owner == value) return; + if (client.native.matchmaking.SetLobbyOwner(CurrentLobby, value)) { _owner = value; } + } } ulong _owner = 0; // Can the lobby be joined by other people public bool Joinable { - get { return _joinable; } - set { if (_joinable == value) return; client.native.matchmaking.SetLobbyJoinable(LobbyID, value); _joinable = value; } + 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()); + } + } } - bool _joinable = true; //steam default // How many people can be in the Lobby public int MaxMembers { - get { return _maxMembers; } - set { if (_maxMembers == value) return; client.native.matchmaking.SetLobbyMemberLimit(LobbyID, value); _maxMembers = value; } + 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); + } } - int _maxMembers = 0; //How many people are currently in the lobby public int NumMembers { - get { return client.native.matchmaking.GetNumLobbyMembers(LobbyID);} + get { return client.native.matchmaking.GetNumLobbyMembers(CurrentLobby);} } //leave the current lobby public void Leave() { - client.native.matchmaking.LeaveLobby(LobbyID); + client.native.matchmaking.LeaveLobby(CurrentLobby); + _owner = 0; + CurrentLobbyData = null; } public void Dispose() @@ -189,8 +333,6 @@ namespace Facepunch.Steamworks } /*not implemented - // returns a lobby metadata key/values pair by index - client.native.matchmaking.GetLobbyDataByIndex; //set the game server of the lobby client.native.matchmaking.GetLobbyGameServer; @@ -199,22 +341,15 @@ namespace Facepunch.Steamworks //data for people in the actual lobby - scores/elo/characters/etc. client.native.matchmaking.SetLobbyMemberData; //local user client.native.matchmaking.GetLobbyMemberData; //any user in this lobby - // returns steamid of member // note that the current user must be in a lobby to retrieve CSteamIDs of other users in that lobby client.native.matchmaking.GetLobbyMemberByIndex; - //for linking lobbies idk havent looked hard yet - client.native.matchmaking.SetLinkedLobby; - //chat functions client.native.matchmaking.SendLobbyChatMsg; client.native.matchmaking.GetLobbyChatEntry; - //get total data count (why?) - client.native.matchmaking.GetLobbyDataCount - //invite your frans client.native.matchmaking.InviteUserToLobby //this invites the user the current lobby the invitee is in */ diff --git a/Facepunch.Steamworks/Client/LobbyList.cs b/Facepunch.Steamworks/Client/LobbyList.cs index 1019c4e..7dc9266 100644 --- a/Facepunch.Steamworks/Client/LobbyList.cs +++ b/Facepunch.Steamworks/Client/LobbyList.cs @@ -70,17 +70,32 @@ namespace Facepunch.Steamworks } else { - //need to sub the lobbyid to the request func - //client.native.matchmaking.RequestLobbyData(lobby) + //need to get the info for the missing lobby + client.native.matchmaking.RequestLobbyData(lobby); + SteamNative.LobbyDataUpdate_t.RegisterCallback(client, OnLobbyDataUpdated); } } - if (OnLobbiesRefreshed != null) { OnLobbiesRefreshed(); } + if (OnLobbiesUpdated != null) { OnLobbiesUpdated(); } } - - public Action OnLobbiesRefreshed; + void OnLobbyDataUpdated(LobbyDataUpdate_t callback, bool error) + { + if (callback.Success == 1) //1 if success, 0 if failure + { + Lobby lobby = Lobbies.Find(x => x.LobbyID == callback.SteamIDLobby); + if (lobby == null) //need to add this lobby to the list + { + Lobbies.Add(lobby); + } + + //otherwise lobby data in general was updated and you should listen to see what changed + if (OnLobbiesUpdated != null) { OnLobbiesUpdated(); } + } + } + + public Action OnLobbiesUpdated; public void Dispose() {