diff --git a/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj b/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj
index c69df4d..e25411f 100644
--- a/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj
+++ b/Facepunch.Steamworks.Test/Facepunch.Steamworks.Test.csproj
@@ -38,6 +38,7 @@
TRACE
prompt
4
+ x64
true
@@ -62,7 +63,7 @@
bin\x86\Debug\
DEBUG;TRACE
full
- x86
+ x64
prompt
MinimumRecommendedRules.ruleset
@@ -71,7 +72,7 @@
TRACE
true
pdbonly
- x86
+ x64
prompt
MinimumRecommendedRules.ruleset
@@ -90,6 +91,7 @@
+
diff --git a/Facepunch.Steamworks.Test/SteamMatchmakingTest.cs b/Facepunch.Steamworks.Test/SteamMatchmakingTest.cs
new file mode 100644
index 0000000..c385b13
--- /dev/null
+++ b/Facepunch.Steamworks.Test/SteamMatchmakingTest.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Steamworks.Data;
+
+namespace Steamworks
+{
+ [TestClass]
+ [DeploymentItem( "steam_api64.dll" )]
+ public class SteamMatchmakingTest
+ {
+ [TestMethod]
+ public async Task LobbyList()
+ {
+ await CreateLobby();
+
+ var list = await SteamMatchmaking.LobbyList
+ .RequestAsync();
+
+ if ( list == null )
+ {
+ Console.WriteLine( "No Lobbies Found!" );
+ return;
+ }
+
+ foreach ( var lobby in list )
+ {
+ Console.WriteLine( $"[{lobby.Id}] owned by {lobby.Owner} ({lobby.MemberCount}/{lobby.MaxMembers})" );
+ }
+ }
+
+ [TestMethod]
+ public async Task LobbyListWithAtLeastOne()
+ {
+ await CreateLobby();
+ await LobbyList();
+ }
+
+ [TestMethod]
+ public async Task CreateLobby()
+ {
+ var lobbyr = await SteamMatchmaking.CreateLobbyAsync( 32 );
+ if ( !lobbyr.HasValue )
+ {
+ Console.WriteLine( "No lobby created!" );
+ return;
+ }
+
+ var lobby = lobbyr.Value;
+ lobby.SetPublic();
+ lobby.SetData( "gametype", "sausage" );
+ lobby.SetData( "dicks", "unlicked" );
+
+ Console.WriteLine( $"lobby: {lobby.Id}" );
+
+ foreach ( var entry in lobby.Data )
+ {
+ Console.WriteLine( $" - {entry.Key} {entry.Value}" );
+ }
+
+ Console.WriteLine( $"members: {lobby.MemberCount}/{lobby.MaxMembers}" );
+
+ Console.WriteLine( $"Owner: {lobby.Owner}" );
+ Console.WriteLine( $"Owner Is Local Player: {lobby.Owner.IsMe}" );
+
+ lobby.SendChatString( "Hello I Love Lobbies" );
+ }
+ }
+
+}
diff --git a/Facepunch.Steamworks/Generated/SteamEnums.cs b/Facepunch.Steamworks/Generated/SteamEnums.cs
index ddf03bf..3786fc6 100644
--- a/Facepunch.Steamworks/Generated/SteamEnums.cs
+++ b/Facepunch.Steamworks/Generated/SteamEnums.cs
@@ -338,7 +338,7 @@ namespace Steamworks
//
// EChatRoomEnterResponse
//
- internal enum ChatRoomEnterResponse : int
+ public enum RoomEnter : int
{
Success = 1,
DoesntExist = 2,
diff --git a/Facepunch.Steamworks/Generated/SteamStructs.cs b/Facepunch.Steamworks/Generated/SteamStructs.cs
index 2801a14..1e1839e 100644
--- a/Facepunch.Steamworks/Generated/SteamStructs.cs
+++ b/Facepunch.Steamworks/Generated/SteamStructs.cs
@@ -1429,7 +1429,7 @@ namespace Steamworks.Data
internal struct JoinClanChatRoomCompletionResult_t
{
internal ulong SteamIDClanChat; // m_steamIDClanChat class CSteamID
- internal ChatRoomEnterResponse ChatRoomEnterResponse; // m_eChatRoomEnterResponse enum EChatRoomEnterResponse
+ internal RoomEnter ChatRoomEnterResponse; // m_eChatRoomEnterResponse enum EChatRoomEnterResponse
#region SteamCallback
internal static readonly int StructSize = System.Runtime.InteropServices.Marshal.SizeOf( typeof(JoinClanChatRoomCompletionResult_t) );
diff --git a/Facepunch.Steamworks/SteamMatchmaking.cs b/Facepunch.Steamworks/SteamMatchmaking.cs
index 380a165..3de8d5c 100644
--- a/Facepunch.Steamworks/SteamMatchmaking.cs
+++ b/Facepunch.Steamworks/SteamMatchmaking.cs
@@ -35,5 +35,18 @@ namespace Steamworks
//VolumeHasChanged_t.Install( x => OnVolumeChanged?.Invoke( x.NewVolume ) );
}
+ public static LobbyQuery LobbyList => new LobbyQuery();
+
+ ///
+ /// Creates a new invisible lobby. Call lobby.SetPublic to take it online.
+ ///
+ public static async Task CreateLobbyAsync( int maxMembers = 100 )
+ {
+ var lobby = await Internal.CreateLobby( LobbyType.Invisible, maxMembers );
+ if ( !lobby.HasValue || lobby.Value.Result != Result.OK ) return null;
+
+ return new Lobby { Id = lobby.Value.SteamIDLobby };
+ }
+
}
}
\ No newline at end of file
diff --git a/Facepunch.Steamworks/Structs/Lobby.cs b/Facepunch.Steamworks/Structs/Lobby.cs
new file mode 100644
index 0000000..3c2fdeb
--- /dev/null
+++ b/Facepunch.Steamworks/Structs/Lobby.cs
@@ -0,0 +1,206 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Steamworks.Data
+{
+ public struct Lobby
+ {
+ public SteamId Id { get; internal set; }
+
+ ///
+ /// Try to join this room. Will return RoomEnter.Success on success,
+ /// and anything else is a failure
+ ///
+ public async Task Join()
+ {
+ var result = await SteamMatchmaking.Internal.JoinLobby( Id );
+ if ( !result.HasValue ) return RoomEnter.Error;
+
+ return (RoomEnter) result.Value.EChatRoomEnterResponse;
+ }
+
+ ///
+ /// Leave a lobby; this will take effect immediately on the client side
+ /// other users in the lobby will be notified by a LobbyChatUpdate_t callback
+ ///
+ public void Leave()
+ {
+ SteamMatchmaking.Internal.LeaveLobby( Id );
+ }
+
+ ///
+ /// Invite another user to the lobby
+ /// will return true if the invite is successfully sent, whether or not the target responds
+ /// returns false if the local user is not connected to the Steam servers
+ ///
+ public bool InviteFriend( SteamId steamid )
+ {
+ return SteamMatchmaking.Internal.InviteUserToLobby( Id, steamid );
+ }
+
+ ///
+ /// returns the number of users in the specified lobby
+ ///
+ public int MemberCount => SteamMatchmaking.Internal.GetNumLobbyMembers( Id );
+
+ ///
+ /// Returns current members. Need to be in the lobby to see the users.
+ ///
+ public IEnumerable Members
+ {
+ get
+ {
+ for( int i = 0; i < MemberCount; i++ )
+ {
+ yield return new Friend( SteamMatchmaking.Internal.GetLobbyMemberByIndex( Id, i ) );
+ }
+ }
+ }
+
+
+ ///
+ /// Get data associated with this lobby
+ ///
+ public string GetData( string key )
+ {
+ return SteamMatchmaking.Internal.GetLobbyData( Id, key );
+ }
+
+ ///
+ /// Get data associated with this lobby
+ ///
+ public bool SetData( string key, string value )
+ {
+ if ( key.Length > 255 ) throw new System.ArgumentException( "Key should be < 255 chars", nameof( key ) );
+ if ( value.Length > 8192 ) throw new System.ArgumentException( "Value should be < 8192 chars", nameof( key ) );
+
+ return SteamMatchmaking.Internal.SetLobbyData( Id, key, value );
+ }
+
+ ///
+ /// Removes a metadata key from the lobby
+ ///
+ public bool DeleteData( string key )
+ {
+ return SteamMatchmaking.Internal.DeleteLobbyData( Id, key );
+ }
+
+ ///
+ /// Get all data for this lobby
+ ///
+ public IEnumerable> Data
+ {
+ get
+ {
+ var cnt = SteamMatchmaking.Internal.GetLobbyDataCount( Id );
+
+ var a = Helpers.TakeStringBuilder();
+ var b = Helpers.TakeStringBuilder();
+
+ for ( int i =0; i( a.ToString(), b.ToString() );
+ }
+ }
+ }
+ }
+
+ ///
+ /// Gets per-user metadata for someone in this lobby
+ ///
+ public string GetMemberData( Friend member, string key )
+ {
+ return SteamMatchmaking.Internal.GetLobbyMemberData( Id, member.Id, key );
+ }
+
+ ///
+ /// Sets per-user metadata (for the local user implicitly)
+ ///
+ public void SetMemberData( Friend member, string key, string value )
+ {
+ SteamMatchmaking.Internal.SetLobbyMemberData( Id, key, value );
+ }
+
+ ///
+ /// Sends a string to the chat room
+ ///
+ public bool SendChatString( string message )
+ {
+ var data = System.Text.Encoding.UTF8.GetBytes( message );
+ return SendChatBytes( data );
+ }
+
+ ///
+ /// Sends bytes the the chat room
+ ///
+ public unsafe bool SendChatBytes( byte[] data )
+ {
+ fixed ( byte* ptr = data )
+ {
+ return SteamMatchmaking.Internal.SendLobbyChatMsg( Id, (IntPtr)ptr, data.Length );
+ }
+ }
+
+ ///
+ /// Refreshes metadata for a lobby you're not necessarily in right now
+ /// you never do this for lobbies you're a member of, only if your
+ /// this will send down all the metadata associated with a lobby
+ /// this is an asynchronous call
+ /// returns false if the local user is not connected to the Steam servers
+ /// results will be returned by a LobbyDataUpdate_t callback
+ /// if the specified lobby doesn't exist, LobbyDataUpdate_t::m_bSuccess will be set to false
+ ///
+ public bool Refresh()
+ {
+ return SteamMatchmaking.Internal.RequestLobbyData( Id );
+ }
+
+ ///
+ /// Max members able to join this lobby. Cannot be over 250.
+ /// Can only be set by the owner
+ ///
+ public int MaxMembers
+ {
+ get => SteamMatchmaking.Internal.GetLobbyMemberLimit( Id );
+ set => SteamMatchmaking.Internal.SetLobbyMemberLimit( Id, value );
+ }
+
+ public bool SetPublic()
+ {
+ return SteamMatchmaking.Internal.SetLobbyType( Id, LobbyType.Public );
+ }
+
+ public bool SetPrivate()
+ {
+ return SteamMatchmaking.Internal.SetLobbyType( Id, LobbyType.Private );
+ }
+
+ public bool SetInvisible()
+ {
+ return SteamMatchmaking.Internal.SetLobbyType( Id, LobbyType.Invisible );
+ }
+
+ public bool SetFriendsOnly()
+ {
+ return SteamMatchmaking.Internal.SetLobbyType( Id, LobbyType.FriendsOnly );
+ }
+
+ public bool SetJoinable( bool b )
+ {
+ return SteamMatchmaking.Internal.SetLobbyJoinable( Id, b );
+ }
+
+ ///
+ /// You must be the lobby owner to set the owner
+ ///
+ public Friend Owner
+ {
+ get => new Friend( SteamMatchmaking.Internal.GetLobbyOwner( Id ) );
+ set => SteamMatchmaking.Internal.SetLobbyOwner( Id, value.Id );
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Facepunch.Steamworks/Structs/LobbyQuery.cs b/Facepunch.Steamworks/Structs/LobbyQuery.cs
new file mode 100644
index 0000000..36146d1
--- /dev/null
+++ b/Facepunch.Steamworks/Structs/LobbyQuery.cs
@@ -0,0 +1,116 @@
+using System.Threading.Tasks;
+
+namespace Steamworks.Data
+{
+ public struct LobbyQuery
+ {
+ // TODO FILTERS
+ // AddRequestLobbyListStringFilter
+ // - WithKeyValue, WithoutKeyValue
+ // AddRequestLobbyListNumericalFilter
+ // - WithLower, WithHigher, WithEqual, WithNotEqual
+ // AddRequestLobbyListNearValueFilter
+ // - OrderByNear
+
+ #region Distance Filter
+ internal LobbyDistanceFilter? distance;
+
+ ///
+ /// only lobbies in the same immediate region will be returned
+ ///
+ public LobbyQuery FilterDistanceClose()
+ {
+ distance = LobbyDistanceFilter.Close;
+ return this;
+ }
+
+ ///
+ /// only lobbies in the same immediate region will be returned
+ ///
+ public LobbyQuery FilterDistanceFar()
+ {
+ distance = LobbyDistanceFilter.Far;
+ return this;
+ }
+
+ ///
+ /// only lobbies in the same immediate region will be returned
+ ///
+ public LobbyQuery FilterDistanceWorldwide()
+ {
+ distance = LobbyDistanceFilter.Worldwide;
+ return this;
+ }
+ #endregion
+
+ #region Slots Filter
+ internal int? slotsAvailable;
+
+ ///
+ /// returns only lobbies with the specified number of slots available
+ ///
+ public LobbyQuery WithSlotsAvailable( int minSlots )
+ {
+ slotsAvailable = minSlots;
+ return this;
+ }
+
+ #endregion
+
+ #region Max results filter
+ internal int? maxResults;
+
+ ///
+ /// sets how many results to return, the lower the count the faster it is to download the lobby results
+ ///
+ public LobbyQuery WithMaxResults( int max )
+ {
+ maxResults = max;
+ return this;
+ }
+
+ #endregion
+
+
+ void ApplyFilters()
+ {
+ if ( distance.HasValue )
+ {
+ SteamMatchmaking.Internal.AddRequestLobbyListDistanceFilter( distance.Value );
+ }
+
+ if ( slotsAvailable.HasValue )
+ {
+ SteamMatchmaking.Internal.AddRequestLobbyListFilterSlotsAvailable( slotsAvailable.Value );
+ }
+
+ if ( maxResults.HasValue )
+ {
+ SteamMatchmaking.Internal.AddRequestLobbyListResultCountFilter( maxResults.Value );
+ }
+ }
+
+ ///
+ /// Run the query, get the matching lobbies
+ ///
+ public async Task RequestAsync()
+ {
+ ApplyFilters();
+
+ LobbyMatchList_t? list = await SteamMatchmaking.Internal.RequestLobbyList();
+ if ( !list.HasValue || list.Value.LobbiesMatching == 0 )
+ {
+ return null;
+ }
+
+ Lobby[] lobbies = new Lobby[list.Value.LobbiesMatching];
+
+ for ( int i = 0; i < list.Value.LobbiesMatching; i++ )
+ {
+ lobbies[i] = new Lobby { Id = SteamMatchmaking.Internal.GetLobbyByIndex( i ) };
+ }
+
+ return lobbies;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Generator/Cleanup.cs b/Generator/Cleanup.cs
index 915bce5..9ea92bb 100644
--- a/Generator/Cleanup.cs
+++ b/Generator/Cleanup.cs
@@ -25,6 +25,7 @@ public static class Cleanup
type = type.Replace( "UGCMatchingUGCType", "UgcType" );
type = type.Replace( "SteamItemInstanceID_t", "InventoryItemId" );
type = type.Replace( "SteamItemDef_t", "InventoryDefId" );
+ type = type.Replace( "ChatRoomEnterResponse", "RoomEnter" );
return type;
}
@@ -59,6 +60,7 @@ public static class Cleanup
if ( name == "InventoryItemId" ) return "public";
if ( name == "InventoryDefId" ) return "public";
if ( name == "P2PSend" ) return "public";
+ if ( name == "RoomEnter" ) return "public";
return "internal";
}