using System;
using MSCLoader;
using Steamworks;
namespace MSCO
{
///
/// 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).
///
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; } }
/// Lobby erstellt (Host).
public event Action Created;
/// Lobby betreten (Client): Lobby-ID und Host.
public event Action Entered;
/// Einladung angenommen / "Spiel beitreten" im Freundesfenster.
public event Action JoinRequested;
/// Jemand hat die Lobby verlassen (Steam-ID).
public event Action MemberLeft;
/// Fehler (Text für die Statuszeile).
public event Action Failed;
CallResult createdResult;
CallResult enterResult;
Callback joinRequestedCallback;
Callback chatUpdateCallback;
Callback 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;
/// Steam-Anmeldeticket für dedizierte Server (der Server kann es bei Steam prüfen).
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.Create(OnLobbyCreated);
enterResult = CallResult.Create(OnLobbyEntered);
joinRequestedCallback = Callback.Create(OnJoinRequested);
chatUpdateCallback = Callback.Create(OnChatUpdate);
sessionRequestCallback = Callback.Create(OnSessionRequest);
initialized = true;
}
catch (Exception e)
{
MscoLog.Error("[MSCO] Steam init failed: " + e.Message);
}
return initialized;
}
/// Jedes Frame aufrufen, damit Steam-Callbacks ankommen.
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);
}
/// Wurde das Spiel über eine Steam-Einladung gestartet? ("+connect_lobby <id>")
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);
}
}
}