From e3840a9a3a9c215840da0a876b03dcd74dca0b9f Mon Sep 17 00:00:00 2001 From: Garry Newman Date: Wed, 1 May 2019 15:55:22 +0100 Subject: [PATCH] LobbyQuery, Lobby Joining, Lobby Creating --- .../Facepunch.Steamworks.Test.csproj | 6 +- .../SteamMatchmakingTest.cs | 73 +++++++ Facepunch.Steamworks/Generated/SteamEnums.cs | 2 +- .../Generated/SteamStructs.cs | 2 +- Facepunch.Steamworks/SteamMatchmaking.cs | 13 ++ Facepunch.Steamworks/Structs/Lobby.cs | 206 ++++++++++++++++++ Facepunch.Steamworks/Structs/LobbyQuery.cs | 116 ++++++++++ Generator/Cleanup.cs | 2 + 8 files changed, 416 insertions(+), 4 deletions(-) create mode 100644 Facepunch.Steamworks.Test/SteamMatchmakingTest.cs create mode 100644 Facepunch.Steamworks/Structs/Lobby.cs create mode 100644 Facepunch.Steamworks/Structs/LobbyQuery.cs 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"; }