Files
MSCO/SteamLobby.cs
T

228 lines
8.0 KiB
C#

using System;
using MSCLoader;
using Steamworks;
namespace MSCO
{
/// <summary>
/// Steam-Lobby für P2P: Host erstellt eine Freunde-Lobby, lädt über das Steam-Overlay ein,
/// Freunde treten über die Einladung bzw. "Spiel beitreten" bei. Die eigentlichen Daten
/// laufen danach über Steam P2P (siehe SteamConnection).
/// </summary>
public class SteamLobby
{
const string VersionKey = "msco_version";
const uint ChatMemberLeftMask = 2 | 4 | 8 | 16; // Left, Disconnected, Kicked, Banned
public CSteamID LobbyId { get; private set; }
public bool InLobby { get { return LobbyId.m_SteamID != 0; } }
/// <summary>Lobby erstellt (Host).</summary>
public event Action<CSteamID> Created;
/// <summary>Lobby betreten (Client): Lobby-ID und Host.</summary>
public event Action<CSteamID, CSteamID> Entered;
/// <summary>Einladung angenommen / "Spiel beitreten" im Freundesfenster.</summary>
public event Action<CSteamID> JoinRequested;
/// <summary>Jemand hat die Lobby verlassen (Steam-ID).</summary>
public event Action<CSteamID> MemberLeft;
/// <summary>Fehler (Text für die Statuszeile).</summary>
public event Action<string> Failed;
CallResult<LobbyCreated_t> createdResult;
CallResult<LobbyEnter_t> enterResult;
Callback<GameLobbyJoinRequested_t> joinRequestedCallback;
Callback<LobbyChatUpdate_t> chatUpdateCallback;
Callback<P2PSessionRequest_t> sessionRequestCallback;
bool initialized;
public static bool IsAvailable
{
get
{
try { return SteamAPI.IsSteamRunning() && SteamUser.GetSteamID().m_SteamID != 0; }
catch { return false; }
}
}
public static string PersonaName
{
get
{
try { return SteamFriends.GetPersonaName(); }
catch { return null; }
}
}
public static ulong OwnSteamId
{
get
{
try { return SteamUser.GetSteamID().m_SteamID; }
catch { return 0; }
}
}
static HAuthTicket authTicket;
static bool hasAuthTicket;
/// <summary>Steam-Anmeldeticket für dedizierte Server (der Server kann es bei Steam prüfen).</summary>
public static byte[] CreateAuthTicket()
{
try
{
CancelAuthTicket();
var buffer = new byte[1024];
uint length;
authTicket = SteamUser.GetAuthSessionTicket(buffer, buffer.Length, out length);
hasAuthTicket = true;
var ticket = new byte[length];
Array.Copy(buffer, ticket, length);
return ticket;
}
catch
{
return new byte[0];
}
}
public static void CancelAuthTicket()
{
if (!hasAuthTicket) return;
hasAuthTicket = false;
try { SteamUser.CancelAuthTicket(authTicket); } catch { }
}
public bool Init()
{
if (initialized) return true;
if (!IsAvailable) return false;
try
{
createdResult = CallResult<LobbyCreated_t>.Create(OnLobbyCreated);
enterResult = CallResult<LobbyEnter_t>.Create(OnLobbyEntered);
joinRequestedCallback = Callback<GameLobbyJoinRequested_t>.Create(OnJoinRequested);
chatUpdateCallback = Callback<LobbyChatUpdate_t>.Create(OnChatUpdate);
sessionRequestCallback = Callback<P2PSessionRequest_t>.Create(OnSessionRequest);
initialized = true;
}
catch (Exception e)
{
MscoLog.Error("[MSCO] Steam init failed: " + e.Message);
}
return initialized;
}
/// <summary>Jedes Frame aufrufen, damit Steam-Callbacks ankommen.</summary>
public void Tick()
{
if (!initialized) return;
try { SteamAPI.RunCallbacks(); } catch { }
}
public void Create(int maxPlayers)
{
if (!Init()) { Fail("Steam is not running"); return; }
Leave();
createdResult.Set(SteamMatchmaking.CreateLobby(ELobbyType.k_ELobbyTypeFriendsOnly, maxPlayers));
}
public void Join(CSteamID lobby)
{
if (!Init()) { Fail("Steam is not running"); return; }
Leave();
enterResult.Set(SteamMatchmaking.JoinLobby(lobby));
}
public void OpenInviteDialog()
{
if (InLobby) SteamFriends.ActivateGameOverlayInviteDialog(LobbyId);
}
public void Leave()
{
if (!InLobby) return;
try { SteamMatchmaking.LeaveLobby(LobbyId); } catch { }
LobbyId = new CSteamID(0);
}
public bool IsMember(CSteamID user)
{
if (!InLobby) return false;
int count = SteamMatchmaking.GetNumLobbyMembers(LobbyId);
for (int i = 0; i < count; i++)
if (SteamMatchmaking.GetLobbyMemberByIndex(LobbyId, i) == user) return true;
return false;
}
// ---------------------------------------------------------------- Callbacks
void OnLobbyCreated(LobbyCreated_t result, bool ioFailure)
{
if (ioFailure || result.m_eResult != EResult.k_EResultOK)
{
Fail("Could not create Steam lobby (" + result.m_eResult + ")");
return;
}
LobbyId = new CSteamID(result.m_ulSteamIDLobby);
SteamMatchmaking.SetLobbyData(LobbyId, VersionKey, MSCO.ModVersion);
if (Created != null) Created(LobbyId);
}
void OnLobbyEntered(LobbyEnter_t result, bool ioFailure)
{
if (ioFailure || result.m_EChatRoomEnterResponse != 1) // 1 = Success
{
Fail("Could not join the lobby (full or closed)");
return;
}
LobbyId = new CSteamID(result.m_ulSteamIDLobby);
string hostVersion = SteamMatchmaking.GetLobbyData(LobbyId, VersionKey);
if (!string.IsNullOrEmpty(hostVersion) && hostVersion != MSCO.ModVersion)
{
Leave();
Fail("Version mismatch (host " + hostVersion + ", you " + MSCO.ModVersion + ")");
return;
}
CSteamID owner = SteamMatchmaking.GetLobbyOwner(LobbyId);
if (Entered != null) Entered(LobbyId, owner);
}
void OnJoinRequested(GameLobbyJoinRequested_t request)
{
if (JoinRequested != null) JoinRequested(request.m_steamIDLobby);
}
void OnChatUpdate(LobbyChatUpdate_t update)
{
if (update.m_ulSteamIDLobby != LobbyId.m_SteamID) return;
if ((update.m_rgfChatMemberStateChange & ChatMemberLeftMask) == 0) return;
if (MemberLeft != null) MemberLeft(new CSteamID(update.m_ulSteamIDUserChanged));
}
void OnSessionRequest(P2PSessionRequest_t request)
{
// Nur Verbindungen von Spielern aus unserer Lobby annehmen
if (IsMember(request.m_steamIDRemote))
SteamNetworking.AcceptP2PSessionWithUser(request.m_steamIDRemote);
}
void Fail(string message)
{
if (Failed != null) Failed(message);
}
/// <summary>Wurde das Spiel über eine Steam-Einladung gestartet? ("+connect_lobby &lt;id&gt;")</summary>
public static CSteamID LobbyFromCommandLine()
{
string[] args = Environment.GetCommandLineArgs();
for (int i = 0; i < args.Length - 1; i++)
{
ulong id;
if (args[i] == "+connect_lobby" && ulong.TryParse(args[i + 1], out id)) return new CSteamID(id);
}
return new CSteamID(0);
}
}
}