From e3f84eac32f7d6e068f649108ca881062f79ab60 Mon Sep 17 00:00:00 2001 From: Kyle Kukshtel Date: Wed, 19 Jul 2017 19:07:59 -0700 Subject: [PATCH 01/13] Initial pass at getting coverage for Client functions in API --- Facepunch.Steamworks/Client.cs | 8 + Facepunch.Steamworks/Client/Lobby.cs | 202 ++++++++++++++++++ .../Client/LobbyList.Lobby.cs | 33 +++ Facepunch.Steamworks/Client/LobbyList.cs | 117 ++++++++++ 4 files changed, 360 insertions(+) create mode 100644 Facepunch.Steamworks/Client/Lobby.cs create mode 100644 Facepunch.Steamworks/Client/LobbyList.Lobby.cs create mode 100644 Facepunch.Steamworks/Client/LobbyList.cs diff --git a/Facepunch.Steamworks/Client.cs b/Facepunch.Steamworks/Client.cs index ecf1bfa..e46e5e4 100644 --- a/Facepunch.Steamworks/Client.cs +++ b/Facepunch.Steamworks/Client.cs @@ -55,6 +55,7 @@ public partial class Client : BaseSteamworks public Voice Voice { get; private set; } public ServerList ServerList { get; private set; } + public LobbyList LobbyList { get; private set; } public App App { get; private set; } public Achievements Achievements { get; private set; } public Stats Stats { get; private set; } @@ -88,6 +89,7 @@ public Client( uint appId ) // Voice = new Voice( this ); ServerList = new ServerList( this ); + LobbyList = new LobbyList(this); App = new App( this ); Stats = new Stats( this ); Achievements = new Achievements( this ); @@ -159,6 +161,12 @@ public override void Dispose() ServerList = null; } + if (LobbyList != null) + { + LobbyList.Dispose(); + LobbyList = null; + } + if ( App != null ) { App.Dispose(); diff --git a/Facepunch.Steamworks/Client/Lobby.cs b/Facepunch.Steamworks/Client/Lobby.cs new file mode 100644 index 0000000..72ef977 --- /dev/null +++ b/Facepunch.Steamworks/Client/Lobby.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.Text; +using SteamNative; +using Result = SteamNative.Result; + +namespace Facepunch.Steamworks +{ + public partial class Client : IDisposable + { + public void JoinLobby(ulong LobbyID) + { + native.matchmaking.JoinLobby(LobbyID, OnLobbyJoined); + } + + void OnLobbyJoined(LobbyEnter_t callback, bool error) + { + //TODO: + } + + + //The type of lobby you are creating + public enum LobbyType : int + { + Private = SteamNative.LobbyType.Private, + FriendsOnly = SteamNative.LobbyType.FriendsOnly, + Public = SteamNative.LobbyType.Public, + Invisible = SteamNative.LobbyType.Invisible + } + + // Create a lobby, auto joins the created lobby + public Lobby CreateLobby(LobbyType lobbyType, int maxMembers, string name) + { + var lobby = new Lobby(this); + native.matchmaking.CreateLobby((SteamNative.LobbyType)lobbyType, maxMembers, lobby.OnLobbyCreated); + if (lobby.IsValid) + { + lobby.Name = name; + lobby.MaxMembers = maxMembers; + lobby.LobbyType = lobbyType; + } + return lobby; + } + } + public class Lobby : IDisposable + { + 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 LobbyData["name"]; } + set { SetLobbyData("name", value); } + } + + public Lobby(Client c) + { + client = c; + } + + internal void OnLobbyCreated(LobbyCreated_t callback, bool error) + { + //from SpaceWarClient.cpp 793 + if (error || (callback.Result != Result.OK)) + { + IsError = true; + return; + } + + Owner = client.SteamId; //this is implicitly set on creation but need to cache it here + LobbyID = callback.SteamIDLobby; + } + + Dictionary LobbyData = new Dictionary(); + public void SetLobbyData(string key, string value) + { + if (LobbyData.ContainsKey(key)) + { + if (LobbyData[key] == value) + return; + + LobbyData[key] = value; + } + else + { + LobbyData.Add(key, value); + } + + client.native.matchmaking.SetLobbyData(LobbyID, key, value); + } + + public void RemoveLobbyData(string key) + { + if (LobbyData.ContainsKey(key)) + { + LobbyData.Remove(key); + } + + client.native.matchmaking.DeleteLobbyData(LobbyID, key); + } + + public Client.LobbyType LobbyType + { + get { return _lobbyType; } + set { if (_lobbyType == value) return; client.native.matchmaking.SetLobbyType(LobbyID, (SteamNative.LobbyType)value); _lobbyType = value; } //returns bool + } + Client.LobbyType _lobbyType; + + + //Must be the owner to change the owner + public ulong Owner + { + get + { + if (_owner == 0) + { + _owner = client.native.matchmaking.GetLobbyOwner(LobbyID); + return _owner; + } + return _owner; + } + private set { if (_owner == value) return; client.native.matchmaking.SetLobbyOwner(LobbyID, 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; } + } + 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; } + } + int _maxMembers = 0; + + public void Leave() + { + client.native.matchmaking.LeaveLobby(LobbyID); + } + + public void Dispose() + { + client = null; + } + + /*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; + client.native.matchmaking.SetLobbyGameServer; + + //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.Lobby.cs b/Facepunch.Steamworks/Client/LobbyList.Lobby.cs new file mode 100644 index 0000000..e16dab2 --- /dev/null +++ b/Facepunch.Steamworks/Client/LobbyList.Lobby.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Facepunch.Steamworks +{ + public partial class LobbyList + { + public class Lobby + { + internal Client Client; + public string Name { get; private set; } + public ulong LobbyID { get; private set; } + public ulong Owner { get; private set; } + public int MemberLimit{ get; private set; } + public int NumMembers{ get; private set; } + + internal static Lobby FromSteam(Client client, ulong lobby) + { + return new Lobby() + { + Client = client, + LobbyID = lobby, + Name = client.native.matchmaking.GetLobbyData(lobby, "name"), + MemberLimit = client.native.matchmaking.GetLobbyMemberLimit(lobby), + Owner = client.native.matchmaking.GetLobbyOwner(lobby), + NumMembers = client.native.matchmaking.GetNumLobbyMembers(lobby) + }; + + } + } + } +} diff --git a/Facepunch.Steamworks/Client/LobbyList.cs b/Facepunch.Steamworks/Client/LobbyList.cs new file mode 100644 index 0000000..b4e7fd4 --- /dev/null +++ b/Facepunch.Steamworks/Client/LobbyList.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Text; +using SteamNative; + +namespace Facepunch.Steamworks +{ + public partial class LobbyList : IDisposable + { + internal Client client; + public List Lobbies = new List(); + + internal LobbyList(Client client) + { + this.client = client; + } + + public void Refresh ( LobbyFilter filter = null) + { + if(filter != null) + { + client.native.matchmaking.AddRequestLobbyListDistanceFilter((SteamNative.LobbyDistanceFilter)filter.DistanceFilter); + client.native.matchmaking.AddRequestLobbyListFilterSlotsAvailable(filter.SlotsAvailable); + client.native.matchmaking.AddRequestLobbyListResultCountFilter(filter.MaxResults); + foreach (KeyValuePair fil in filter.StringFilters) + { + client.native.matchmaking.AddRequestLobbyListStringFilter(fil.Key, fil.Value, SteamNative.LobbyComparison.Equal); + } + foreach (KeyValuePair fil in filter.NearFilters) + { + client.native.matchmaking.AddRequestLobbyListNearValueFilter(fil.Key, fil.Value); + } + foreach (KeyValuePair> fil in filter.NumericalFilters) + { + client.native.matchmaking.AddRequestLobbyListNumericalFilter(fil.Key, fil.Value.Value, (SteamNative.LobbyComparison)fil.Value.Key); + } + } + + // this will never return lobbies that are full (via the actual api) + client.native.matchmaking.RequestLobbyList(OnLobbyList); + + /* + if (filter == null) may need this if we are getting too many lobbies + { + filter = new Filter(); + //filter.Add("appid", client.AppId.ToString()); + }*/ + + } + + void OnLobbyList(LobbyMatchList_t callback, bool error) + { + if (error) return; + Lobbies.Clear(); + uint lobbiesMatching = callback.LobbiesMatching; + // lobbies are returned in order of closeness to the user, so add them to the list in that order + for (int i = 0; i < lobbiesMatching; i++) + { + ulong lobby = client.native.matchmaking.GetLobbyByIndex(i); + Lobby newLobby = Lobby.FromSteam(client, lobby); + if (newLobby.Name != "") + { + Lobbies.Add(newLobby); + } + else + { + //need to sub the lobbyid to the request func + //client.native.matchmaking.RequestLobbyData(lobby) + } + + } + } + + public void Dispose() + { + client = null; + } + + public class LobbyFilter + { + // Filters that match actual metadata keys exactly + public Dictionary StringFilters = new Dictionary(); + // Filters that are of string key and int value for that key to be close to + public Dictionary NearFilters = new Dictionary(); + //Filters that are of string key and int value, with a comparison filter to say how we should relate to the value + public Dictionary> NumericalFilters = new Dictionary>(); + public Distance DistanceFilter { get; set; } + public int SlotsAvailable { get; set; } + public int MaxResults { get; set; } + + public enum Distance : int + { + Close = SteamNative.LobbyDistanceFilter.Close, + Default = SteamNative.LobbyDistanceFilter.Default, + Far = SteamNative.LobbyDistanceFilter.Far, + Worldwide = SteamNative.LobbyDistanceFilter.Worldwide + } + + public enum Comparison : int + { + EqualToOrLessThan = SteamNative.LobbyComparison.EqualToOrLessThan, + LessThan = SteamNative.LobbyComparison.LessThan, + Equal = SteamNative.LobbyComparison.Equal, + GreaterThan = SteamNative.LobbyComparison.GreaterThan, + EqualToOrGreaterThan = SteamNative.LobbyComparison.EqualToOrGreaterThan, + NotEqual = SteamNative.LobbyComparison.NotEqual + } + + public LobbyFilter(Distance distanceFilter, int slotsAvailable, int maxResults) + { + DistanceFilter = distanceFilter; + SlotsAvailable = slotsAvailable; + MaxResults = maxResults; + } + } + } +} From 7e38750f2ee7b4c2a5da988317039ca63d8f3985 Mon Sep 17 00:00:00 2001 From: Kyle Kukshtel Date: Tue, 25 Jul 2017 10:09:41 -0700 Subject: [PATCH 02/13] changes lobby initialization and makes lobbylist filter easier --- Facepunch.Steamworks.Test/Client/Lobby.cs | 32 ++++++++++ Facepunch.Steamworks/Client/Lobby.cs | 70 +++++++++++++-------- Facepunch.Steamworks/Client/LobbyList.cs | 74 ++++++++++++----------- 3 files changed, 116 insertions(+), 60 deletions(-) create mode 100644 Facepunch.Steamworks.Test/Client/Lobby.cs diff --git a/Facepunch.Steamworks.Test/Client/Lobby.cs b/Facepunch.Steamworks.Test/Client/Lobby.cs new file mode 100644 index 0000000..8f3e7b7 --- /dev/null +++ b/Facepunch.Steamworks.Test/Client/Lobby.cs @@ -0,0 +1,32 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Facepunch.Steamworks.Test +{ + [DeploymentItem("steam_api.dll")] + [DeploymentItem("steam_api64.dll")] + [DeploymentItem("steam_appid.txt")] + [TestClass] + class Lobby + { + [TestMethod] + public void FriendList() + { + /* + using (var client = new Facepunch.Steamworks.Client(252490)) + { + Assert.IsTrue(client.IsValid); + + client.Friends.Refresh(); + + Assert.IsNotNull(client.Friends.All); + + foreach (var friend in client.Friends.All) + { + Console.WriteLine("{0}: {1} (Friend:{2}) (Blocked:{3})", friend.Id, friend.Name, friend.IsFriend, friend.IsBlocked); + } + } + */ + } + } +} diff --git a/Facepunch.Steamworks/Client/Lobby.cs b/Facepunch.Steamworks/Client/Lobby.cs index 72ef977..5748672 100644 --- a/Facepunch.Steamworks/Client/Lobby.cs +++ b/Facepunch.Steamworks/Client/Lobby.cs @@ -18,9 +18,18 @@ void OnLobbyJoined(LobbyEnter_t callback, bool error) //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; + } + } + public class Lobby : IDisposable + { //The type of lobby you are creating - public enum LobbyType : int + public enum Type : int { Private = SteamNative.LobbyType.Private, FriendsOnly = SteamNative.LobbyType.FriendsOnly, @@ -28,22 +37,6 @@ public enum LobbyType : int Invisible = SteamNative.LobbyType.Invisible } - // Create a lobby, auto joins the created lobby - public Lobby CreateLobby(LobbyType lobbyType, int maxMembers, string name) - { - var lobby = new Lobby(this); - native.matchmaking.CreateLobby((SteamNative.LobbyType)lobbyType, maxMembers, lobby.OnLobbyCreated); - if (lobby.IsValid) - { - lobby.Name = name; - lobby.MaxMembers = maxMembers; - lobby.LobbyType = lobbyType; - } - return lobby; - } - } - public class Lobby : IDisposable - { internal Client client; /// @@ -68,16 +61,23 @@ public class Lobby : IDisposable /// public string Name { - get { return LobbyData["name"]; } - set { SetLobbyData("name", value); } + get { return _name; } + set { if (_name == value) return; SetLobbyData("name", value); } } + string _name = ""; - public Lobby(Client c) + /// + /// Callback for when lobby is created + /// + public Action OnLobbyCreated; + + public Lobby(Client c, Type type) { client = c; + LobbyType = type; } - internal void OnLobbyCreated(LobbyCreated_t callback, bool error) + internal void OnLobbyCreatedAPI(LobbyCreated_t callback, bool error) { //from SpaceWarClient.cpp 793 if (error || (callback.Result != Result.OK)) @@ -88,6 +88,10 @@ internal void OnLobbyCreated(LobbyCreated_t callback, bool error) 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(); } } Dictionary LobbyData = new Dictionary(); @@ -118,12 +122,21 @@ public void RemoveLobbyData(string key) client.native.matchmaking.DeleteLobbyData(LobbyID, key); } - public Client.LobbyType LobbyType + public Type LobbyType { get { return _lobbyType; } - set { if (_lobbyType == value) return; client.native.matchmaking.SetLobbyType(LobbyID, (SteamNative.LobbyType)value); _lobbyType = value; } //returns bool + set + { + if (_lobbyType == value) return; + //only call the proper method if the lobby is valid, otherwise cache the value + if(IsValid) + { + client.native.matchmaking.SetLobbyType(LobbyID, (SteamNative.LobbyType)value); //returns bool? + } + _lobbyType = value; + } } - Client.LobbyType _lobbyType; + Type _lobbyType; //Must be the owner to change the owner @@ -158,6 +171,13 @@ public int MaxMembers } int _maxMembers = 0; + //How many people are currently in the lobby + public int NumMembers + { + get { return client.native.matchmaking.GetNumLobbyMembers(LobbyID);} + } + + //leave the current lobby public void Leave() { client.native.matchmaking.LeaveLobby(LobbyID); diff --git a/Facepunch.Steamworks/Client/LobbyList.cs b/Facepunch.Steamworks/Client/LobbyList.cs index b4e7fd4..1019c4e 100644 --- a/Facepunch.Steamworks/Client/LobbyList.cs +++ b/Facepunch.Steamworks/Client/LobbyList.cs @@ -15,36 +15,42 @@ internal LobbyList(Client client) this.client = client; } - public void Refresh ( LobbyFilter filter = null) + public void Refresh ( Filter filter = null) { - if(filter != null) + if(filter == null) { - client.native.matchmaking.AddRequestLobbyListDistanceFilter((SteamNative.LobbyDistanceFilter)filter.DistanceFilter); - client.native.matchmaking.AddRequestLobbyListFilterSlotsAvailable(filter.SlotsAvailable); - client.native.matchmaking.AddRequestLobbyListResultCountFilter(filter.MaxResults); - foreach (KeyValuePair fil in filter.StringFilters) - { - client.native.matchmaking.AddRequestLobbyListStringFilter(fil.Key, fil.Value, SteamNative.LobbyComparison.Equal); - } - foreach (KeyValuePair fil in filter.NearFilters) - { - client.native.matchmaking.AddRequestLobbyListNearValueFilter(fil.Key, fil.Value); - } - foreach (KeyValuePair> fil in filter.NumericalFilters) - { - client.native.matchmaking.AddRequestLobbyListNumericalFilter(fil.Key, fil.Value.Value, (SteamNative.LobbyComparison)fil.Value.Key); - } + filter = new Filter(); + filter.StringFilters.Add("appid", client.AppId.ToString()); } + client.native.matchmaking.AddRequestLobbyListDistanceFilter((SteamNative.LobbyDistanceFilter)filter.DistanceFilter); + + if(filter.SlotsAvailable != null) + { + client.native.matchmaking.AddRequestLobbyListFilterSlotsAvailable((int)filter.SlotsAvailable); + } + + if (filter.MaxResults != null) + { + client.native.matchmaking.AddRequestLobbyListResultCountFilter((int)filter.MaxResults); + } + + foreach (KeyValuePair fil in filter.StringFilters) + { + client.native.matchmaking.AddRequestLobbyListStringFilter(fil.Key, fil.Value, SteamNative.LobbyComparison.Equal); + } + foreach (KeyValuePair fil in filter.NearFilters) + { + client.native.matchmaking.AddRequestLobbyListNearValueFilter(fil.Key, fil.Value); + } + foreach (KeyValuePair> fil in filter.NumericalFilters) + { + client.native.matchmaking.AddRequestLobbyListNumericalFilter(fil.Key, fil.Value.Value, (SteamNative.LobbyComparison)fil.Value.Key); + } + + // this will never return lobbies that are full (via the actual api) client.native.matchmaking.RequestLobbyList(OnLobbyList); - - /* - if (filter == null) may need this if we are getting too many lobbies - { - filter = new Filter(); - //filter.Add("appid", client.AppId.ToString()); - }*/ } @@ -69,14 +75,19 @@ void OnLobbyList(LobbyMatchList_t callback, bool error) } } + + if (OnLobbiesRefreshed != null) { OnLobbiesRefreshed(); } } + + public Action OnLobbiesRefreshed; + public void Dispose() { client = null; } - public class LobbyFilter + public class Filter { // Filters that match actual metadata keys exactly public Dictionary StringFilters = new Dictionary(); @@ -84,9 +95,9 @@ public class LobbyFilter public Dictionary NearFilters = new Dictionary(); //Filters that are of string key and int value, with a comparison filter to say how we should relate to the value public Dictionary> NumericalFilters = new Dictionary>(); - public Distance DistanceFilter { get; set; } - public int SlotsAvailable { get; set; } - public int MaxResults { get; set; } + public Distance DistanceFilter = Distance.Worldwide; + public int? SlotsAvailable { get; set; } + public int? MaxResults { get; set; } public enum Distance : int { @@ -105,13 +116,6 @@ public enum Comparison : int EqualToOrGreaterThan = SteamNative.LobbyComparison.EqualToOrGreaterThan, NotEqual = SteamNative.LobbyComparison.NotEqual } - - public LobbyFilter(Distance distanceFilter, int slotsAvailable, int maxResults) - { - DistanceFilter = distanceFilter; - SlotsAvailable = slotsAvailable; - MaxResults = maxResults; - } } } } From e1c738fc8b133639238a2b4da5c117c451c0d93a Mon Sep 17 00:00:00 2001 From: Kyle Kukshtel Date: Fri, 28 Jul 2017 02:20:28 -0700 Subject: [PATCH 03/13] Updates Lobby to work for both Lobby members and Lobby Owners Uses a more rigurous approach to LobbyData and uses it as a cache for string-able values to be able to be retrieved by members and set only by the owner. --- Facepunch.Steamworks.Test/Client/Lobby.cs | 29 +- Facepunch.Steamworks/Client/Lobby.cs | 335 +++++++++++++++------- Facepunch.Steamworks/Client/LobbyList.cs | 25 +- 3 files changed, 274 insertions(+), 115 deletions(-) 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 @@ public enum Type : int 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 @@ internal void OnLobbyCreatedAPI(LobbyCreated_t callback, bool error) //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 @@ public void Dispose() } /*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 @@ public void Dispose() //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 @@ void OnLobbyList(LobbyMatchList_t callback, bool error) } 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() { From c40037784ef91794e35be6498617954961ef1aae Mon Sep 17 00:00:00 2001 From: Kyle Kukshtel Date: Mon, 31 Jul 2017 09:28:09 -0700 Subject: [PATCH 04/13] Caches created LobbyType to be assigned as a LobbyData to be referenced by other members in the lobby. Because there is no GetLobbyType, provides a layer in which all users can get a LobbyType from LobbyData. --- Facepunch.Steamworks/Client/Lobby.cs | 36 +++++++++++++++++++++------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/Facepunch.Steamworks/Client/Lobby.cs b/Facepunch.Steamworks/Client/Lobby.cs index 0db6f79..4cc1645 100644 --- a/Facepunch.Steamworks/Client/Lobby.cs +++ b/Facepunch.Steamworks/Client/Lobby.cs @@ -29,7 +29,7 @@ public enum Type : int FriendsOnly = SteamNative.LobbyType.FriendsOnly, Public = SteamNative.LobbyType.Public, Invisible = SteamNative.LobbyType.Invisible, - None + Error //happens if you try to get this when you aren't in a valid lobby } internal Client client; @@ -37,7 +37,7 @@ public enum Type : int public Lobby(Client c) { client = c; - SteamNative.LobbyDataUpdate_t.RegisterCallback(client, OnLobbyDataUpdated); + SteamNative.LobbyDataUpdate_t.RegisterCallback(client, OnLobbyDataUpdatedAPI); } /// @@ -84,9 +84,11 @@ void OnLobbyJoinedAPI(LobbyEnter_t callback, bool error) public void Create(Lobby.Type lobbyType, int maxMembers) { client.native.matchmaking.CreateLobby((SteamNative.LobbyType)lobbyType, maxMembers, OnLobbyCreatedAPI); - LobbyType = lobbyType; + createdLobbyType = lobbyType; } + internal Type createdLobbyType; + internal void OnLobbyCreatedAPI(LobbyCreated_t callback, bool error) { //from SpaceWarClient.cpp 793 @@ -102,7 +104,9 @@ internal void OnLobbyCreatedAPI(LobbyCreated_t callback, bool error) 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); } } @@ -115,7 +119,7 @@ public class LobbyData { internal Client client; internal ulong lobby; - internal Dictionary data; + internal Dictionary data; public LobbyData(Client c, ulong l) { @@ -134,6 +138,16 @@ public string GetData(string k) return "ERROR: key not found"; } + public List> GetAllData() + { + List> returnData = new List>(); + foreach(KeyValuePair item in data) + { + returnData.Add(new KeyValuePair(item.Key, item.Value)); + } + return returnData; + } + public bool SetData(string k, string v) { if (data.ContainsKey(k)) @@ -173,7 +187,7 @@ public bool RemoveData(string k) } - internal void OnLobbyDataUpdated(LobbyDataUpdate_t callback, bool error) + internal void OnLobbyDataUpdatedAPI(LobbyDataUpdate_t callback, bool error) { if(error) { return; } if(callback.SteamIDLobby == CurrentLobby) //actual lobby data was updated by owner @@ -198,13 +212,19 @@ internal void UpdateLobbyData() CurrentLobbyData.SetData(key, value); } } + + if(OnLobbyDataUpdated != null) { OnLobbyDataUpdated(); } } + //Called when the lobby data itself has been updated. This callback is slower than the actual setting/getting of LobbyData, but it ensures safety. + public Action OnLobbyDataUpdated; + + public Type LobbyType { get { - if (!IsValid) { return Type.None; } //if we're currently in a valid server + 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) @@ -220,12 +240,12 @@ public Type LobbyType case "Public": return Type.Public; default: - return Type.None; + return Type.Error; } } set { - if(!IsValid) { return; } + if(!IsValid) { return; } if(client.native.matchmaking.SetLobbyType(CurrentLobby, (SteamNative.LobbyType)value)) { CurrentLobbyData.SetData("lobbytype", value.ToString()); From beccb5e5e9cc7229e0e7040b294299f26ab659b1 Mon Sep 17 00:00:00 2001 From: Kyle Kukshtel Date: Mon, 31 Jul 2017 09:29:53 -0700 Subject: [PATCH 05/13] Gives LobbyList the ability to be queried for if it is Finished or not. A lobby refresh will finish when the number of cached lobbies equals the number of requests. --- Facepunch.Steamworks/Client/LobbyList.cs | 67 ++++++++++++++++++++---- 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/Facepunch.Steamworks/Client/LobbyList.cs b/Facepunch.Steamworks/Client/LobbyList.cs index 7dc9266..cdee4da 100644 --- a/Facepunch.Steamworks/Client/LobbyList.cs +++ b/Facepunch.Steamworks/Client/LobbyList.cs @@ -8,16 +8,36 @@ namespace Facepunch.Steamworks public partial class LobbyList : IDisposable { internal Client client; - public List Lobbies = new List(); + + //The list of retrieved lobbies + public List Lobbies { get; private set; } + + //True when all the possible lobbies have had their data updated + //if the number of lobbies is now equal to the initial request number, we've found all lobbies + public bool Finished { get; private set; } + + //The number of possible lobbies we can get data from + internal List requests; internal LobbyList(Client client) { this.client = client; + Lobbies = new List(); + requests = new List(); } + /// + /// Refresh the List of Lobbies. If no filter is passed in, a default one is created that filters based on AppId ("appid"). + /// + /// public void Refresh ( Filter filter = null) { - if(filter == null) + //init out values + Lobbies.Clear(); + requests.Clear(); + Finished = false; + + if (filter == null) { filter = new Filter(); filter.StringFilters.Add("appid", client.AppId.ToString()); @@ -25,7 +45,7 @@ public void Refresh ( Filter filter = null) client.native.matchmaking.AddRequestLobbyListDistanceFilter((SteamNative.LobbyDistanceFilter)filter.DistanceFilter); - if(filter.SlotsAvailable != null) + if (filter.SlotsAvailable != null) { client.native.matchmaking.AddRequestLobbyListFilterSlotsAvailable((int)filter.SlotsAvailable); } @@ -43,10 +63,10 @@ public void Refresh ( Filter filter = null) { client.native.matchmaking.AddRequestLobbyListNearValueFilter(fil.Key, fil.Value); } - foreach (KeyValuePair> fil in filter.NumericalFilters) - { - client.native.matchmaking.AddRequestLobbyListNumericalFilter(fil.Key, fil.Value.Value, (SteamNative.LobbyComparison)fil.Value.Key); - } + //foreach (KeyValuePair> fil in filter.NumericalFilters) + //{ + // client.native.matchmaking.AddRequestLobbyListNumericalFilter(fil.Key, fil.Value.Value, (SteamNative.LobbyComparison)fil.Value.Key); + //} // this will never return lobbies that are full (via the actual api) @@ -54,23 +74,32 @@ public void Refresh ( Filter filter = null) } + void OnLobbyList(LobbyMatchList_t callback, bool error) { if (error) return; - Lobbies.Clear(); + + //how many lobbies matched uint lobbiesMatching = callback.LobbiesMatching; + // lobbies are returned in order of closeness to the user, so add them to the list in that order for (int i = 0; i < lobbiesMatching; i++) { + //add the lobby to the list of requests ulong lobby = client.native.matchmaking.GetLobbyByIndex(i); + requests.Add(lobby); + + //cast to a LobbyList.Lobby Lobby newLobby = Lobby.FromSteam(client, lobby); if (newLobby.Name != "") { + //if the lobby is valid add it to the valid return lobbies Lobbies.Add(newLobby); + checkFinished(); } else { - //need to get the info for the missing lobby + //else we need to get the info for the missing lobby client.native.matchmaking.RequestLobbyData(lobby); SteamNative.LobbyDataUpdate_t.RegisterCallback(client, OnLobbyDataUpdated); } @@ -80,19 +109,35 @@ void OnLobbyList(LobbyMatchList_t callback, bool error) if (OnLobbiesUpdated != null) { OnLobbiesUpdated(); } } + void checkFinished() + { + if (Lobbies.Count == requests.Count) + { + Finished = true; + return; + } + Finished = false; + } + void OnLobbyDataUpdated(LobbyDataUpdate_t callback, bool error) { if (callback.Success == 1) //1 if success, 0 if failure { + //find the lobby that has been updated Lobby lobby = Lobbies.Find(x => x.LobbyID == callback.SteamIDLobby); - if (lobby == null) //need to add this lobby to the list + + //if this lobby isn't yet in the list of lobbies, we know that we should add it + if (lobby == null) { Lobbies.Add(lobby); + checkFinished(); } //otherwise lobby data in general was updated and you should listen to see what changed if (OnLobbiesUpdated != null) { OnLobbiesUpdated(); } } + + } public Action OnLobbiesUpdated; @@ -109,7 +154,7 @@ public class Filter // Filters that are of string key and int value for that key to be close to public Dictionary NearFilters = new Dictionary(); //Filters that are of string key and int value, with a comparison filter to say how we should relate to the value - public Dictionary> NumericalFilters = new Dictionary>(); + //public Dictionary> NumericalFilters = new Dictionary>(); public Distance DistanceFilter = Distance.Worldwide; public int? SlotsAvailable { get; set; } public int? MaxResults { get; set; } From 75484c1e001c2a5aa9f1039953074cb5ef29f544 Mon Sep 17 00:00:00 2001 From: Kyle Kukshtel Date: Mon, 31 Jul 2017 09:30:26 -0700 Subject: [PATCH 06/13] Adds in test functions for Lobby functionality in Steamworks --- Facepunch.Steamworks.Test/Client/Lobby.cs | 212 +++++++++++++++++++++- 1 file changed, 211 insertions(+), 1 deletion(-) diff --git a/Facepunch.Steamworks.Test/Client/Lobby.cs b/Facepunch.Steamworks.Test/Client/Lobby.cs index 3319edb..0a05800 100644 --- a/Facepunch.Steamworks.Test/Client/Lobby.cs +++ b/Facepunch.Steamworks.Test/Client/Lobby.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Diagnostics; +using Facepunch.Steamworks; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Facepunch.Steamworks.Test @@ -23,7 +25,10 @@ public void CreateLobby() { Assert.IsTrue(success); Assert.IsTrue(client.Lobby.IsValid); - Console.WriteLine(client.Lobby.CurrentLobby); + Console.WriteLine("lobby created: " + client.Lobby.CurrentLobby); + Console.WriteLine($"Owner: {client.Lobby.Owner}"); + Console.WriteLine($"Max Members: {client.Lobby.MaxMembers}"); + Console.WriteLine($"Num Members: {client.Lobby.NumMembers}"); client.Lobby.Leave(); }; @@ -37,5 +42,210 @@ public void CreateLobby() } } + + [TestMethod] + public void GetCreatedLobbyData() + { + using (var client = new Facepunch.Steamworks.Client(252490)) + { + Assert.IsTrue(client.IsValid); + + client.Lobby.Create(Steamworks.Lobby.Type.Public, 10); + + client.Lobby.OnLobbyCreated = (success) => + { + Assert.IsTrue(success); + Assert.IsTrue(client.Lobby.IsValid); + Console.WriteLine("lobby created: " + client.Lobby.CurrentLobby); + foreach (KeyValuePair data in client.Lobby.CurrentLobbyData.GetAllData()) + { + if (data.Key == "appid") + { + Assert.IsTrue(data.Value == "252490"); + } + Console.WriteLine($"{data.Key} {data.Value}"); + } + client.Lobby.Leave(); + }; + + var sw = Stopwatch.StartNew(); + + while (sw.Elapsed.TotalSeconds < 3) + { + client.Update(); + System.Threading.Thread.Sleep(10); + } + + } + } + + [TestMethod] + public void UpdateLobbyData() + { + using (var client = new Facepunch.Steamworks.Client(252490)) + { + Assert.IsTrue(client.IsValid); + + client.Lobby.Create(Steamworks.Lobby.Type.Public, 10); + + client.Lobby.OnLobbyCreated = (success) => + { + Assert.IsTrue(success); + Assert.IsTrue(client.Lobby.IsValid); + Console.WriteLine("lobby created: " + client.Lobby.CurrentLobby); + + client.Lobby.Name = "My Updated Lobby Name"; + client.Lobby.CurrentLobbyData.SetData("testkey", "testvalue"); + client.Lobby.LobbyType = Steamworks.Lobby.Type.Private; + client.Lobby.MaxMembers = 5; + client.Lobby.Joinable = false; + + foreach (KeyValuePair data in client.Lobby.CurrentLobbyData.GetAllData()) + { + if (data.Key == "appid") + { + Assert.IsTrue(data.Value == "252490"); + } + + if (data.Key == "testkey") + { + Assert.IsTrue(data.Value == "testvalue"); + } + + if (data.Key == "lobbytype") + { + Assert.IsTrue(data.Value == Steamworks.Lobby.Type.Private.ToString()); + } + + Console.WriteLine($"{data.Key} {data.Value}"); + } + + + + }; + + + client.Lobby.OnLobbyDataUpdated = () => + { + Console.WriteLine("lobby data updated"); + Console.WriteLine(client.Lobby.MaxMembers); + Console.WriteLine(client.Lobby.Joinable); + }; + + + + var sw = Stopwatch.StartNew(); + + while (sw.Elapsed.TotalSeconds < 3) + { + client.Update(); + System.Threading.Thread.Sleep(10); + } + + client.Lobby.Leave(); + + } + } + + [TestMethod] + public void RefreshLobbyList() + { + using (var client = new Facepunch.Steamworks.Client(252490)) + { + Assert.IsTrue(client.IsValid); + + client.Lobby.OnLobbyCreated = (success) => + { + Assert.IsTrue(success); + Assert.IsTrue(client.Lobby.IsValid); + Console.WriteLine("lobby created: " + client.Lobby.CurrentLobby); + client.LobbyList.Refresh(); + }; + + client.LobbyList.OnLobbiesUpdated = () => + { + Console.WriteLine("lobbies updating"); + if (client.LobbyList.Finished) + { + Console.WriteLine("lobbies finished updating"); + Console.WriteLine($"found {client.LobbyList.Lobbies.Count} lobbies"); + + foreach (LobbyList.Lobby lobby in client.LobbyList.Lobbies) + { + Console.WriteLine($"Found Lobby: {lobby.Name}"); + } + + client.Lobby.Leave(); + + } + + }; + + client.Lobby.Create(Steamworks.Lobby.Type.Public, 10); + + var sw = Stopwatch.StartNew(); + + while (sw.Elapsed.TotalSeconds < 3) + { + client.Update(); + System.Threading.Thread.Sleep(10); + } + + } + } + + [TestMethod] + public void RefreshLobbyListWithFilter() + { + using (var client = new Facepunch.Steamworks.Client(480)) + { + Assert.IsTrue(client.IsValid); + + client.Lobby.OnLobbyCreated = (success) => + { + Assert.IsTrue(success); + Assert.IsTrue(client.Lobby.IsValid); + Console.WriteLine("lobby created: " + client.Lobby.CurrentLobby); + client.Lobby.CurrentLobbyData.SetData("testkey", "testvalue"); + }; + + client.Lobby.OnLobbyDataUpdated = () => + { + var filter = new LobbyList.Filter(); + filter.StringFilters.Add("testkey", "testvalue"); + client.LobbyList.Refresh(filter); + }; + + client.LobbyList.OnLobbiesUpdated = () => + { + Console.WriteLine("lobbies updating"); + if (client.LobbyList.Finished) + { + Console.WriteLine("lobbies finished updating"); + Console.WriteLine($"found {client.LobbyList.Lobbies.Count} lobbies"); + + foreach (LobbyList.Lobby lobby in client.LobbyList.Lobbies) + { + Console.WriteLine($"Found Lobby: {lobby.Name}"); + } + + } + + }; + + client.Lobby.Create(Steamworks.Lobby.Type.Public, 10); + + var sw = Stopwatch.StartNew(); + + while (sw.Elapsed.TotalSeconds < 5) + { + client.Update(); + System.Threading.Thread.Sleep(10); + } + + client.Lobby.Leave(); + + } + } } } From bcd8495d6eda1e4d34fd83c5551842a61f7bc86c Mon Sep 17 00:00:00 2001 From: Kyle Kukshtel Date: Mon, 31 Jul 2017 22:44:46 -0700 Subject: [PATCH 07/13] Adds in ability to send Lobby messages Also adds in some callbacks around being invited to a lobby, as well callbacks for being requested to join a lobby. --- Facepunch.Steamworks.Test/Client/Lobby.cs | 43 +++++- Facepunch.Steamworks/Client/Lobby.cs | 179 +++++++++++++++++++--- 2 files changed, 203 insertions(+), 19 deletions(-) diff --git a/Facepunch.Steamworks.Test/Client/Lobby.cs b/Facepunch.Steamworks.Test/Client/Lobby.cs index 0a05800..b84d089 100644 --- a/Facepunch.Steamworks.Test/Client/Lobby.cs +++ b/Facepunch.Steamworks.Test/Client/Lobby.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using Facepunch.Steamworks; +using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Facepunch.Steamworks.Test @@ -197,7 +198,7 @@ public void RefreshLobbyList() [TestMethod] public void RefreshLobbyListWithFilter() { - using (var client = new Facepunch.Steamworks.Client(480)) + using (var client = new Facepunch.Steamworks.Client(252490)) { Assert.IsTrue(client.IsValid); @@ -247,5 +248,45 @@ public void RefreshLobbyListWithFilter() } } + + [TestMethod] + public void SendChatMessage() + { + using (var client = new Facepunch.Steamworks.Client(252490)) + { + Assert.IsTrue(client.IsValid); + string testString = "Hello, World"; + + client.Lobby.OnLobbyCreated = (success) => + { + Assert.IsTrue(success); + Assert.IsTrue(client.Lobby.IsValid); + Console.WriteLine("lobby created: " + client.Lobby.CurrentLobby); + client.Lobby.CurrentLobbyData.SetData("testkey", "testvalue"); + client.Lobby.SendChatMessage(testString); + }; + + client.Lobby.OnChatMessageRecieved = (steamID, bytes, length) => + { + string message = Encoding.UTF8.GetString(bytes, 0, length); + Console.WriteLine("message recieved"); + Assert.IsTrue(message == testString); + }; + + client.Lobby.Create(Steamworks.Lobby.Type.Public, 10); + + var sw = Stopwatch.StartNew(); + + while (sw.Elapsed.TotalSeconds < 5) + { + client.Update(); + System.Threading.Thread.Sleep(10); + } + + client.Lobby.Leave(); + + } + } + } } diff --git a/Facepunch.Steamworks/Client/Lobby.cs b/Facepunch.Steamworks/Client/Lobby.cs index 4cc1645..a7a8c6a 100644 --- a/Facepunch.Steamworks/Client/Lobby.cs +++ b/Facepunch.Steamworks/Client/Lobby.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Text; using SteamNative; -using Result = SteamNative.Result; namespace Facepunch.Steamworks { @@ -38,6 +37,11 @@ public Lobby(Client c) { client = c; SteamNative.LobbyDataUpdate_t.RegisterCallback(client, OnLobbyDataUpdatedAPI); + SteamNative.LobbyChatMsg_t.RegisterCallback(client, OnLobbyChatMessageRecievedAPI); + SteamNative.LobbyChatUpdate_t.RegisterCallback(client, OnLobbyStateUpdatedAPI); + SteamNative.GameLobbyJoinRequested_t.RegisterCallback(client, OnLobbyJoinRequestedAPI); + SteamNative.LobbyInvite_t.RegisterCallback(client, OnUserInvitedToLobbyAPI); + SteamNative.PersonaStateChange_t.RegisterCallback(client, OnLobbyMemberPersonaChangeAPI); } /// @@ -53,11 +57,12 @@ public Lobby(Client c) public bool IsValid => CurrentLobby != 0; /// - /// Join a Lobby through its LobbyID. LobbyJoined is called when the lobby has successfully been joined. + /// 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); } @@ -74,6 +79,9 @@ void OnLobbyJoinedAPI(LobbyEnter_t callback, bool error) 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; /// @@ -216,9 +224,21 @@ internal void UpdateLobbyData() if(OnLobbyDataUpdated != null) { OnLobbyDataUpdated(); } } - //Called when the lobby data itself has been updated. This callback is slower than the actual setting/getting of LobbyData, but it ensures safety. + internal void UpdateLobbyMemberData() + { + if (OnLobbyMemberDataUpdated != null) { OnLobbyMemberDataUpdated(); } + } + + /// + /// Called when the lobby data itself has been updated. Called when someone has joined/Owner has updated data + /// 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) + /// + public Action OnLobbyMemberDataUpdated; + public Type LobbyType { @@ -253,6 +273,74 @@ public Type LobbyType } } + private unsafe void OnLobbyChatMessageRecievedAPI(LobbyChatMsg_t callback, bool error) + { + //from Client.Networking + if(error || callback.SteamIDLobby != CurrentLobby) { return; } + + byte[] ReceiveBuffer = new byte[1024]; + SteamNative.CSteamID steamid = 1; + ChatEntryType chatEntryType; //not used + int readData = 0; + fixed (byte* p = ReceiveBuffer) + { + readData = client.native.matchmaking.GetLobbyChatEntry(CurrentLobby, (int)callback.ChatID, out steamid, (IntPtr)p, ReceiveBuffer.Length, out chatEntryType); + while (ReceiveBuffer.Length < readData) + { + ReceiveBuffer = new byte[readData + 1024]; + readData = client.native.matchmaking.GetLobbyChatEntry(CurrentLobby, (int)callback.ChatID, out steamid, (IntPtr)p, ReceiveBuffer.Length, out chatEntryType); + } + + } + + if (OnChatMessageRecieved != null) { OnChatMessageRecieved(steamid, ReceiveBuffer, readData); } + + } + + /// + /// Callback to get chat messages. + /// + public Action OnChatMessageRecieved; + + /// + /// 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); + } + } + + public enum MemberStateChange + { + Entered = ChatMemberStateChange.Entered, + Left = ChatMemberStateChange.Left, + Disconnected = ChatMemberStateChange.Disconnected, + Kicked = ChatMemberStateChange.Kicked, + Banned = ChatMemberStateChange.Banned, + } + + internal void OnLobbyStateUpdatedAPI(LobbyChatUpdate_t callback, bool error) + { + if (error || callback.SteamIDLobby != CurrentLobby) { return; } + MemberStateChange change = (MemberStateChange)callback.GfChatMemberStateChange; + ulong initiator = callback.SteamIDMakingChange; + ulong affected = callback.SteamIDUserChanged; + + if (OnLobbyStateChanged != null) { OnLobbyStateChanged(change, initiator, affected); } + } + + /// + /// Called when the state of the Lobby is somehow shifted. Usually when someone joins or leaves the lobby. + /// + 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 /// @@ -291,7 +379,9 @@ private set } ulong _owner = 0; - // Can the lobby be joined by other people + /// + /// Is the Lobby joinable by other people? Defaults to true; + /// public bool Joinable { get @@ -318,7 +408,9 @@ public bool Joinable } } - // How many people can be in the Lobby + /// + /// How many people can be in the Lobby + /// public int MaxMembers { get @@ -333,16 +425,21 @@ public int MaxMembers } } - //How many people are currently in the lobby + /// + /// How many people are currently in the Lobby + /// public int NumMembers { get { return client.native.matchmaking.GetNumLobbyMembers(CurrentLobby);} } - //leave the current lobby + /// + /// Leave the CurrentLobby. + /// public void Leave() { - client.native.matchmaking.LeaveLobby(CurrentLobby); + if (CurrentLobby != 0) { client.native.matchmaking.LeaveLobby(CurrentLobby); } + CurrentLobby = 0; _owner = 0; CurrentLobbyData = null; } @@ -352,6 +449,60 @@ 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 + /// + /// + 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; + } + + /// + /// 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, bool error) + { + if (error || (callback.GameID != client.AppId)) { return; } + if (OnUserInvitedToLobby != null) { OnUserInvitedToLobby(callback.SteamIDLobby, callback.SteamIDUser); } + + } + + /// + /// 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; + + /// + /// Joins a lobby is a request was made to join the lobby through the friends list or an invite + /// + internal void OnLobbyJoinRequestedAPI(GameLobbyJoinRequested_t callback, bool error) + { + if (error) { return; } + Join(callback.SteamIDLobby); + } + + /// + /// Makes sure we send an update callback if a Lobby user updates their information + /// + internal void OnLobbyMemberPersonaChangeAPI(PersonaStateChange_t callback, bool error) + { + if (error || !client.native.friends.IsUserInSource(callback.SteamID, CurrentLobby)) { return; } + UpdateLobbyMemberData(); + } + /*not implemented //set the game server of the lobby @@ -362,16 +513,8 @@ public void Dispose() 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; - - //chat functions - client.native.matchmaking.SendLobbyChatMsg; - client.native.matchmaking.GetLobbyChatEntry; - - //invite your frans - client.native.matchmaking.InviteUserToLobby //this invites the user the current lobby the invitee is in + //used with game server stuff + SteamNative.LobbyGameCreated_t */ } } From a1f24c5f24e3982829a09d7916c66f98dea73f5d Mon Sep 17 00:00:00 2001 From: Kyle Kukshtel Date: Mon, 31 Jul 2017 23:42:29 -0700 Subject: [PATCH 08/13] Implements ability for a user to set/get individual user metadata for the joined lobby --- Facepunch.Steamworks.Test/Client/Lobby.cs | 38 +++++++++++++ Facepunch.Steamworks/Client/Lobby.cs | 66 +++++++++++++++++------ 2 files changed, 88 insertions(+), 16 deletions(-) diff --git a/Facepunch.Steamworks.Test/Client/Lobby.cs b/Facepunch.Steamworks.Test/Client/Lobby.cs index b84d089..dd2cf04 100644 --- a/Facepunch.Steamworks.Test/Client/Lobby.cs +++ b/Facepunch.Steamworks.Test/Client/Lobby.cs @@ -288,5 +288,43 @@ public void SendChatMessage() } } + [TestMethod] + public void SetGetUserMetadata() + { + using (var client = new Facepunch.Steamworks.Client(252490)) + { + Assert.IsTrue(client.IsValid); + + client.Lobby.OnLobbyCreated = (success) => + { + Assert.IsTrue(success); + Assert.IsTrue(client.Lobby.IsValid); + Console.WriteLine("lobby created: " + client.Lobby.CurrentLobby); + client.Lobby.SetMemberData("testkey", "testvalue"); + }; + + client.Lobby.OnLobbyMemberDataUpdated = (steamID) => + { + string name = client.Friends.GetName(steamID); + Console.WriteLine(name + " updated data"); + Assert.IsTrue(client.Lobby.GetMemberData(steamID, "testkey") == "testvalue"); + Console.WriteLine("testkey is now: " + client.Lobby.GetMemberData(steamID, "testkey")); + }; + + client.Lobby.Create(Steamworks.Lobby.Type.Public, 10); + + var sw = Stopwatch.StartNew(); + + while (sw.Elapsed.TotalSeconds < 5) + { + client.Update(); + System.Threading.Thread.Sleep(10); + } + + client.Lobby.Leave(); + + } + } + } } diff --git a/Facepunch.Steamworks/Client/Lobby.cs b/Facepunch.Steamworks/Client/Lobby.cs index a7a8c6a..7b3f66b 100644 --- a/Facepunch.Steamworks/Client/Lobby.cs +++ b/Facepunch.Steamworks/Client/Lobby.cs @@ -123,6 +123,9 @@ internal void OnLobbyCreatedAPI(LobbyCreated_t callback, bool error) /// public Action OnLobbyCreated; + /// + /// Class to hold global lobby data. This is stuff like maps/modes/etc. Data set here can be filtered by LobbyList. + /// public class LobbyData { internal Client client; @@ -195,15 +198,39 @@ public bool RemoveData(string k) } + /// + /// 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, bool error) { - if(error) { return; } + if(error || (callback.SteamIDLobby != CurrentLobby)) { return; } if(callback.SteamIDLobby == CurrentLobby) //actual lobby data was updated by owner { UpdateLobbyData(); } - //TODO: need to check and see if the updated member is in this lobby + if(UserIsInCurrentLobby(callback.SteamIDMember)) //some member of this lobby updated their information + { + if (OnLobbyMemberDataUpdated != null) { OnLobbyMemberDataUpdated(callback.SteamIDMember); } + } } /// @@ -224,20 +251,15 @@ internal void UpdateLobbyData() if(OnLobbyDataUpdated != null) { OnLobbyDataUpdated(); } } - internal void UpdateLobbyMemberData() - { - if (OnLobbyMemberDataUpdated != null) { OnLobbyMemberDataUpdated(); } - } - /// - /// Called when the lobby data itself has been updated. Called when someone has joined/Owner has updated data + /// 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) + /// 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 Action OnLobbyMemberDataUpdated; public Type LobbyType @@ -456,6 +478,7 @@ public void Dispose() /// public ulong[] GetMemberIDs() { + ulong[] memIDs = new ulong[NumMembers]; for (int i = 0; i < NumMembers; i++) { @@ -464,6 +487,21 @@ public ulong[] GetMemberIDs() return memIDs; } + 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. /// @@ -499,8 +537,8 @@ internal void OnLobbyJoinRequestedAPI(GameLobbyJoinRequested_t callback, bool er /// internal void OnLobbyMemberPersonaChangeAPI(PersonaStateChange_t callback, bool error) { - if (error || !client.native.friends.IsUserInSource(callback.SteamID, CurrentLobby)) { return; } - UpdateLobbyMemberData(); + if (error || !UserIsInCurrentLobby(callback.SteamID)) { return; } + if (OnLobbyMemberDataUpdated != null) { OnLobbyMemberDataUpdated(callback.SteamID); } } /*not implemented @@ -509,10 +547,6 @@ internal void OnLobbyMemberPersonaChangeAPI(PersonaStateChange_t callback, bool client.native.matchmaking.GetLobbyGameServer; client.native.matchmaking.SetLobbyGameServer; - //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 - //used with game server stuff SteamNative.LobbyGameCreated_t */ From 6aa5bcf1acef0a5c9be3b0323b337b561bdc7843 Mon Sep 17 00:00:00 2001 From: Kyle Kukshtel Date: Mon, 31 Jul 2017 23:54:32 -0700 Subject: [PATCH 09/13] Updates Readme with basic Lobby Info --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index ee7bc17..8b170ac 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,33 @@ var server = new Facepunch.Steamworks.Server( 252490, 0, 28015, true, "MyGame453 This will register a secure server for game 252490, any ip, port 28015. Again, more usage in the Facepunch.Steamworks.Test project. +## Lobby + +To create a Lobby do this. +```csharp +var client = new Facepunch.Steamworks.Client( 252490 ); +client.Lobby.Create(Steamworks.Lobby.Type.Public, 10); +``` + +Created lobbies are auto-joined, but if you want to find a friend's lobby, you'd call +```csharp +client.LobbyList.Refresh(); +//wait for the callback +client.LobbyList.OnLobbiesUpdated = () => +{ + if (client.LobbyList.Finished) + { + foreach (LobbyList.Lobby lobby in client.LobbyList.Lobbies) + { + Console.WriteLine($"Found Lobby: {lobby.Name}"); + } + } +}; +//join a lobby you found +client.Lobby.Join(LobbyList.Lobbies[0]); +``` + + # Unity Yeah this works under Unity. That's half the point of it. From abea08d04699ebb632b8d7f5a1c5eeea5cca5dcb Mon Sep 17 00:00:00 2001 From: Kyle Kukshtel Date: Tue, 1 Aug 2017 00:19:37 -0700 Subject: [PATCH 10/13] Fixes bug where LobbyList wouldn't report finished if it returned 0 lobbies --- Facepunch.Steamworks/Client/LobbyList.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Facepunch.Steamworks/Client/LobbyList.cs b/Facepunch.Steamworks/Client/LobbyList.cs index cdee4da..905695d 100644 --- a/Facepunch.Steamworks/Client/LobbyList.cs +++ b/Facepunch.Steamworks/Client/LobbyList.cs @@ -106,6 +106,8 @@ void OnLobbyList(LobbyMatchList_t callback, bool error) } + checkFinished(); + if (OnLobbiesUpdated != null) { OnLobbiesUpdated(); } } From cc4fdd532403cbaacb9f3b6f5c14d73e26450fd9 Mon Sep 17 00:00:00 2001 From: Kyle Kukshtel Date: Fri, 4 Aug 2017 01:56:14 -0700 Subject: [PATCH 11/13] Cleans up some comments --- Facepunch.Steamworks/Client/Lobby.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Facepunch.Steamworks/Client/Lobby.cs b/Facepunch.Steamworks/Client/Lobby.cs index 7b3f66b..858fc4b 100644 --- a/Facepunch.Steamworks/Client/Lobby.cs +++ b/Facepunch.Steamworks/Client/Lobby.cs @@ -119,7 +119,7 @@ internal void OnLobbyCreatedAPI(LobbyCreated_t callback, bool error) } /// - /// Callback for when lobby is created + /// Callback for when lobby is created. Parameter resolves true when the Lobby was successfully created /// public Action OnLobbyCreated; @@ -320,7 +320,7 @@ private unsafe void OnLobbyChatMessageRecievedAPI(LobbyChatMsg_t callback, bool } /// - /// Callback to get chat messages. + /// Callback to get chat messages. Use Encoding.UTF8.GetString to retrive the message. /// public Action OnChatMessageRecieved; @@ -360,6 +360,8 @@ internal void OnLobbyStateUpdatedAPI(LobbyChatUpdate_t callback, bool error) /// /// 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; @@ -473,9 +475,9 @@ public void Dispose() /// /// 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 + /// 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. /// - /// public ulong[] GetMemberIDs() { From bef249000afc3b28c24ba8089d0c9761d4268c7b Mon Sep 17 00:00:00 2001 From: Kyle Kukshtel Date: Sun, 6 Aug 2017 22:09:13 -0700 Subject: [PATCH 12/13] Simple comment update --- .gitignore | 5 ++++- Facepunch.Steamworks/Client/Lobby.cs | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b4b44f0..059b7d3 100644 --- a/.gitignore +++ b/.gitignore @@ -55,11 +55,14 @@ Facepunch.Steamworks.Test/bin/Release/Facepunch.Steamworks.Test.dll Facepunch.Steamworks.Test/bin/Release/Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll *.user *.cache +*.idea +*.vscode TestResults obj Facepunch.Steamworks/bin/Debug/Facepunch.Steamworks.Api.dll Facepunch.Steamworks/bin/Debug/Facepunch.Steamworks.dll Facepunch.Steamworks/bin/Release/Facepunch.Steamworks.dll +Facepunch.Steamworks/bin *.opendb *.db Facepunch.Steamworks.dll @@ -69,4 +72,4 @@ mscorlib.dll *.nlp packages Generator/bin -*.XML \ No newline at end of file +*.XML diff --git a/Facepunch.Steamworks/Client/Lobby.cs b/Facepunch.Steamworks/Client/Lobby.cs index 858fc4b..fb11536 100644 --- a/Facepunch.Steamworks/Client/Lobby.cs +++ b/Facepunch.Steamworks/Client/Lobby.cs @@ -478,6 +478,7 @@ public void Dispose() /// 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() { From 5067c32eedc7791d58eab610401ff48110821758 Mon Sep 17 00:00:00 2001 From: Kyle Kukshtel Date: Mon, 7 Aug 2017 09:24:28 -0700 Subject: [PATCH 13/13] Small README update and adjusts doc tags for Lobby functions. --- Facepunch.Steamworks/Client/Lobby.cs | 39 +++++++++++++++++++++++++--- README.md | 5 ++-- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/Facepunch.Steamworks/Client/Lobby.cs b/Facepunch.Steamworks/Client/Lobby.cs index fb11536..1d20130 100644 --- a/Facepunch.Steamworks/Client/Lobby.cs +++ b/Facepunch.Steamworks/Client/Lobby.cs @@ -49,6 +49,9 @@ public Lobby(Client c) /// 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; } /// @@ -139,6 +142,11 @@ public LobbyData(Client c, ulong l) data = new Dictionary(); } + /// + /// Get the lobby value for the specific key + /// + /// The key to find + /// The value at key public string GetData(string k) { if (data.ContainsKey(k)) @@ -149,16 +157,26 @@ public string GetData(string k) return "ERROR: key not found"; } - public List> GetAllData() + /// + /// Get a list of all the data in the Lobby + /// + /// Dictionary of all the key/value pairs in the data + public Dictionary GetAllData() { - List> returnData = new List>(); + Dictionary returnData = new Dictionary(); foreach(KeyValuePair item in data) { - returnData.Add(new KeyValuePair(item.Key, item.Value)); + returnData.Add(item.Key, item.Value); } return returnData; } + /// + /// Set the value for specified Key. Note that the keys "joinable", "appid", "name", and "lobbytype" are reserved for internal library use. + /// + /// The key to set the value for + /// The value of the Key + /// True if data successfully set public bool SetData(string k, string v) { if (data.ContainsKey(k)) @@ -182,6 +200,11 @@ public bool SetData(string k, string v) return false; } + /// + /// Remove the key from the LobbyData. Note that the keys "joinable", "appid", "name", and "lobbytype" are reserved for internal library use. + /// + /// The key to remove + /// True if Key successfully removed public bool RemoveData(string k) { if (data.ContainsKey(k)) @@ -339,6 +362,9 @@ public unsafe bool SendChatMessage(string message) } } + /// + /// Enums to catch the state of a user when their state has changed + /// public enum MemberStateChange { Entered = ChatMemberStateChange.Entered, @@ -490,6 +516,11 @@ public ulong[] GetMemberIDs() 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; } @@ -527,7 +558,7 @@ internal void OnUserInvitedToLobbyAPI(LobbyInvite_t callback, bool error) public Action OnUserInvitedToLobby; /// - /// Joins a lobby is a request was made to join the lobby through the friends list or an invite + /// 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, bool error) { diff --git a/README.md b/README.md index 8b170ac..30d4f9e 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ You can view examples of everything in the Facepunch.Steamworks.Test project. ## Client -Compile and add the library to your project. To create a client you can do this. +Compile the Facepunch.Steamworks project and add the library to your Unity project. To create a client you can do this. ```csharp var client = new Facepunch.Steamworks.Client( 252490 ); @@ -72,7 +72,6 @@ This will register a secure server for game 252490, any ip, port 28015. Again, m To create a Lobby do this. ```csharp -var client = new Facepunch.Steamworks.Client( 252490 ); client.Lobby.Create(Steamworks.Lobby.Type.Public, 10); ``` @@ -94,6 +93,8 @@ client.LobbyList.OnLobbiesUpdated = () => client.Lobby.Join(LobbyList.Lobbies[0]); ``` +Your can find more examples of Lobby functionality in the Lobby.cs file in the test project. Sending chat messages, assinging lobby data and member data, etc. + # Unity