TH1/Unity/Assets/Scripts/TH1_Logic/Steam/SteamLobbyManager.cs
2026-01-19 20:03:32 +08:00

935 lines
35 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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Logic;
using Logic.CrashSight;
using Steamworks;
using TH1_Core.Managers;
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;
// 事件委托
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;
public SteamLobbyManager()
{
_steamSDKUpdateRecord = 2;
_onlineFriendsIdUpdateRecord = 2;
_kickInfoUpdateRecord = 2;
_refreshSteamStatus = 0;
_onlineFriendsId = new Dictionary<ulong, CSteamID>();
_onlineFriendsInfo = new Dictionary<ulong, MemberInfo>();
_memberInfos = new Dictionary<ulong, MemberInfo>();
_membersCache = new Dictionary<ulong, CSteamID>();
_status = new SteamNetworkStatus();
}
// 初始化
public void Init()
{
_steamSDKUpdateRecord += Time.deltaTime;
if (_steamSDKUpdateRecord < 2) return;
_steamSDKUpdateRecord = 0;
RefreshSteamInit();
RefreshSteamStatus();
//RefreshSteamGUI();
RefreshLoginStatus();
RefreshLobbyStatus();
RefreshSteamGUI();
}
// 刷新 Steam
private void RefreshSteamInit()
{
if (_isSteamInitialized) return;
// 检查Steam是否运行
if (!SteamAPI.IsSteamRunning())
{
// LogSystem.LogError("Steam客户端未运行请先启动Steam。");
return;
}
// 初始化Steam API
LogSystem.LogInfo("开始初始化Steam...");
_isSteamInitialized = SteamAPI.Init();
if (!_isSteamInitialized)
{
LogSystem.LogError("Steam API初始化失败");
LogSystem.LogError("可能的原因:");
LogSystem.LogError("1. Steam客户端未运行");
LogSystem.LogError("2. steam_appid.txt文件配置错误开发环境");
LogSystem.LogError("3. 没有以Steam方式启动应用开发环境");
LogSystem.LogError("4. Steam App ID不匹配");
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();
}
// 初始化 Steam UI 面板
private void RefreshSteamGUI()
{
if (_guiObj) return;
// 构建 GUI 输入 Mono
_guiObj = new GameObject();
_guiObj.name = "SteamGUI";
_guiObj.AddComponent<SteamGUIMono>();
_guiObj.SetActive(true);
}
// 刷新用户登录状态
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);
// 初始化P2P
SimpleP2P.Instance.Initialize();
// 订阅P2P事件
SimpleP2P.Instance.OnPeerConnectedEvent += OnP2PPeerConnected;
SimpleP2P.Instance.OnPeerDisconnectedEvent += OnP2PPeerDisconnected;
SimpleP2P.Instance.OnConnectionErrorEvent += OnP2PConnectionError;
LogSystem.LogInfo("SteamLobbyManager initialized");
_isLobbyInitialized = true;
}
// 定时更新在线好友
private void RefreshOnlineFriends()
{
_onlineFriendsIdUpdateRecord += Time.deltaTime;
if (_onlineFriendsIdUpdateRecord > 2)
{
_onlineFriendsIdUpdateRecord = 0;
var friends = GetOnlineFriends();
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();
}
// 简单联机健康判定:需 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 CreateFriendsLobby(int maxMembers = 4)
{
if (CurrentState != LobbyState.None)
{
LogSystem.LogInfo($"Cannot create lobby in state: {CurrentState}");
return;
}
CurrentState = LobbyState.Creating;
LogSystem.LogInfo($"Creating friends lobby with max members: {maxMembers}");
SteamMatchmaking.CreateLobby(ELobbyType.k_ELobbyTypeFriendsOnly, maxMembers);
}
// 加入房间
public void JoinLobby(CSteamID lobbyId)
{
if (CurrentState != LobbyState.None)
{
//LogSystem.LogInfo($"Cannot join lobby in state: {CurrentState}");
//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);
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())
{
OnLobbyErrorEvent?.Invoke("Only lobby owner can kick members");
return;
}
if (memberId == SteamUser.GetSteamID().m_SteamID)
{
OnLobbyErrorEvent?.Invoke("Cannot kick yourself");
return;
}
LogSystem.LogInfo($"Kicking member: {memberId}");
// 通过设置成员数据标记被踢,客户端检测到后自动离开
SteamMatchmaking.SetLobbyMemberData(CurrentLobby, $"kick_{memberId}", "true");
}
public bool IsInitialized()
{
return _isSteamInitialized && _isLobbyInitialized && _isLoggedIn && SimpleP2P.Instance.IsInitialized;
}
public void InviteFriend(ulong memberId)
{
if (!CurrentLobby.IsValid())
{
OnLobbyErrorEvent?.Invoke("Not in a lobby");
return;
}
if (!_onlineFriendsId.ContainsKey(memberId)) return;
LogSystem.LogInfo($"Inviting friend: {memberId}");
SteamMatchmaking.InviteUserToLobby(CurrentLobby, _onlineFriendsId[memberId]);
}
// 打开好友邀请对话框
public void OpenInviteOverlay()
{
if (!CurrentLobby.IsValid())
{
OnLobbyErrorEvent?.Invoke("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 Dictionary<ulong, MemberInfo> GetOnlineFriendsDict()
{
return _onlineFriendsInfo;
}
// 设置房间数据
public void SetLobbyData(string key, string value)
{
if (!IsLobbyOwner())
{
OnLobbyErrorEvent?.Invoke("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;
OnLobbyErrorEvent?.Invoke($"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, "owner", SteamUser.GetSteamID().m_SteamID.ToString());
SteamMatchmaking.SetLobbyData(CurrentLobby, "version", "1.0");
SteamMatchmaking.SetLobbyData(CurrentLobby, "game_mode", "default");
// 设置Rich Presence
SteamFriends.SetRichPresence("connect", "+connect_lobby " + CurrentLobby.m_SteamID);
SteamFriends.SetRichPresence("status", "In Lobby");
OnLobbyCreatedEvent?.Invoke(CurrentLobby);
}
// 加入请求回调
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;
LogSystem.LogError($"Failed to enter lobby: {(EChatRoomEnterResponse)data.m_EChatRoomEnterResponse}");
return;
}
CurrentLobby = new CSteamID(data.m_ulSteamIDLobby);
CachedOwner = SteamMatchmaking.GetLobbyOwner(CurrentLobby);
CurrentState = LobbyState.InLobby;
LogSystem.LogInfo($"Successfully entered lobby: {CurrentLobby}");
// 检查是否被踢
CheckIfKicked();
OnLobbyReadyInternal();
OnLobbyMembersChangedInternal();
}
// 成员变化回调
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();
}
}
// 房主切换时内部处理
private void OnHostChangedInternal()
{
if (IsLobbyOwner())
{
LogSystem.LogInfo("I became the new host");
}
else
{
LogSystem.LogInfo($"New host is: {CachedOwner}");
// 普通成员断开旧连接,连接新房主
SimpleP2P.Instance.DisconnectAll();
SimpleP2P.Instance.ConnectToPeer(CachedOwner);
}
}
// 房间准备完毕时内部处理
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;
// 清除Rich Presence
SteamFriends.ClearRichPresence();
}
// P2P事件处理
private void OnP2PPeerConnected(CSteamID steamID)
{
if (IsLobbyOwner()) Main.Instance.GameLogic.OnConnectToOtherPlayer(steamID.m_SteamID);
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}");
}
// 发送P2P消息
public bool SendMessageToPeer(ulong member, byte[] data, bool reliable = true)
{
if (!IsInLobby())
{
OnLobbyErrorEvent?.Invoke("Not in lobby");
return false;
}
if (!IsMemberInLobby(member)) return false;
if (member == GetSelfMemberId()) return false;
var cSteamId = new CSteamID(member);
return SimpleP2P.Instance.SendTo(cSteamId, data, reliable);
}
// 广播P2P消息
public void BroadcastMessage(byte[] data, bool reliable = true)
{
if (!IsInLobby())
{
OnLobbyErrorEvent?.Invoke("Not in lobby");
return;
}
SimpleP2P.Instance.Broadcast(data, reliable);
}
// 枚举房间成员
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();
}
// 清理资源
public void Cleanup()
{
LeaveLobby();
SimpleP2P.Instance.Cleanup();
_cbLobbyCreated?.Dispose();
_cbLobbyJoinRequested?.Dispose();
_cbLobbyEnter?.Dispose();
_cbLobbyChatUpdate?.Dispose();
LogSystem.LogInfo("SteamLobbyManager cleaned up");
}
}
}