Merge pull request #56 from kkukshtel/master

Adds in Lobby functionality to library
This commit is contained in:
Garry Newman 2017-08-08 09:27:42 +01:00 committed by GitHub
commit 5dcfda6c15
7 changed files with 1175 additions and 2 deletions

3
.gitignore vendored
View File

@ -55,11 +55,14 @@ Facepunch.Steamworks.Test/bin/Release/Facepunch.Steamworks.Test.dll
Facepunch.Steamworks.Test/bin/Release/Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll Facepunch.Steamworks.Test/bin/Release/Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll
*.user *.user
*.cache *.cache
*.idea
*.vscode
TestResults TestResults
obj obj
Facepunch.Steamworks/bin/Debug/Facepunch.Steamworks.Api.dll Facepunch.Steamworks/bin/Debug/Facepunch.Steamworks.Api.dll
Facepunch.Steamworks/bin/Debug/Facepunch.Steamworks.dll Facepunch.Steamworks/bin/Debug/Facepunch.Steamworks.dll
Facepunch.Steamworks/bin/Release/Facepunch.Steamworks.dll Facepunch.Steamworks/bin/Release/Facepunch.Steamworks.dll
Facepunch.Steamworks/bin
*.opendb *.opendb
*.db *.db
Facepunch.Steamworks.dll Facepunch.Steamworks.dll

View File

@ -0,0 +1,330 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Facepunch.Steamworks;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Facepunch.Steamworks.Test
{
[TestClass]
[DeploymentItem("steam_api.dll")]
[DeploymentItem("steam_api64.dll")]
[DeploymentItem("steam_appid.txt")]
public class Lobby
{
[TestMethod]
public void CreateLobby()
{
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);
Console.WriteLine($"Owner: {client.Lobby.Owner}");
Console.WriteLine($"Max Members: {client.Lobby.MaxMembers}");
Console.WriteLine($"Num Members: {client.Lobby.NumMembers}");
client.Lobby.Leave();
};
var sw = Stopwatch.StartNew();
while (sw.Elapsed.TotalSeconds < 3)
{
client.Update();
System.Threading.Thread.Sleep(10);
}
}
}
[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<string, string> 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<string, string> 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(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.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();
}
}
[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();
}
}
[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();
}
}
}
}

View File

@ -55,6 +55,7 @@ public partial class Client : BaseSteamworks
public Voice Voice { get; private set; } public Voice Voice { get; private set; }
public ServerList ServerList { get; private set; } public ServerList ServerList { get; private set; }
public LobbyList LobbyList { get; private set; }
public App App { get; private set; } public App App { get; private set; }
public Achievements Achievements { get; private set; } public Achievements Achievements { get; private set; }
public Stats Stats { get; private set; } public Stats Stats { get; private set; }
@ -88,6 +89,7 @@ public Client( uint appId )
// //
Voice = new Voice( this ); Voice = new Voice( this );
ServerList = new ServerList( this ); ServerList = new ServerList( this );
LobbyList = new LobbyList(this);
App = new App( this ); App = new App( this );
Stats = new Stats( this ); Stats = new Stats( this );
Achievements = new Achievements( this ); Achievements = new Achievements( this );
@ -159,6 +161,12 @@ public override void Dispose()
ServerList = null; ServerList = null;
} }
if (LobbyList != null)
{
LobbyList.Dispose();
LobbyList = null;
}
if ( App != null ) if ( App != null )
{ {
App.Dispose(); App.Dispose();

View File

@ -0,0 +1,588 @@
using System;
using System.Collections.Generic;
using System.Text;
using SteamNative;
namespace Facepunch.Steamworks
{
public partial class Client : IDisposable
{
Lobby _lobby;
public Lobby Lobby
{
get
{
if (_lobby == null)
_lobby = new Steamworks.Lobby(this);
return _lobby;
}
}
}
public class Lobby : IDisposable
{
//The type of lobby you are creating
public enum Type : int
{
Private = SteamNative.LobbyType.Private,
FriendsOnly = SteamNative.LobbyType.FriendsOnly,
Public = SteamNative.LobbyType.Public,
Invisible = SteamNative.LobbyType.Invisible,
Error //happens if you try to get this when you aren't in a valid lobby
}
internal Client client;
public Lobby(Client c)
{
client = c;
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);
}
/// <summary>
/// The CSteamID of the lobby we're currently in.
/// </summary>
public ulong CurrentLobby { get; private set; }
/// <summary>
/// The LobbyData of the CurrentLobby. Note this is the global data for the lobby. Use SetMemberData to set specific member data.
/// </summary>
public LobbyData CurrentLobbyData { get; private set; }
/// <summary>
/// Returns true if this lobby is valid, ie, we've succesffuly created and/or joined a lobby.
/// </summary>
public bool IsValid => CurrentLobby != 0;
/// <summary>
/// Join a Lobby through its LobbyID. OnLobbyJoined is called with the result of the Join attempt.
/// </summary>
/// <param name="lobbyID">CSteamID of lobby to join</param>
public void Join(ulong lobbyID)
{
Leave();
client.native.matchmaking.JoinLobby(lobbyID, OnLobbyJoinedAPI);
}
void OnLobbyJoinedAPI(LobbyEnter_t callback, bool error)
{
if (error || (callback.EChatRoomEnterResponse != (uint)(SteamNative.ChatRoomEnterResponse.Success)))
{
if (OnLobbyJoined != null) { OnLobbyJoined(false); }
return;
}
CurrentLobby = callback.SteamIDLobby;
UpdateLobbyData();
if (OnLobbyJoined != null) { OnLobbyJoined(true); }
}
/// <summary>
/// Called when a lobby has been attempted joined. Returns true if lobby was successfuly joined, false if not.
/// </summary>
public Action<bool> OnLobbyJoined;
/// <summary>
/// Creates a lobby and returns the created lobby. You auto join the created lobby. The lobby is stored in Client.CurrentLobby if successful.
/// </summary>
/// <param name="lobbyType">The Lobby.Type of Lobby to be created</param>
/// <param name="maxMembers">The maximum amount of people you want to be able to be in this lobby, including yourself</param>
public void Create(Lobby.Type lobbyType, int maxMembers)
{
client.native.matchmaking.CreateLobby((SteamNative.LobbyType)lobbyType, maxMembers, OnLobbyCreatedAPI);
createdLobbyType = lobbyType;
}
internal Type createdLobbyType;
internal void OnLobbyCreatedAPI(LobbyCreated_t callback, bool error)
{
//from SpaceWarClient.cpp 793
if (error || (callback.Result != Result.OK))
{
if ( OnLobbyCreated != null) { OnLobbyCreated(false); }
return;
}
//set owner specific properties
Owner = client.SteamId;
CurrentLobby = callback.SteamIDLobby;
CurrentLobbyData = new LobbyData(client, CurrentLobby);
Name = client.Username + "'s Lobby";
CurrentLobbyData.SetData("appid", client.AppId.ToString());
LobbyType = createdLobbyType;
CurrentLobbyData.SetData("lobbytype", LobbyType.ToString());
Joinable = true;
if (OnLobbyCreated != null) { OnLobbyCreated(true); }
}
/// <summary>
/// Callback for when lobby is created. Parameter resolves true when the Lobby was successfully created
/// </summary>
public Action<bool> OnLobbyCreated;
/// <summary>
/// Class to hold global lobby data. This is stuff like maps/modes/etc. Data set here can be filtered by LobbyList.
/// </summary>
public class LobbyData
{
internal Client client;
internal ulong lobby;
internal Dictionary<string, string> data;
public LobbyData(Client c, ulong l)
{
client = c;
lobby = l;
data = new Dictionary<string, string>();
}
/// <summary>
/// Get the lobby value for the specific key
/// </summary>
/// <param name="k">The key to find</param>
/// <returns>The value at key</returns>
public string GetData(string k)
{
if (data.ContainsKey(k))
{
return data[k];
}
return "ERROR: key not found";
}
/// <summary>
/// Get a list of all the data in the Lobby
/// </summary>
/// <returns>Dictionary of all the key/value pairs in the data</returns>
public Dictionary<string,string> GetAllData()
{
Dictionary<string, string> returnData = new Dictionary<string, string>();
foreach(KeyValuePair<string, string> item in data)
{
returnData.Add(item.Key, item.Value);
}
return returnData;
}
/// <summary>
/// Set the value for specified Key. Note that the keys "joinable", "appid", "name", and "lobbytype" are reserved for internal library use.
/// </summary>
/// <param name="k">The key to set the value for</param>
/// <param name="v">The value of the Key</param>
/// <returns>True if data successfully set</returns>
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;
}
/// <summary>
/// Remove the key from the LobbyData. Note that the keys "joinable", "appid", "name", and "lobbytype" are reserved for internal library use.
/// </summary>
/// <param name="k">The key to remove</param>
/// <returns>True if Key successfully removed</returns>
public bool RemoveData(string k)
{
if (data.ContainsKey(k))
{
if (client.native.matchmaking.DeleteLobbyData(lobby, k))
{
data.Remove(k);
return true;
}
}
return false;
}
}
/// <summary>
/// Sets user data for the Lobby. Things like Character, Skin, Ready, etc. Can only set your own member data
/// </summary>
public void SetMemberData(string key, string value)
{
if(CurrentLobby == 0) { return; }
client.native.matchmaking.SetLobbyMemberData(CurrentLobby, key, value);
}
/// <summary>
/// Get the per-user metadata from this lobby. Can get data from any user
/// </summary>
/// <param name="steamID">ulong SteamID of the user you want to get data from</param>
/// <param name="key">String key of the type of data you want to get</param>
/// <returns></returns>
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 || (callback.SteamIDLobby != CurrentLobby)) { return; }
if(callback.SteamIDLobby == CurrentLobby) //actual lobby data was updated by owner
{
UpdateLobbyData();
}
if(UserIsInCurrentLobby(callback.SteamIDMember)) //some member of this lobby updated their information
{
if (OnLobbyMemberDataUpdated != null) { OnLobbyMemberDataUpdated(callback.SteamIDMember); }
}
}
/// <summary>
/// Updates the LobbyData property to have the data for the current lobby, if any
/// </summary>
internal void UpdateLobbyData()
{
int dataCount = client.native.matchmaking.GetLobbyDataCount(CurrentLobby);
CurrentLobbyData = new LobbyData(client, CurrentLobby);
for (int i = 0; i < dataCount; i++)
{
if (client.native.matchmaking.GetLobbyDataByIndex(CurrentLobby, i, out string key, out string value))
{
CurrentLobbyData.SetData(key, value);
}
}
if(OnLobbyDataUpdated != null) { OnLobbyDataUpdated(); }
}
/// <summary>
/// Called when the lobby data itself has been updated. Called when someone has joined/left, Owner has updated data, etc.
/// </summary>
public Action OnLobbyDataUpdated;
/// <summary>
/// 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.
/// </summary>
public Action<ulong> OnLobbyMemberDataUpdated;
public Type LobbyType
{
get
{
if (!IsValid) { return Type.Error; } //if we're currently in a valid server
//we know that we've set the lobby type via the lobbydata in the creation function
//ps this is important because steam doesn't have an easy way to get lobby type (why idk)
string lobbyType = CurrentLobbyData.GetData("lobbytype");
switch (lobbyType)
{
case "Private":
return Type.Private;
case "FriendsOnly":
return Type.FriendsOnly;
case "Invisible":
return Type.Invisible;
case "Public":
return Type.Public;
default:
return Type.Error;
}
}
set
{
if(!IsValid) { return; }
if(client.native.matchmaking.SetLobbyType(CurrentLobby, (SteamNative.LobbyType)value))
{
CurrentLobbyData.SetData("lobbytype", value.ToString());
}
}
}
private 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); }
}
/// <summary>
/// Callback to get chat messages. Use Encoding.UTF8.GetString to retrive the message.
/// </summary>
public Action<ulong, byte[], int> OnChatMessageRecieved;
/// <summary>
/// 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.
/// </summary>
/// <returns>True if message successfully sent</returns>
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);
}
}
/// <summary>
/// Enums to catch the state of a user when their state has changed
/// </summary>
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); }
}
/// <summary>
/// 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
/// </summary>
public Action<MemberStateChange, ulong, ulong> OnLobbyStateChanged;
/// <summary>
/// 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
/// </summary>
public string Name
{
get
{
if (!IsValid) { return ""; }
return CurrentLobbyData.GetData("name");
}
set
{
if (!IsValid) { return; }
CurrentLobbyData.SetData("name", value);
}
}
/// <summary>
/// The Owner of the current lobby. Returns 0 if you are not in a valid lobby.
/// </summary>
public ulong Owner
{
get
{
if (_owner == 0 && IsValid)
{
_owner = client.native.matchmaking.GetLobbyOwner(CurrentLobby);
}
return _owner;
}
private set
{
if (_owner == value) return;
if (client.native.matchmaking.SetLobbyOwner(CurrentLobby, value)) { _owner = value; }
}
}
ulong _owner = 0;
/// <summary>
/// Is the Lobby joinable by other people? Defaults to true;
/// </summary>
public bool Joinable
{
get
{
if (!IsValid) { return false; }
string joinable = CurrentLobbyData.GetData("joinable");
switch (joinable)
{
case "true":
return true;
case "false":
return false;
default:
return false;
}
}
set
{
if (!IsValid) { return; }
if (client.native.matchmaking.SetLobbyJoinable(CurrentLobby, value))
{
CurrentLobbyData.SetData("joinable", value.ToString());
}
}
}
/// <summary>
/// How many people can be in the Lobby
/// </summary>
public int MaxMembers
{
get
{
if (!IsValid) { return 0; } //0 is default, but value is inited when lobby is created.
return client.native.matchmaking.GetLobbyMemberLimit(CurrentLobby);
}
set
{
if (!IsValid) { return; }
client.native.matchmaking.SetLobbyMemberLimit(CurrentLobby, value);
}
}
/// <summary>
/// How many people are currently in the Lobby
/// </summary>
public int NumMembers
{
get { return client.native.matchmaking.GetNumLobbyMembers(CurrentLobby);}
}
/// <summary>
/// Leave the CurrentLobby.
/// </summary>
public void Leave()
{
if (CurrentLobby != 0) { client.native.matchmaking.LeaveLobby(CurrentLobby); }
CurrentLobby = 0;
_owner = 0;
CurrentLobbyData = null;
}
public void Dispose()
{
client = null;
}
/// <summary>
/// Get an array of all the CSteamIDs in the CurrentLobby.
/// Note that you must be in the Lobby you are trying to request the MemberIDs from.
/// Returns an empty array if you aren't in a lobby.
/// </summary>
/// <returns>Array of member SteamIDs</returns>
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;
}
/// <summary>
/// Check to see if a user is in your CurrentLobby
/// </summary>
/// <param name="steamID">SteamID of the user to check for</param>
/// <returns></returns>
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;
}
/// <summary>
/// Invites the specified user to the CurrentLobby the user is in.
/// </summary>
/// <param name="friendID">ulong ID of person to invite</param>
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); }
}
/// <summary>
/// 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
/// </summary>
public Action<ulong, ulong> OnUserInvitedToLobby;
/// <summary>
/// Joins a lobby if a request was made to join the lobby through the friends list or an invite
/// </summary>
internal void OnLobbyJoinRequestedAPI(GameLobbyJoinRequested_t callback, bool error)
{
if (error) { return; }
Join(callback.SteamIDLobby);
}
/// <summary>
/// Makes sure we send an update callback if a Lobby user updates their information
/// </summary>
internal void OnLobbyMemberPersonaChangeAPI(PersonaStateChange_t callback, bool error)
{
if (error || !UserIsInCurrentLobby(callback.SteamID)) { return; }
if (OnLobbyMemberDataUpdated != null) { OnLobbyMemberDataUpdated(callback.SteamID); }
}
/*not implemented
//set the game server of the lobby
client.native.matchmaking.GetLobbyGameServer;
client.native.matchmaking.SetLobbyGameServer;
//used with game server stuff
SteamNative.LobbyGameCreated_t
*/
}
}

View File

@ -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)
};
}
}
}
}

View File

@ -0,0 +1,183 @@
using System;
using System.Collections.Generic;
using System.Text;
using SteamNative;
namespace Facepunch.Steamworks
{
public partial class LobbyList : IDisposable
{
internal Client client;
//The list of retrieved lobbies
public List<Lobby> 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<ulong> requests;
internal LobbyList(Client client)
{
this.client = client;
Lobbies = new List<Lobby>();
requests = new List<ulong>();
}
/// <summary>
/// Refresh the List of Lobbies. If no filter is passed in, a default one is created that filters based on AppId ("appid").
/// </summary>
/// <param name="filter"></param>
public void Refresh ( Filter filter = null)
{
//init out values
Lobbies.Clear();
requests.Clear();
Finished = false;
if (filter == null)
{
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<string, string> fil in filter.StringFilters)
{
client.native.matchmaking.AddRequestLobbyListStringFilter(fil.Key, fil.Value, SteamNative.LobbyComparison.Equal);
}
foreach (KeyValuePair<string, int> fil in filter.NearFilters)
{
client.native.matchmaking.AddRequestLobbyListNearValueFilter(fil.Key, fil.Value);
}
//foreach (KeyValuePair<string, KeyValuePair<Filter.Comparison, int>> 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);
}
void OnLobbyList(LobbyMatchList_t callback, bool error)
{
if (error) return;
//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
{
//else we need to get the info for the missing lobby
client.native.matchmaking.RequestLobbyData(lobby);
SteamNative.LobbyDataUpdate_t.RegisterCallback(client, OnLobbyDataUpdated);
}
}
checkFinished();
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 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;
public void Dispose()
{
client = null;
}
public class Filter
{
// Filters that match actual metadata keys exactly
public Dictionary<string, string> StringFilters = new Dictionary<string, string>();
// Filters that are of string key and int value for that key to be close to
public Dictionary<string, int> NearFilters = new Dictionary<string, int>();
//Filters that are of string key and int value, with a comparison filter to say how we should relate to the value
//public Dictionary<string, KeyValuePair<Comparison, int>> NumericalFilters = new Dictionary<string, KeyValuePair<Comparison, int>>();
public Distance DistanceFilter = Distance.Worldwide;
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
}
}
}
}

View File

@ -41,7 +41,7 @@ You can view examples of everything in the Facepunch.Steamworks.Test project.
## Client ## 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 ```csharp
var client = new Facepunch.Steamworks.Client( 252490 ); var client = new Facepunch.Steamworks.Client( 252490 );
@ -68,6 +68,34 @@ 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. 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
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]);
```
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 # Unity
Yeah this works under Unity. That's half the point of it. Yeah this works under Unity. That's half the point of it.