TH1/Unity/Assets/Scripts/TH1_Logic/Steam/SteamLobbyManager.cs

1488 lines
59 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Logic;
using Logic.CrashSight;
using Logic.Multilingual;
using MemoryPack;
using RuntimeData;
using Steamworks;
using TH1_Core.Events;
using TH1_Core.Managers;
using TH1_Logic.Config;
using TH1_Logic.Core;
using TH1_Logic.Net;
using UnityEngine;
namespace TH1_Logic.Steam
{
// 网络状态信息结构
public struct SteamNetworkStatus
{
public bool IsSteamConnected; // Steam是否连接
public bool IsLoggedOn; // 是否登录
public bool IsP2PReady; // P2P是否就绪
public bool IsInLobby; // 是否在房间中
public int ConnectedPeersCount; // 已连接的P2P节点数量
public string ConnectionQuality; // 连接质量描述
public string DetailedStatus; // 详细状态描述
public bool HasActiveP2PConnections; // 是否有活跃的P2P连接
public bool SteamServerConnected; // Steam服务器连接状态
}
public class SteamLobbyManager : ILobby
{
// 登录状态
private bool _isLoggedIn = false;
public bool IsloggedIn => _isLoggedIn;
// steam 初始化状态
private bool _isSteamInitialized = false;
public bool IsSteamInitialized => _isSteamInitialized;
// lobby 初始化状态
private bool _isLobbyInitialized = false;
public bool IsLobbyInitialized => _isLobbyInitialized;
private SteamNetworkStatus _status;
public SteamNetworkStatus Status => _status;
// 个人信息
private string _selfName = "";
private CSteamID _selfID;
public string SelfName => _selfName;
public CSteamID SelfID => _selfID;
private int _maxLobbyMembers = 4;
private GameObject _guiObj;
public CSteamID CurrentLobby = CSteamID.Nil;
public CSteamID CachedOwner = CSteamID.Nil;
public LobbyState CurrentState { get; private set; } = LobbyState.None;
// 定时刷新
private float _steamSDKUpdateRecord;
private Dictionary<ulong, CSteamID> _onlineFriendsId;
private Dictionary<ulong, MemberInfo> _onlineFriendsInfo;
private Dictionary<ulong, MemberInfo> _memberInfos;
private Dictionary<ulong, CSteamID> _membersCache;
private float _onlineFriendsIdUpdateRecord;
private float _kickInfoUpdateRecord;
private float _refreshSteamStatus;
private bool _steamServerConnected = false;
private int _steamSessionFailCount;
private bool _isHandlingSessionLoss;
private int _suppressP2PSendFailureLobbyErrors;
private const float SteamSessionCheckInterval = 1f;
private const int SteamSessionFailThreshold = 2;
private string RoomName;
// 房间列表
private List<LobbyListInfo> _lobbyListInfos;
public List<LobbyListInfo> LobbyListInfos => _lobbyListInfos;
// 事件委托
public event System.Action<CSteamID> OnLobbyCreatedEvent; // 房间创建成功
public event System.Action<CSteamID> OnLobbyEnteredEvent; // 进入房间
public event System.Action<List<CSteamID>> OnLobbyLeftEvent; // 离开房间
public event System.Action<CSteamID, CSteamID> OnHostChangedEvent; // 房主变更 (oldHost, newHost)
public event System.Action<List<CSteamID>> OnMembersChangedEvent; // 成员变化
public event System.Action<string> OnLobbyErrorEvent; // 房间错误
public event System.Action<CSteamID> OnMemberJoinedEvent; // 成员加入
public event System.Action<CSteamID> OnMemberLeftEvent; // 成员离开
// LobbyCreated_t
// 谁会收到:只有调用 CreateLobby 的本机(创建者)。
// 何时触发_kickInfoUpdateRecord.CreateLobby 异步完成后。
// 成功判定data.m_eResult == k_EResultOK。失败则不会进入房间也不会再收到 LobbyEnter_t。
// 用途:确认为房主,保存 LobbyID设置 LobbyData / Rich Presence然后等别人加入。
private Callback<LobbyCreated_t> _cbLobbyCreated;
// 谁会收到:被邀请者、或通过好友资料 / 邀请链接 / Overlay 点"加入"你 Lobby 的玩家客户端。
// 何时触发:玩家端点击接受邀请(或点击你在好友列表中的"加入游戏"Steam 向你的进程派发此回调。
// 典型处理:立即调用 SteamMatchmaking.JoinLobby(data.m_steamIDLobby)。不代表已经进房,只是"请求加入"。
private Callback<GameLobbyJoinRequested_t> _cbLobbyJoinRequested;
// 谁会收到:任意成功进入 Lobby 的客户端(包括创建者自己与每个加入者)。
// 何时触发JoinLobby或 CreateLobby 成功后的内部自动加入)完成并真正加入成员列表后。
// 作用:此时可读取成员列表 / LobbyData更新 UI开始建立 P2P。
// 注意:创建者会先收到 LobbyCreated_t随后收到自己的 LobbyEnter_t。被邀请者只会收到 GameLobbyJoinRequested_t →(调用 JoinLobby→ LobbyEnter_t。
private Callback<LobbyEnter_t> _cbLobbyEnter;
// 谁会收到:当前已在该 Lobby 中的所有成员。
// 何时触发:成员进入、离开、断线、被踢、被封禁等成员列表变化时。可能多次。
// 数据意义data.m_ulSteamIDLobby = 目标 Lobbydata.m_ulSteamIDUserChanged = 发生变化的成员data.m_ulSteamIDMakingChange = 触发者踢人时是房主data.m_rgfChatMemberStateChange 标志位指示加入/离开/踢/封禁等。
// 用途:刷新成员 UI迁移 Host若房主离开, 清理对应的 P2P 连接。
private Callback<LobbyChatUpdate_t> _cbLobbyChatUpdate;
private Callback<SteamServersConnected_t> _cbSteamServersConnected;
private Callback<SteamServersDisconnected_t> _cbSteamServersDisconnected;
private Callback<SteamServerConnectFailure_t> _cbSteamServerConnectFailure;
private Callback<SteamShutdown_t> _cbSteamShutdown;
// 搜索公开房间
private Callback<LobbyMatchList_t> _cbLobbyMatchList;
public SteamLobbyManager()
{
_steamSDKUpdateRecord = 2;
_onlineFriendsIdUpdateRecord = 2;
_kickInfoUpdateRecord = 2;
_refreshSteamStatus = 0;
_steamSessionFailCount = 0;
_isHandlingSessionLoss = false;
_onlineFriendsId = new Dictionary<ulong, CSteamID>();
_onlineFriendsInfo = new Dictionary<ulong, MemberInfo>();
_memberInfos = new Dictionary<ulong, MemberInfo>();
_membersCache = new Dictionary<ulong, CSteamID>();
_lobbyListInfos = new List<LobbyListInfo>();
_status = new SteamNetworkStatus();
}
// 初始化
public void Init()
{
_steamSDKUpdateRecord += Time.deltaTime;
if (_steamSDKUpdateRecord < 2) return;
_steamSDKUpdateRecord = 0;
RefreshSteamInit();
RefreshSteamStatus();
//RefreshSteamGUI();
RefreshLoginStatus();
RefreshLobbyStatus();
}
// 刷新 Steam
private void RefreshSteamInit()
{
if (_isSteamInitialized) return;
// 检查Steam是否运行
if (!SteamAPI.IsSteamRunning())
{
// LogSystem.LogError("Steam客户端未运行请先启动Steam。");
return;
}
// 初始化Steam API
LogSystem.LogInfo("开始初始化Steam...");
var initResult = SteamAPI.InitEx(out string steamErrMsg);
_isSteamInitialized = initResult == ESteamAPIInitResult.k_ESteamAPIInitResult_OK;
if (!_isSteamInitialized)
{
LogSystem.LogError($"Steam API初始化失败result={initResult}, msg={steamErrMsg}");
switch (initResult)
{
case ESteamAPIInitResult.k_ESteamAPIInitResult_NoSteamClient:
LogSystem.LogError("→ Steam客户端未运行或未登录请先打开Steam并登录账号。");
break;
case ESteamAPIInitResult.k_ESteamAPIInitResult_VersionMismatch:
LogSystem.LogError("→ Steam客户端版本过旧请在Steam中检查更新。");
break;
case ESteamAPIInitResult.k_ESteamAPIInitResult_FailedGeneric:
LogSystem.LogError("→ 通用失败。常见原因当前Steam账号未拥有该AppID需在Steamworks后台授予权限或工作目录下steam_appid.txt位置错误或上一次进程未退干净。");
break;
}
return;
}
LogSystem.LogInfo("Steam API初始化成功");
// 显示启动信息
DisplayLaunchInfo();
CheckSteamAppIdFile();
}
// 刷新 Steam 状态
private void RefreshSteamStatus()
{
if (!_isSteamInitialized) return;
// 基础Steam连接状态
_status.IsSteamConnected = SteamAPI.IsSteamRunning();
_status.IsLoggedOn = SteamUser.BLoggedOn();
_status.IsInLobby = IsInLobby();
_status.IsP2PReady = SimpleP2P.Instance?.IsInitialized ?? false;
_status.SteamServerConnected = IsSteamSessionLikelyAlive();
_steamServerConnected = _status.SteamServerConnected;
}
// 刷新用户登录状态
private void RefreshLoginStatus()
{
if (!_isSteamInitialized || _isLoggedIn) return;
try
{
_isLoggedIn = SteamUser.BLoggedOn();
if (_isLoggedIn)
{
_selfID = SteamUser.GetSteamID();
_selfName = SteamFriends.GetPersonaName();
LogSystem.LogInfo($"Steam用户已登录: {_selfName} ({_selfID})");
}
else
{
LogSystem.LogWarning("Steam用户未登录");
}
}
catch (System.Exception e)
{
LogSystem.LogError($"检查用户登录状态异常: {e.Message}");
}
}
// 刷新 lobby 相关
private void RefreshLobbyStatus()
{
if (!_isSteamInitialized || !_isLoggedIn) return;
if (_isLobbyInitialized) return;
_cbLobbyCreated = Callback<LobbyCreated_t>.Create(OnLobbyCreatedCallback);
_cbLobbyJoinRequested = Callback<GameLobbyJoinRequested_t>.Create(OnLobbyJoinRequestedCallback);
_cbLobbyEnter = Callback<LobbyEnter_t>.Create(OnLobbyEnterCallback);
_cbLobbyChatUpdate = Callback<LobbyChatUpdate_t>.Create(OnLobbyChatUpdateCallback);
_cbSteamServersConnected = Callback<SteamServersConnected_t>.Create(OnSteamServersConnectedCallback);
_cbSteamServersDisconnected = Callback<SteamServersDisconnected_t>.Create(OnSteamServersDisconnectedCallback);
_cbSteamServerConnectFailure = Callback<SteamServerConnectFailure_t>.Create(OnSteamServerConnectFailureCallback);
_cbSteamShutdown = Callback<SteamShutdown_t>.Create(OnSteamShutdownCallback);
// 初始化P2P
SimpleP2P.Instance.Initialize();
// 订阅P2P事件
SimpleP2P.Instance.OnPeerConnectedEvent += OnP2PPeerConnected;
SimpleP2P.Instance.OnPeerDisconnectedEvent += OnP2PPeerDisconnected;
SimpleP2P.Instance.OnConnectionErrorEvent += OnP2PConnectionError;
SimpleP2P.Instance.OnMessageSendFailedEvent += OnP2PMessageSendFailed;
LogSystem.LogInfo("SteamLobbyManager initialized");
_isLobbyInitialized = true;
}
// 定时更新在线好友
private void RefreshOnlineFriends()
{
_onlineFriendsIdUpdateRecord += Time.deltaTime;
if (_onlineFriendsIdUpdateRecord > 2)
{
_onlineFriendsIdUpdateRecord = 0;
var friends = GetOnlineFriends();
// 添加房间内的非好友成员
if (CurrentLobby.IsValid())
{
int memberCount = SteamMatchmaking.GetNumLobbyMembers(CurrentLobby);
for (int i = 0; i < memberCount; i++)
{
var memberId = SteamMatchmaking.GetLobbyMemberByIndex(CurrentLobby, i);
// 跳过自己
if (memberId == SteamUser.GetSteamID()) continue;
// 检查是否已在好友列表中
if (friends.All(f => f.id != memberId))
{
string memberName = SteamFriends.GetFriendPersonaName(memberId);
friends.Add((memberId, memberName));
}
}
}
foreach (var kv in friends)
{
if (!_membersCache.ContainsKey(kv.id.m_SteamID)) _membersCache[kv.id.m_SteamID] = kv.id;
if (!_onlineFriendsId.ContainsKey(kv.id.m_SteamID))
{
_onlineFriendsId[kv.id.m_SteamID] = kv.id;
}
if (!_onlineFriendsInfo.ContainsKey(kv.id.m_SteamID))
{
_onlineFriendsInfo[kv.id.m_SteamID] = new MemberInfo();
_onlineFriendsInfo[kv.id.m_SteamID].Id = kv.id.m_SteamID;
_onlineFriendsInfo[kv.id.m_SteamID].Name = kv.name;
_onlineFriendsInfo[kv.id.m_SteamID].Texture = GetMemberAvatar(kv.id.m_SteamID);
}
}
foreach (var kv in _onlineFriendsInfo)
{
if (kv.Value.Texture == null) kv.Value.Texture = GetMemberAvatar(kv.Key);
}
foreach (var kv in _memberInfos)
{
if (kv.Value.Texture == null) kv.Value.Texture = GetMemberAvatar(kv.Key);
}
}
}
// 定时刷新踢人信息
private void RefreshKickInfo()
{
_kickInfoUpdateRecord += Time.deltaTime;
if (_kickInfoUpdateRecord > 2)
{
_kickInfoUpdateRecord = 0;
CheckIfKicked();
}
}
// 定时更新
public void Update()
{
Init();
if (!_isSteamInitialized || !_isLoggedIn || !_isLobbyInitialized) return;
// 更新Steam回调
SteamAPI.RunCallbacks();
SimpleP2P.Instance.Update();
SimpleP2P.Instance.PollMessages();
RefreshOnlineFriends();
RefreshKickInfo();
CheckSteamSessionHealth();
}
private void CheckSteamSessionHealth()
{
if (!IsInLobby() || _isHandlingSessionLoss)
{
_steamSessionFailCount = 0;
return;
}
_refreshSteamStatus += Time.deltaTime;
if (_refreshSteamStatus < SteamSessionCheckInterval) return;
_refreshSteamStatus = 0f;
var alive = IsSteamSessionLikelyAlive();
if (alive)
{
_steamServerConnected = true;
_steamSessionFailCount = 0;
return;
}
_steamServerConnected = false;
_steamSessionFailCount++;
if (_steamSessionFailCount < SteamSessionFailThreshold) return;
HandleLocalSteamSessionLost("Steam session health check failed");
}
private void HandleLocalSteamSessionLost(string reason)
{
var inMultiGame = Main.MapData?.Net != null && Main.MapData.Net.Mode == NetMode.Multi;
if (!IsInLobby() && !inMultiGame) return;
if (_isHandlingSessionLoss) return;
_isHandlingSessionLoss = true;
LogSystem.LogWarning($"Local Steam session lost: {reason}");
ForceQuitCurrentMultiplayerSession(reason);
}
// 从房间异常退出时
private void ForceQuitCurrentMultiplayerSession(string reason)
{
LogSystem.LogWarning($"Force leaving multiplayer session. reason: {reason}");
if (Main.Instance?.GameLogic != null &&
Main.MapData?.Net != null &&
Main.MapData.Net.Mode == NetMode.Multi &&
Main.Instance.GameLogic.GetCurState() != GameState.Menu)
{
EventManager.Publish(new ExecuteUIBottomBottomBarQuit());
}
}
// 简单联机健康判定:需 Steam 运行 + 已登录 + 中继网络就绪
public bool IsSteamSessionLikelyAlive()
{
if (!_isSteamInitialized) return false;
if (!SteamAPI.IsSteamRunning()) return false;
if (!SteamUser.BLoggedOn()) return false;
var avail = SteamNetworkingUtils.GetRelayNetworkStatus(out var details);
bool relayOk = avail == ESteamNetworkingAvailability.k_ESteamNetworkingAvailability_Current
|| details.m_eAvail == ESteamNetworkingAvailability.k_ESteamNetworkingAvailability_Current;
return relayOk;
}
// 检查 steam_appid.txt 文件
private void CheckSteamAppIdFile()
{
string steamAppIdPath = Path.Combine(Directory.GetCurrentDirectory(), "steam_appid.txt");
if (File.Exists(steamAppIdPath))
{
try
{
string content = File.ReadAllText(steamAppIdPath).Trim();
LogSystem.LogInfo($"发现steam_appid.txt文件App ID: {content}");
}
catch (System.Exception e)
{
LogSystem.LogWarning($"读取steam_appid.txt失败: {e.Message}");
}
}
else
{
LogSystem.LogInfo("未发现steam_appid.txt文件 - 这在Steam平台发布时是正常的");
}
}
// 启动信息
private void DisplayLaunchInfo()
{
if (!_isSteamInitialized) return;
// 获取当前App ID
var currentAppId = SteamUtils.GetAppID();
LogSystem.LogInfo($"当前Steam App ID: {currentAppId}");
// 检查启动方式
bool launchedViaSteam = SteamApps.BIsSubscribedApp(currentAppId);
LogSystem.LogInfo($"通过Steam启动: {(launchedViaSteam ? "" : "")}");
// 显示Steam环境信息
LogSystem.LogInfo($"Steam语言: {SteamApps.GetCurrentGameLanguage()}");
LogSystem.LogInfo($"Steam服务器连接: {(SteamUser.BLoggedOn() ? "" : "")}");
// 检查DLC和订阅状态
if (currentAppId.m_AppId == 480) // Spacewar测试应用
{
LogSystem.LogInfo("当前使用Spacewar测试应用 - 适用于开发测试");
}
else
{
LogSystem.LogInfo($"当前使用正式应用ID: {currentAppId.m_AppId}");
}
}
// 建房
public void CreateLobby(int maxMembers = 4, bool isPublic = true)
{
if (CurrentState != LobbyState.None)
{
LogSystem.LogInfo($"Cannot create lobby in state: {CurrentState}");
return;
}
CurrentState = LobbyState.Creating;
LogSystem.LogInfo($"Creating public lobby with max members: {maxMembers}");
SteamMatchmaking.CreateLobby(isPublic?ELobbyType.k_ELobbyTypePublic:ELobbyType.k_ELobbyTypeFriendsOnly, maxMembers);
}
// 加入房间
public void JoinLobby(CSteamID lobbyId)
{
if (CurrentState != LobbyState.None)
{
//LogSystem.LogInfo($"Cannot join lobby in state: {CurrentState}");
//return;
}
// TODO 这里会涉及到房间子类对于UI的调用对于房间的多态是不合理的暂不处理糊屎
var version = SteamMatchmaking.GetLobbyData(lobbyId, "Version");
if (!string.IsNullOrEmpty(version) && ConfigManager.Instance.VersionCfg.CurVersionInfo.Version != version)
{
LogSystem.LogInfo($"版本不一致 !!!");
// UI 弹框在这里
return;
}
LogSystem.LogInfo($"Joining lobby: {lobbyId}");
CurrentState = LobbyState.Joining;
SteamMatchmaking.JoinLobby(lobbyId);
}
// 离开房间
public void LeaveLobby()
{
if (!CurrentLobby.IsValid() || CurrentState == LobbyState.None) return;
CurrentState = LobbyState.Leaving;
LogSystem.LogInfo("Leaving lobby");
// 断开所有P2P连接
SimpleP2P.Instance.DisconnectAll();
SteamMatchmaking.LeaveLobby(CurrentLobby);
GameNetSender.Instance.ClearLobbyDataSyncState();
ResetLobbyState();
OnLobbyLeftEvent?.Invoke(null);
}
// 解散房间(仅房主可用)
public void DisbandLobby()
{
if (!IsLobbyOwner())
{
LogSystem.LogInfo("Only lobby owner can disband the lobby");
return;
}
LogSystem.LogInfo("Disbanding lobby");
// 设置房间数据标记解散
SteamMatchmaking.SetLobbyData(CurrentLobby, "disbanded", "true");
// 踢出所有其他成员
foreach (var member in EnumerateMembers())
{
if (member != SteamUser.GetSteamID())
{
// Steam没有直接踢人API通过设置数据让客户端自动离开
SteamMatchmaking.SetLobbyMemberData(CurrentLobby, "kicked", "true");
}
}
// 房主最后离开
LeaveLobby();
}
// 踢出指定成员(仅房主可用)
public void KickMember(ulong memberId)
{
if (!IsLobbyOwner())
{
LogSystem.LogError("Only lobby owner can kick members");
return;
}
if (memberId == SteamUser.GetSteamID().m_SteamID)
{
LogSystem.LogError("Cannot kick yourself");
return;
}
LogSystem.LogInfo($"Kicking member: {memberId}");
// 房主写 LobbyData只有房主有权限设 LobbyData所有客户端都能 GetLobbyData 读到
// 之前用的是 SetLobbyMemberData那个 API 只能设自己的成员数据,写出去对方根本读不到 —— 协议不匹配
// 被踢方在 RefreshKickInfo → CheckIfKicked 里读 kick_{selfId},发现是 "true" 就自动 LeaveLobby
SteamMatchmaking.SetLobbyData(CurrentLobby, $"kick_{memberId}", "true");
}
// 通过房间ID直接加入无需好友关系
public void JoinLobbyById(ulong lobbyId)
{
var lobbyCSteamId = new CSteamID(lobbyId);
JoinLobby(lobbyCSteamId);
}
// 获取当前房间ID用于分享
public ulong GetShareableLobbyId()
{
return CurrentLobby.IsValid() ? CurrentLobby.m_SteamID : 0;
}
// 生成房间码简化的Base36编码
public string GenerateRoomCode()
{
if (!CurrentLobby.IsValid()) return "";
ulong lobbyId = CurrentLobby.m_SteamID;
return Base36Encode(lobbyId);
}
// 通过房间码加入
public void JoinByRoomCode(string code)
{
ulong lobbyId = Base36Decode(code);
if (lobbyId == 0)
{
LogSystem.LogError("Invalid room code");
return;
}
JoinLobbyById(lobbyId);
}
private string Base36Encode(ulong value)
{
const string chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
var result = new System.Text.StringBuilder();
while (value > 0)
{
result.Insert(0, chars[(int)(value % 36)]);
value /= 36;
}
return result.ToString();
}
private ulong Base36Decode(string code)
{
const string chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
ulong result = 0;
foreach (char c in code.ToUpper())
{
int index = chars.IndexOf(c);
if (index < 0) return 0;
result = result * 36 + (ulong)index;
}
return result;
}
// 邀请非好友需要对方Steam ID
public string GenerateUserCode()
{
if (!_isSteamInitialized || !_isLoggedIn) return null;
return Base36Encode(SteamUser.GetSteamID().m_SteamID);
}
public void InviteByRawSteamId(string code)
{
if (!CurrentLobby.IsValid())
{
LogSystem.LogError("Not in a lobby");
return;
}
if (string.IsNullOrEmpty(code))
{
LogSystem.LogError("Invalid user code");
return;
}
ulong steamId = Base36Decode(code);
if (steamId == 0)
{
LogSystem.LogError("Invalid user code format");
return;
}
var targetId = new CSteamID(steamId);
if (!targetId.IsValid())
{
LogSystem.LogError("Invalid Steam ID");
return;
}
LogSystem.LogInfo($"Inviting user by code: {code} (Steam ID: {steamId})");
SteamMatchmaking.InviteUserToLobby(CurrentLobby, targetId);
}
// 切换房间类型(仅房主可用)
public bool SetLobbyType(ELobbyType lobbyType)
{
if (!IsLobbyOwner())
{
LogSystem.LogError("Only lobby owner can change lobby type");
return false;
}
if (!CurrentLobby.IsValid())
{
LogSystem.LogError("Not in a lobby");
return false;
}
bool success = SteamMatchmaking.SetLobbyType(CurrentLobby, lobbyType);
if (success)
{
LogSystem.LogInfo($"Lobby type changed to: {lobbyType}");
}
else
{
LogSystem.LogError($"Failed to change lobby type to: {lobbyType}");
}
return success;
}
// 便捷方法:切换到公开房间
public bool SetLobbyPublic()
{
return SetLobbyType(ELobbyType.k_ELobbyTypePublic);
}
// 便捷方法:切换到仅好友
public bool SetLobbyFriendsOnly()
{
return SetLobbyType(ELobbyType.k_ELobbyTypeFriendsOnly);
}
// 发送游戏内邀请 不走 Steam 邀请框
public bool SendGameInvite(ulong targetSteamId)
{
var lobbyInfo = new LobbyListInfo
{
LobbyId = CurrentLobby.m_SteamID,
OwnerId = _selfID.m_SteamID,
OwnerName = SteamMatchmaking.GetLobbyData(CurrentLobby, "Owner"),
RoomName = SteamMatchmaking.GetLobbyData(CurrentLobby, "RoomName"),
Version = SteamMatchmaking.GetLobbyData(CurrentLobby, "Version"),
CurrentPlayers = SteamMatchmaking.GetNumLobbyMembers(CurrentLobby),
MaxPlayers = SteamMatchmaking.GetLobbyMemberLimit(CurrentLobby),
};
var data = new InviteMessage();
data.LobbyInfo = lobbyInfo;
byte[] bytes = MemoryPackSerializer.Serialize<BaseMessage>(data);
// 优先从缓存获取,否则直接构造 CSteamID
var targetId = _onlineFriendsId.TryGetValue(targetSteamId, out var cachedId) ? cachedId : new CSteamID(targetSteamId);
if (!targetId.IsValid())
{
LogSystem.LogError($"Invalid target Steam ID: {targetSteamId}");
return false;
}
bool isSucceed = SimpleP2P.Instance.SendToWithOutConnect(targetId, bytes);
if (!isSucceed)
{
LogSystem.LogError($"Failed to send game invite to: {targetSteamId}");
return false;
}
LogSystem.LogInfo($"Game invite sent to: {targetSteamId}");
return true;
}
// 搜索房间
public void SearchPublicLobbies(ELobbyDistanceFilter filter = ELobbyDistanceFilter.k_ELobbyDistanceFilterWorldwide,
int count = 100, string roomName = "", string romeCode="", bool onlyMenu = true)
{
_cbLobbyMatchList = Callback<LobbyMatchList_t>.Create(OnLobbyMatchListCallback);
// 添加筛选条件
SteamMatchmaking.AddRequestLobbyListDistanceFilter(filter);
// 限制返回数量
SteamMatchmaking.AddRequestLobbyListResultCountFilter(count);
// 标记游戏名,确保只搜索到本游戏的房间,其实只在测试状态下有用
SteamMatchmaking.AddRequestLobbyListStringFilter("Game", "TOHOTOPIA", ELobbyComparison.k_ELobbyComparisonEqual);
// 只筛选未进游戏的房间(菜单状态)
if (onlyMenu)
SteamMatchmaking.AddRequestLobbyListStringFilter("GameState", "0", ELobbyComparison.k_ELobbyComparisonEqual);
// 按名称筛选
if (!string.IsNullOrEmpty(roomName))
SteamMatchmaking.AddRequestLobbyListStringFilter("RoomName", roomName, ELobbyComparison.k_ELobbyComparisonEqual);
// 搜索制定房间码
if (!string.IsNullOrEmpty(romeCode))
SteamMatchmaking.AddRequestLobbyListStringFilter("RoomCode", romeCode, ELobbyComparison.k_ELobbyComparisonEqual);
// 数值筛选:只显示人数未满的房间
// SteamMatchmaking.AddRequestLobbyListNumericalFilter("open_slots", 1, ELobbyComparison.k_ELobbyComparisonGreaterThan);
SteamMatchmaking.RequestLobbyList();
}
// 搜索结果回调
private void OnLobbyMatchListCallback(LobbyMatchList_t data)
{
_lobbyListInfos.Clear();
for (int i = 0; i < data.m_nLobbiesMatching; i++)
{
var lobbyId = SteamMatchmaking.GetLobbyByIndex(i);
var ownerId = SteamMatchmaking.GetLobbyOwner(lobbyId);
_lobbyListInfos.Add(new LobbyListInfo
{
LobbyId = lobbyId.m_SteamID,
OwnerId = ownerId.m_SteamID,
OwnerName = SteamMatchmaking.GetLobbyData(lobbyId, "Owner"),
RoomName = SteamMatchmaking.GetLobbyData(lobbyId, "RoomName"),
Version = SteamMatchmaking.GetLobbyData(lobbyId, "Version"),
CurrentPlayers = SteamMatchmaking.GetNumLobbyMembers(lobbyId),
MaxPlayers = SteamMatchmaking.GetLobbyMemberLimit(lobbyId),
GameState = int.Parse(SteamMatchmaking.GetLobbyData(lobbyId, "GameState")),
});
}
// 触发UI刷新事件
EventManager.Publish(new UpdateUIOutsideMultiplayLobbyList());
}
// 刷新房间信息
public void RefreshLobbyListInfo()
{
foreach (var lobbyInfo in _lobbyListInfos)
{
var cSteamId = new CSteamID(lobbyInfo.LobbyId);
lobbyInfo.OwnerName = SteamMatchmaking.GetLobbyData(cSteamId, "Owner");
lobbyInfo.RoomName = SteamMatchmaking.GetLobbyData(cSteamId, "RoomName");
lobbyInfo.Version = SteamMatchmaking.GetLobbyData(cSteamId, "Version");
lobbyInfo.CurrentPlayers = SteamMatchmaking.GetNumLobbyMembers(cSteamId);
lobbyInfo.MaxPlayers = SteamMatchmaking.GetLobbyMemberLimit(cSteamId);
lobbyInfo.GameState = int.Parse(SteamMatchmaking.GetLobbyData(cSteamId, "GameState"));
}
}
// 过滤房间名称,只保留中文、英文字母、数字和常用符号
public static string FilterRoomName(string input, int maxLength = 20)
{
if (string.IsNullOrEmpty(input)) return "Default";
var result = new System.Text.StringBuilder();
foreach (char c in input)
{
// 中文字符范围:\u4e00-\u9fff
bool isChinese = c >= '\u4e00' && c <= '\u9fff';
// 英文字母
bool isLetter = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
// 数字
bool isDigit = c >= '0' && c <= '9';
// 常用符号(可根据需要调整)
bool isAllowedSymbol = c == '_' || c == '-' || c == '.';
if (isChinese || isLetter || isDigit || isAllowedSymbol)
{
result.Append(c);
}
// 达到最大长度时停止
if (result.Length >= maxLength) break;
}
if (string.IsNullOrEmpty(result.ToString())) return "Default";
return result.ToString();
}
public bool IsInitialized()
{
return _isSteamInitialized && _isLobbyInitialized && _isLoggedIn && SimpleP2P.Instance.IsInitialized;
}
public void InviteFriend(ulong memberId)
{
if (!CurrentLobby.IsValid())
{
LogSystem.LogError("Not in a lobby");
return;
}
var isSucceed = SendGameInvite(memberId);
if (isSucceed) return;
if (!_onlineFriendsId.ContainsKey(memberId)) return;
LogSystem.LogInfo($"Inviting friend: {memberId}");
SteamMatchmaking.InviteUserToLobby(CurrentLobby, _onlineFriendsId[memberId]);
}
// 打开好友邀请对话框
public void OpenInviteOverlay()
{
if (!CurrentLobby.IsValid())
{
LogSystem.LogError("Not in a lobby");
return;
}
SteamFriends.ActivateGameOverlayInviteDialog(CurrentLobby);
}
// 获取在线好友
public List<(CSteamID id, string name)> GetOnlineFriends()
{
var list = new List<(CSteamID, string)>();
int count = SteamFriends.GetFriendCount(EFriendFlags.k_EFriendFlagImmediate);
for (int i = 0; i < count; i++)
{
var fid = SteamFriends.GetFriendByIndex(i, EFriendFlags.k_EFriendFlagImmediate);
var state = SteamFriends.GetFriendPersonaState(fid);
if (state == EPersonaState.k_EPersonaStateOnline ||
state == EPersonaState.k_EPersonaStateAway ||
state == EPersonaState.k_EPersonaStateBusy ||
state == EPersonaState.k_EPersonaStateSnooze)
{
list.Add((fid, SteamFriends.GetFriendPersonaName(fid)));
}
}
return list;
}
// 获取所有在线好友信息
public void SetGameState(int state)
{
if (!IsInLobby() || !IsLobbyOwner()) return;
if (!CurrentLobby.IsValid()) return;
SteamMatchmaking.SetLobbyData(CurrentLobby, "GameState", state.ToString());
}
public Dictionary<ulong, MemberInfo> GetOnlineFriendsDict()
{
return _onlineFriendsInfo;
}
// 设置房间数据
public void SetLobbyData(string key, string value)
{
if (!IsLobbyOwner())
{
LogSystem.LogError("Only lobby owner can set lobby data");
return;
}
if (!CurrentLobby.IsValid()) return;
SteamMatchmaking.SetLobbyData(CurrentLobby, key, value);
}
// 获取房间数据
public string GetLobbyData(string key)
{
if (!CurrentLobby.IsValid()) return "";
return SteamMatchmaking.GetLobbyData(CurrentLobby, key);
}
// 房间创建回调
private void OnLobbyCreatedCallback(LobbyCreated_t data)
{
if (data.m_eResult != EResult.k_EResultOK)
{
CurrentState = LobbyState.None;
LogSystem.LogError($"Failed to create lobby: {data.m_eResult}");
return;
}
CurrentLobby = new CSteamID(data.m_ulSteamIDLobby);
CachedOwner = SteamUser.GetSteamID();
LogSystem.LogInfo($"Lobby created successfully: {CurrentLobby}");
// 设置房间基础数据
SteamMatchmaking.SetLobbyData(CurrentLobby, "Game", "TOHOTOPIA");
SteamMatchmaking.SetLobbyData(CurrentLobby, "Owner", SelfName);
SteamMatchmaking.SetLobbyData(CurrentLobby, "Version", ConfigManager.Instance.VersionCfg.CurVersionInfo.Version);
//SteamMatchmaking.SetLobbyData(CurrentLobby, "RoomName", FilterRoomName(SelfName + MultilingualManager.Instance.GetMultilingualText(Table.Instance.TextDataAssets.OutsideMultiplayRoomNameSuffix)));
SteamMatchmaking.SetLobbyData(CurrentLobby, "RoomName", SelfName + MultilingualManager.Instance.GetMultilingualText(Table.Instance.TextDataAssets.OutsideMultiplayRoomNameSuffix));
SteamMatchmaking.SetLobbyData(CurrentLobby, "RoomCode", GenerateRoomCode());
SteamMatchmaking.SetLobbyData(CurrentLobby, "GameState", "0");
// 设置Rich Presence
SteamFriends.SetRichPresence("status", "In Lobby");
OnLobbyCreatedEvent?.Invoke(CurrentLobby);
}
private void OnSteamServersConnectedCallback(SteamServersConnected_t data)
{
_steamServerConnected = true;
_steamSessionFailCount = 0;
LogSystem.LogInfo("Steam servers connected");
}
private void OnSteamServersDisconnectedCallback(SteamServersDisconnected_t data)
{
_steamServerConnected = false;
_steamSessionFailCount = SteamSessionFailThreshold;
HandleLocalSteamSessionLost($"Steam servers disconnected: {data.m_eResult}");
}
private void OnSteamServerConnectFailureCallback(SteamServerConnectFailure_t data)
{
_steamServerConnected = false;
LogSystem.LogWarning($"Steam server connect failure: {data.m_eResult}");
}
private void OnSteamShutdownCallback(SteamShutdown_t data)
{
_steamServerConnected = false;
_steamSessionFailCount = SteamSessionFailThreshold;
HandleLocalSteamSessionLost("Steam shutdown callback");
}
// 加入请求回调
private void OnLobbyJoinRequestedCallback(GameLobbyJoinRequested_t data)
{
LogSystem.LogInfo($"Join requested for lobby: {data.m_steamIDLobby}");
// 检查是否被踢或房间被解散
var disbandedData = SteamMatchmaking.GetLobbyData(data.m_steamIDLobby, "disbanded");
if (disbandedData == "true")
{
LogSystem.LogInfo($"Cannot join disbanded lobby");
return;
}
JoinLobby(data.m_steamIDLobby);
}
// 进入房间回调
private void OnLobbyEnterCallback(LobbyEnter_t data)
{
// 检查加入结果
if ((EChatRoomEnterResponse)data.m_EChatRoomEnterResponse != EChatRoomEnterResponse.k_EChatRoomEnterResponseSuccess)
{
CurrentState = LobbyState.None;
var errorMsg = $"Failed to enter lobby: {(EChatRoomEnterResponse)data.m_EChatRoomEnterResponse}";
LogSystem.LogError(errorMsg);
OnLobbyErrorEvent?.Invoke(errorMsg);
return;
}
CurrentLobby = new CSteamID(data.m_ulSteamIDLobby);
CachedOwner = SteamMatchmaking.GetLobbyOwner(CurrentLobby);
CurrentState = LobbyState.InLobby;
_isHandlingSessionLoss = false;
_steamSessionFailCount = 0;
LogSystem.LogInfo($"Successfully entered lobby: {CurrentLobby}");
// 检查是否被踢
CheckIfKicked();
OnLobbyReadyInternal();
OnLobbyMembersChangedInternal();
if (IsLobbyOwner())
{
GameNetSender.Instance.ClearLobbyDataSyncState();
}
else
{
GameNetSender.Instance.MarkLobbyDataSyncRequired();
GameNetSender.Instance.RequestLobbyData(true);
}
// 触发加入成功事件
OnLobbyEnteredEvent?.Invoke(CurrentLobby);
}
// 成员变化回调
private void OnLobbyChatUpdateCallback(LobbyChatUpdate_t data)
{
if (data.m_ulSteamIDLobby != CurrentLobby.m_SteamID) return;
var changedUser = new CSteamID(data.m_ulSteamIDUserChanged);
var stateChange = (EChatMemberStateChange)data.m_rgfChatMemberStateChange;
LogSystem.LogInfo($"Lobby member update: {changedUser} - {stateChange}");
// 处理成员状态变化
if (stateChange.HasFlag(EChatMemberStateChange.k_EChatMemberStateChangeEntered))
{
OnMemberJoinedEvent?.Invoke(changedUser);
}
else if (stateChange.HasFlag(EChatMemberStateChange.k_EChatMemberStateChangeLeft) ||
stateChange.HasFlag(EChatMemberStateChange.k_EChatMemberStateChangeDisconnected) ||
stateChange.HasFlag(EChatMemberStateChange.k_EChatMemberStateChangeKicked) ||
stateChange.HasFlag(EChatMemberStateChange.k_EChatMemberStateChangeBanned))
{
OnMemberLeftEvent?.Invoke(changedUser);
// 断开P2P连接
SimpleP2P.Instance.DisconnectFromPeer(changedUser);
}
OnLobbyMembersChangedInternal();
CheckOwnerChange();
}
// 检查房主变化
private void CheckOwnerChange()
{
if (!CurrentLobby.IsValid()) return;
var currentOwner = SteamMatchmaking.GetLobbyOwner(CurrentLobby);
if (!CachedOwner.IsValid())
{
CachedOwner = currentOwner;
return;
}
if (currentOwner != CachedOwner)
{
var oldOwner = CachedOwner;
CachedOwner = currentOwner;
LogSystem.LogInfo($"Host changed from {oldOwner} to {currentOwner}");
OnHostChangedEvent?.Invoke(oldOwner, currentOwner);
OnHostChangedInternal(oldOwner, currentOwner);
}
}
// 房主切换时内部处理
private void OnHostChangedInternal(CSteamID oldOwner, CSteamID newOwner)
{
if (oldOwner == SteamUser.GetSteamID())
{
LogSystem.LogWarning($"Local host ownership changed to {newOwner}, waiting local disconnect handling");
return;
}
LogSystem.LogWarning($"Host left lobby(oldHost={oldOwner}, newHost={newOwner}), forcing all members leave");
HandleLocalSteamSessionLost($"Host changed from {oldOwner} to {newOwner}");
}
// 房间准备完毕时内部处理
private void OnLobbyReadyInternal()
{
CheckConnectionStatus();
}
// 检查连接状态
public void CheckConnectionStatus()
{
// 如果不是房主,连接到房主
if (!IsLobbyOwner() && CachedOwner.IsValid() && !SimpleP2P.Instance.IsConnectedTo(CachedOwner))
{
LogSystem.LogInfo($"Reconnecting to host: {CachedOwner}");
SimpleP2P.Instance.ConnectToPeer(CachedOwner);
}
}
// 有成员变化时内部处理
private void OnLobbyMembersChangedInternal()
{
var members = EnumerateMembers().ToList();
LogSystem.LogInfo($"Lobby members changed. Count: {members.Count}");
UpdateMemberInfo();
OnMembersChangedEvent?.Invoke(members);
}
// 检查是否被踢
private void CheckIfKicked()
{
var kickData = SteamMatchmaking.GetLobbyMemberData(CurrentLobby, SteamUser.GetSteamID(), "kicked");
var specificKick = SteamMatchmaking.GetLobbyData(CurrentLobby, $"kick_{SteamUser.GetSteamID().m_SteamID}");
if (kickData == "true" || specificKick == "true")
{
LogSystem.LogInfo("I was kicked from the lobby");
LeaveLobby();
}
}
// 重置房间状态
private void ResetLobbyState()
{
CurrentLobby = CSteamID.Nil;
CachedOwner = CSteamID.Nil;
CurrentState = LobbyState.None;
_isHandlingSessionLoss = false;
_steamSessionFailCount = 0;
_refreshSteamStatus = 0f;
// 清除Rich Presence
SteamFriends.ClearRichPresence();
}
// P2P事件处理
private void OnP2PPeerConnected(CSteamID steamID)
{
if (IsLobbyOwner())
{
Main.Instance.GameLogic.OnConnectToOtherPlayer(steamID.m_SteamID);
if (Main.Instance.GameLogic.GetCurState() == GameState.Menu && Main.Instance.MapConfig != null)
{
if (Main.Instance.MapConfig.UpdateLobbyMember(GetAllMemberInfo()))
Main.Instance.MapConfig.CheckMapConfigChanged();
GameNetSender.Instance.SendLobbyData(Main.Instance.MapConfig, steamID.m_SteamID);
}
}
else if (steamID.m_SteamID == GetLobbyOwnerId())
GameNetSender.Instance.RequestLobbyData(true);
LogSystem.LogInfo($"P2P connection established with: {steamID}");
}
private void OnP2PPeerDisconnected(CSteamID steamID)
{
if (!IsLobbyOwner())
{
if (steamID.m_SteamID == GetLobbyOwnerId()) Main.Instance.GameLogic.OnDisconnectToHost();
}
LogSystem.LogInfo($"P2P connection lost with: {steamID}");
}
private void OnP2PConnectionError(string error)
{
LogSystem.LogError($"P2P connection error: {error}");
}
private void OnP2PMessageSendFailed(CSteamID steamID, string reason)
{
var error = $"P2P message send failed: target={steamID}, reason={reason}";
LogSystem.LogError(error);
if (_suppressP2PSendFailureLobbyErrors <= 0) OnLobbyErrorEvent?.Invoke(error);
}
// 发送P2P消息
public bool SendMessageToPeer(ulong member, byte[] data, bool reliable = true)
{
if (!IsInLobby())
{
return ReportP2PSendPrecheckFailed(member, "Not in lobby");
}
if (data == null || data.Length == 0)
return ReportP2PSendPrecheckFailed(member, "Trying to send null or empty data");
if (!IsMemberInLobby(member))
return ReportP2PSendPrecheckFailed(member, $"Target member is not in lobby: {member}");
if (member == GetSelfMemberId())
return ReportP2PSendPrecheckFailed(member, $"Trying to send P2P message to self: {member}");
var cSteamId = new CSteamID(member);
return SimpleP2P.Instance.SendTo(cSteamId, data, reliable);
}
// 广播P2P消息
public bool BroadcastMessage(byte[] data, bool reliable = true)
{
if (!IsInLobby())
{
OnLobbyErrorEvent?.Invoke("P2P broadcast failed: Not in lobby");
LogSystem.LogError("P2P broadcast failed: Not in lobby");
return false;
}
if (data == null || data.Length == 0)
{
OnLobbyErrorEvent?.Invoke("P2P broadcast failed: Trying to broadcast null or empty data");
LogSystem.LogError("P2P broadcast failed: Trying to broadcast null or empty data");
return false;
}
var selfMemberId = GetSelfMemberId();
var targets = new List<CSteamID>();
foreach (var memberId in GetAllMemberIds())
{
if (memberId == selfMemberId) continue;
targets.Add(new CSteamID(memberId));
}
if (targets.Count == 0) return true;
var successCount = 0;
var failedCount = 0;
_suppressP2PSendFailureLobbyErrors++;
try
{
foreach (var target in targets)
{
if (SimpleP2P.Instance.SendTo(target, data, reliable))
{
successCount++;
continue;
}
failedCount++;
LogSystem.LogWarning($"P2P broadcast enqueue failed: target={target}, bytes={data.Length}");
}
}
finally
{
_suppressP2PSendFailureLobbyErrors--;
}
if (successCount > 0)
{
if (failedCount > 0)
LogSystem.LogWarning($"P2P broadcast partially succeeded: success={successCount}, failed={failedCount}, bytes={data.Length}");
return true;
}
var error = $"P2P broadcast failed for all targets: targets={targets.Count}, bytes={data.Length}";
LogSystem.LogError(error);
OnLobbyErrorEvent?.Invoke(error);
return false;
}
private bool ReportP2PSendPrecheckFailed(ulong member, string reason)
{
OnP2PMessageSendFailed(new CSteamID(member), reason);
return false;
}
// 枚举房间成员
private IEnumerable<CSteamID> EnumerateMembers()
{
if (!CurrentLobby.IsValid()) yield break;
int count = SteamMatchmaking.GetNumLobbyMembers(CurrentLobby);
for (int i = 0; i < count; i++)
yield return SteamMatchmaking.GetLobbyMemberByIndex(CurrentLobby, i);
}
// 获取房间所有成员
public List<ulong> GetAllMemberIds()
{
return _memberInfos.Keys.ToList();
}
// 刷新房间内的成员信息
private void UpdateMemberInfo()
{
if (!CurrentLobby.IsValid()) return;
_memberInfos.Clear();
int count = SteamMatchmaking.GetNumLobbyMembers(CurrentLobby);
for (int i = 0; i < count; i++)
{
var cSteamId = SteamMatchmaking.GetLobbyMemberByIndex(CurrentLobby, i);
if (!_membersCache.ContainsKey(cSteamId.m_SteamID)) _membersCache[cSteamId.m_SteamID] = cSteamId;
_memberInfos[cSteamId.m_SteamID] = new MemberInfo();
_memberInfos[cSteamId.m_SteamID].Id = cSteamId.m_SteamID;
_memberInfos[cSteamId.m_SteamID].Name = SteamFriends.GetFriendPersonaName(cSteamId);
_memberInfos[cSteamId.m_SteamID].Texture = GetMemberAvatar(cSteamId.m_SteamID);
}
}
public Dictionary<ulong, MemberInfo> GetAllMemberInfo()
{
return _memberInfos;
}
public MemberInfo GetMemberInfo(ulong steamID)
{
return _memberInfos.GetValueOrDefault(steamID);
}
// 获取头像
public Texture2D GetMemberAvatar(ulong memberId)
{
var cSteamId = GetCSteamID(memberId);
if (!cSteamId.IsValid()) return null;
// 先检查头像是否可用
if (SteamFriends.RequestUserInformation(cSteamId, false))
{
// 如果返回false说明信息已缓存可以直接获取
// 如果返回true说明正在请求需要等待
LogSystem.LogInfo($"Avatar for {memberId} is being downloaded");
return null;
}
int avatarInt = SteamFriends.GetLargeFriendAvatar(cSteamId);
if (avatarInt == -1 || avatarInt == 0) // 0也表示无效
{
LogSystem.LogWarning($"Invalid avatar handle for {memberId}: {avatarInt}");
return null;
}
bool success = SteamUtils.GetImageSize(avatarInt, out uint width, out uint height);
if (!success || width == 0 || height == 0)
{
LogSystem.LogWarning($"Invalid avatar size for {memberId}: {width}x{height}");
return null;
}
byte[] imageData = new byte[width * height * 4];
success = SteamUtils.GetImageRGBA(avatarInt, imageData, imageData.Length);
if (!success)
{
LogSystem.LogWarning($"Failed to get avatar data for {memberId}");
return null;
}
Texture2D avatar = new Texture2D((int)width, (int)height, TextureFormat.RGBA32, false);
avatar.LoadRawTextureData(imageData);
avatar.Apply();
return avatar;
}
// 获取成员数量
public int GetMemberCount()
{
if (!CurrentLobby.IsValid()) return 0;
return SteamMatchmaking.GetNumLobbyMembers(CurrentLobby);
}
// 获取最大成员数
public int GetMemberLimit()
{
if (!CurrentLobby.IsValid()) return 0;
return SteamMatchmaking.GetLobbyMemberLimit(CurrentLobby);
}
// 判断成员是否在房间中
public bool IsMemberInLobby(ulong memberId)
{
if (!CurrentLobby.IsValid()) return false;
int count = SteamMatchmaking.GetNumLobbyMembers(CurrentLobby);
for (int i = 0; i < count; i++)
{
var cSteamId = SteamMatchmaking.GetLobbyMemberByIndex(CurrentLobby, i);
if (cSteamId.m_SteamID == memberId) return true;
}
return false;
}
// 根据 memberId 获取 CSteamId
public CSteamID GetCSteamID(ulong memberId)
{
if (!_membersCache.ContainsKey(memberId)) return CSteamID.Nil;
return _membersCache[memberId];
}
// 获取自己的 memberId
public ulong GetSelfMemberId()
{
return SteamUser.GetSteamID().m_SteamID;
}
// 获取房主的 memberId
public ulong GetLobbyOwnerId()
{
if (!CurrentLobby.IsValid()) return 0;
var owner = SteamMatchmaking.GetLobbyOwner(CurrentLobby);
return owner.m_SteamID;
}
// 获取当前房间状态
public LobbyState GetCurState()
{
return CurrentState;
}
// 自己是否是房主
public bool IsLobbyOwner()
{
return IsInLobby() && SteamMatchmaking.GetLobbyOwner(CurrentLobby) == SteamUser.GetSteamID();
}
// 自己是否在房间中
public bool IsInLobby()
{
return IsInitialized() && CurrentState == LobbyState.InLobby && CurrentLobby.IsValid();
}
// 获取自己的当前Steam在线状态 (如: 在线, 忙碌, 离开, 隐身, 离线)
public EPersonaState GetSelfPersonaState()
{
if (!_isSteamInitialized || !_isLoggedIn) return EPersonaState.k_EPersonaStateOffline;
return SteamFriends.GetPersonaState();
}
// 检查自己当前的Steam在线状态是否为“隐身”或“离线”
// 重要提醒Steam客户端API【无法直接获取】玩家个人资料面板上设置的“资料页隐私公开/仅好友/私密)”或“游戏详情隐私”。
// 资料页的隐私设置是受到Steam严格保护的如果玩家资料设置了私密导致别人看不见加入按钮代码层面是查不到的。
// 只能通过在UI上写一行提示“如果您邀请的好友没有出现加入按钮请将您的Steam资料或游戏详情设为公开”。
public bool IsSelfStatusInvisibleOrOffline()
{
var state = GetSelfPersonaState();
return state == EPersonaState.k_EPersonaStateOffline ||
state == EPersonaState.k_EPersonaStateInvisible;
}
// 清理资源
public void Cleanup()
{
LeaveLobby();
SimpleP2P.Instance.OnPeerConnectedEvent -= OnP2PPeerConnected;
SimpleP2P.Instance.OnPeerDisconnectedEvent -= OnP2PPeerDisconnected;
SimpleP2P.Instance.OnConnectionErrorEvent -= OnP2PConnectionError;
SimpleP2P.Instance.OnMessageSendFailedEvent -= OnP2PMessageSendFailed;
SimpleP2P.Instance.Cleanup();
_cbLobbyCreated?.Dispose();
_cbLobbyJoinRequested?.Dispose();
_cbLobbyEnter?.Dispose();
_cbLobbyChatUpdate?.Dispose();
_cbSteamServersConnected?.Dispose();
_cbSteamServersDisconnected?.Dispose();
_cbSteamServerConnectFailure?.Dispose();
_cbSteamShutdown?.Dispose();
LogSystem.LogInfo("SteamLobbyManager cleaned up");
}
}
// 房间信息结构
[MemoryPackable]
public partial class LobbyListInfo
{
public ulong LobbyId;
public ulong OwnerId;
public string OwnerName;
public string RoomName;
public string Version;
public int CurrentPlayers;
public int MaxPlayers;
public int GameState;
}
}