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.
This commit is contained in:
Kyle Kukshtel 2017-07-28 02:20:28 -07:00
parent 7e38750f2e
commit e1c738fc8b
3 changed files with 274 additions and 115 deletions

View File

@ -1,32 +1,41 @@
using System; using System;
using System.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Facepunch.Steamworks.Test namespace Facepunch.Steamworks.Test
{ {
[TestClass]
[DeploymentItem("steam_api.dll")] [DeploymentItem("steam_api.dll")]
[DeploymentItem("steam_api64.dll")] [DeploymentItem("steam_api64.dll")]
[DeploymentItem("steam_appid.txt")] [DeploymentItem("steam_appid.txt")]
[TestClass] public class Lobby
class Lobby
{ {
[TestMethod] [TestMethod]
public void FriendList() public void CreateLobby()
{ {
/*
using (var client = new Facepunch.Steamworks.Client(252490)) using (var client = new Facepunch.Steamworks.Client(252490))
{ {
Assert.IsTrue(client.IsValid); Assert.IsTrue(client.IsValid);
client.Friends.Refresh(); client.Lobby.Create(Steamworks.Lobby.Type.Public, 10);
Assert.IsNotNull(client.Friends.All); client.Lobby.OnLobbyCreated = (success) =>
foreach (var friend in client.Friends.All)
{ {
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);
} }
} }
*/
} }
} }
} }

View File

@ -8,22 +8,16 @@ namespace Facepunch.Steamworks
{ {
public partial class Client : IDisposable public partial class Client : IDisposable
{ {
public void JoinLobby(ulong LobbyID) Lobby _lobby;
{
native.matchmaking.JoinLobby(LobbyID, OnLobbyJoined);
}
void OnLobbyJoined(LobbyEnter_t callback, bool error) public Lobby Lobby
{ {
//TODO: get
} {
if (_lobby == null)
// Create a lobby, auto joins the created lobby _lobby = new Steamworks.Lobby(this);
public Lobby CreateLobby(Lobby.Type lobbyType, int maxMembers) return _lobby;
{ }
var lobby = new Lobby(this, lobbyType);
native.matchmaking.CreateLobby((SteamNative.LobbyType)lobbyType, maxMembers, lobby.OnLobbyCreatedAPI);
return lobby;
} }
} }
public class Lobby : IDisposable public class Lobby : IDisposable
@ -34,47 +28,63 @@ public enum Type : int
Private = SteamNative.LobbyType.Private, Private = SteamNative.LobbyType.Private,
FriendsOnly = SteamNative.LobbyType.FriendsOnly, FriendsOnly = SteamNative.LobbyType.FriendsOnly,
Public = SteamNative.LobbyType.Public, Public = SteamNative.LobbyType.Public,
Invisible = SteamNative.LobbyType.Invisible Invisible = SteamNative.LobbyType.Invisible,
None
} }
internal Client client; internal Client client;
/// <summary> public Lobby(Client c)
/// Returns true if we tried to create this lobby but it returned
/// an error.
/// </summary>
public bool IsError { get; private set; }
/// <summary>
/// Returns true if this lobby is valid, ie, we've received
/// a positive response from Steam about it.
/// </summary>
public bool IsValid => LobbyID != 0;
/// <summary>
/// The CSteamID of the lobby that was created
/// </summary>
internal ulong LobbyID { get; private set; }
/// <summary>
/// The name of the lobby as a property for easy getting/setting
/// </summary>
public string Name
{
get { return _name; }
set { if (_name == value) return; SetLobbyData("name", value); }
}
string _name = "";
/// <summary>
/// Callback for when lobby is created
/// </summary>
public Action OnLobbyCreated;
public Lobby(Client c, Type type)
{ {
client = c; client = c;
LobbyType = type; SteamNative.LobbyDataUpdate_t.RegisterCallback(client, OnLobbyDataUpdated);
}
/// <summary>
/// The CSteamID of the lobby we're currently in.
/// </summary>
public ulong CurrentLobby { get; private set; }
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. LobbyJoined is called when the lobby has successfully been joined.
/// </summary>
/// <param name="lobbyID">CSteamID of lobby to join</param>
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<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);
LobbyType = lobbyType;
} }
internal void OnLobbyCreatedAPI(LobbyCreated_t callback, bool error) internal void OnLobbyCreatedAPI(LobbyCreated_t callback, bool error)
@ -82,105 +92,239 @@ internal void OnLobbyCreatedAPI(LobbyCreated_t callback, bool error)
//from SpaceWarClient.cpp 793 //from SpaceWarClient.cpp 793
if (error || (callback.Result != Result.OK)) if (error || (callback.Result != Result.OK))
{ {
IsError = true; if ( OnLobbyCreated != null) { OnLobbyCreated(false); }
return; return;
} }
Owner = client.SteamId; //this is implicitly set on creation but need to cache it here //set owner specific properties
LobbyID = callback.SteamIDLobby; Owner = client.SteamId;
MaxMembers = client.native.matchmaking.GetLobbyMemberLimit(LobbyID); CurrentLobby = callback.SteamIDLobby;
SetLobbyData("appid", client.AppId.ToString()); CurrentLobbyData = new LobbyData(client, CurrentLobby);
Name = client.Username + "'s Lobby";
if (OnLobbyCreated != null) { OnLobbyCreated(); } CurrentLobbyData.SetData("appid", client.AppId.ToString());
CurrentLobbyData.SetData("lobbytype", LobbyType.ToString());
if (OnLobbyCreated != null) { OnLobbyCreated(true); }
} }
Dictionary<string, string> LobbyData = new Dictionary<string, string>(); /// <summary>
public void SetLobbyData(string key, string value) /// Callback for when lobby is created
/// </summary>
public Action<bool> OnLobbyCreated;
public class LobbyData
{ {
if (LobbyData.ContainsKey(key)) internal Client client;
{ internal ulong lobby;
if (LobbyData[key] == value) internal Dictionary<string, string> data;
return;
LobbyData[key] = value; public LobbyData(Client c, ulong l)
}
else
{ {
LobbyData.Add(key, value); client = c;
lobby = l;
data = new Dictionary<string, string>();
}
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
}
/// <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);
}
}
} }
public Type LobbyType 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 set
{ {
if (_lobbyType == value) return; if(!IsValid) { return; }
//only call the proper method if the lobby is valid, otherwise cache the value if(client.native.matchmaking.SetLobbyType(CurrentLobby, (SteamNative.LobbyType)value))
if(IsValid)
{ {
client.native.matchmaking.SetLobbyType(LobbyID, (SteamNative.LobbyType)value); //returns bool? CurrentLobbyData.SetData("lobbytype", value.ToString());
} }
_lobbyType = value;
} }
} }
Type _lobbyType;
/// <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);
}
}
//Must be the owner to change the owner /// <summary>
/// The Owner of the current lobby. Returns 0 if you are not in a valid lobby.
/// </summary>
public ulong Owner public ulong Owner
{ {
get get
{ {
if (_owner == 0) if (_owner == 0 && IsValid)
{ {
_owner = client.native.matchmaking.GetLobbyOwner(LobbyID); _owner = client.native.matchmaking.GetLobbyOwner(CurrentLobby);
return _owner;
} }
return _owner; 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; ulong _owner = 0;
// Can the lobby be joined by other people // Can the lobby be joined by other people
public bool Joinable public bool Joinable
{ {
get { return _joinable; } get
set { if (_joinable == value) return; client.native.matchmaking.SetLobbyJoinable(LobbyID, value); _joinable = value; } {
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 // How many people can be in the Lobby
public int MaxMembers public int MaxMembers
{ {
get { return _maxMembers; } get
set { if (_maxMembers == value) return; client.native.matchmaking.SetLobbyMemberLimit(LobbyID, value); _maxMembers = value; } {
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 //How many people are currently in the lobby
public int NumMembers public int NumMembers
{ {
get { return client.native.matchmaking.GetNumLobbyMembers(LobbyID);} get { return client.native.matchmaking.GetNumLobbyMembers(CurrentLobby);}
} }
//leave the current lobby //leave the current lobby
public void Leave() public void Leave()
{ {
client.native.matchmaking.LeaveLobby(LobbyID); client.native.matchmaking.LeaveLobby(CurrentLobby);
_owner = 0;
CurrentLobbyData = null;
} }
public void Dispose() public void Dispose()
@ -189,8 +333,6 @@ public void Dispose()
} }
/*not implemented /*not implemented
// returns a lobby metadata key/values pair by index
client.native.matchmaking.GetLobbyDataByIndex;
//set the game server of the lobby //set the game server of the lobby
client.native.matchmaking.GetLobbyGameServer; client.native.matchmaking.GetLobbyGameServer;
@ -199,22 +341,15 @@ public void Dispose()
//data for people in the actual lobby - scores/elo/characters/etc. //data for people in the actual lobby - scores/elo/characters/etc.
client.native.matchmaking.SetLobbyMemberData; //local user client.native.matchmaking.SetLobbyMemberData; //local user
client.native.matchmaking.GetLobbyMemberData; //any user in this lobby client.native.matchmaking.GetLobbyMemberData; //any user in this lobby
// returns steamid of member // returns steamid of member
// note that the current user must be in a lobby to retrieve CSteamIDs of other users in that lobby // note that the current user must be in a lobby to retrieve CSteamIDs of other users in that lobby
client.native.matchmaking.GetLobbyMemberByIndex; client.native.matchmaking.GetLobbyMemberByIndex;
//for linking lobbies idk havent looked hard yet
client.native.matchmaking.SetLinkedLobby;
//chat functions //chat functions
client.native.matchmaking.SendLobbyChatMsg; client.native.matchmaking.SendLobbyChatMsg;
client.native.matchmaking.GetLobbyChatEntry; client.native.matchmaking.GetLobbyChatEntry;
//get total data count (why?)
client.native.matchmaking.GetLobbyDataCount
//invite your frans //invite your frans
client.native.matchmaking.InviteUserToLobby //this invites the user the current lobby the invitee is in client.native.matchmaking.InviteUserToLobby //this invites the user the current lobby the invitee is in
*/ */

View File

@ -70,17 +70,32 @@ void OnLobbyList(LobbyMatchList_t callback, bool error)
} }
else else
{ {
//need to sub the lobbyid to the request func //need to get the info for the missing lobby
//client.native.matchmaking.RequestLobbyData(lobby) client.native.matchmaking.RequestLobbyData(lobby);
SteamNative.LobbyDataUpdate_t.RegisterCallback(client, OnLobbyDataUpdated);
} }
} }
if (OnLobbiesRefreshed != null) { OnLobbiesRefreshed(); } if (OnLobbiesUpdated != null) { OnLobbiesUpdated(); }
} }
void OnLobbyDataUpdated(LobbyDataUpdate_t callback, bool error)
public Action OnLobbiesRefreshed; {
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() public void Dispose()
{ {