935 lines
35 KiB
C#
935 lines
35 KiB
C#
|
||
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 = 目标 Lobby;data.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");
|
||
}
|
||
}
|
||
}
|