Files
MSCO/SteamLobby.cs
T
MrMahoandClaude Opus 5.5 ea1b0f74d9 Mod 2.8.1: separate mod version from network protocol version
ModVersion (2.8.1, shown in MSCLoader/MSC Launcher) and ProtocolVersion
(2.8) are separate; only the first two digits are compared (P2P host, Steam
lobby, server info, server browser) and only the protocol version is sent,
so minor updates stay compatible with 2.8 servers, hosts and players.
README: how to publish minor and major updates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 17:48:07 +02:00

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.ProtocolVersion); // ältere Mods vergleichen genau
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) && !MSCO.Compatible(hostVersion))
{
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);
}
}
}