From c8e9195fd7f5d97944ca27994caed480ee7cf8f1 Mon Sep 17 00:00:00 2001 From: wuwenbo Date: Tue, 26 May 2026 23:59:19 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=BE=E6=8A=A5=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Unity/Assets/Scripts/TH1_Logic/Net/ILobby.cs | 9 + .../TH1_Logic/Steam/GameNetReceiver.cs | 15 ++ .../TH1_Logic/Steam/SteamLobbyManager.cs | 170 +++++++++++++++--- .../TH1_Logic/Steam/SteamObjectSerializer.cs | 13 ++ 4 files changed, 183 insertions(+), 24 deletions(-) diff --git a/Unity/Assets/Scripts/TH1_Logic/Net/ILobby.cs b/Unity/Assets/Scripts/TH1_Logic/Net/ILobby.cs index 6137cbb21..954b4c0a0 100644 --- a/Unity/Assets/Scripts/TH1_Logic/Net/ILobby.cs +++ b/Unity/Assets/Scripts/TH1_Logic/Net/ILobby.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; +using TH1_Logic.Steam; using UnityEngine; namespace TH1_Logic.Net @@ -39,6 +40,9 @@ namespace TH1_Logic.Net // 邀请好友 public void InviteFriend(ulong memberId); + + // 举报房间 + public bool ReportLobby(LobbyListInfo lobbyInfo); // 踢出成员 public void KickMember(ulong memberId); @@ -177,6 +181,11 @@ namespace TH1_Logic.Net } + public bool ReportLobby(LobbyListInfo lobbyInfo) + { + return false; + } + public void KickMember(ulong memberId) { diff --git a/Unity/Assets/Scripts/TH1_Logic/Steam/GameNetReceiver.cs b/Unity/Assets/Scripts/TH1_Logic/Steam/GameNetReceiver.cs index 383a2e92a..9244fb3f5 100644 --- a/Unity/Assets/Scripts/TH1_Logic/Steam/GameNetReceiver.cs +++ b/Unity/Assets/Scripts/TH1_Logic/Steam/GameNetReceiver.cs @@ -40,6 +40,7 @@ namespace TH1_Logic.Steam if (message.MessageType == P2PMsgType.HeartbeatReply) OnReceivedHeartbeatReply((HeartbeatReplyMessage)message); if (message.MessageType == P2PMsgType.ChatMessage) OnReceivedChatMessage((ChatMessage)message); if (message.MessageType == P2PMsgType.InviteMessage) OnReceivedInviteMessage((InviteMessage)message); + if (message.MessageType == P2PMsgType.LobbyReport) OnReceivedLobbyReport((LobbyReportMessage)message); } catch (System.Exception e) { @@ -593,5 +594,19 @@ namespace TH1_Logic.Steam } } + + // 房间外举报消息,只有被举报房间的房主处理 + private void OnReceivedLobbyReport(LobbyReportMessage message) + { + if (message == null) + { + LogSystem.LogError($"消息解析失败: OnReceivedLobbyReport"); + NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.NetworkMessageParseFailed); + return; + } + + if (LobbyManager.Instance.Lobby is SteamLobbyManager steamLobby) + steamLobby.OnReceivedLobbyReport(message); + } } } diff --git a/Unity/Assets/Scripts/TH1_Logic/Steam/SteamLobbyManager.cs b/Unity/Assets/Scripts/TH1_Logic/Steam/SteamLobbyManager.cs index 9b667a1b2..0bcb3f09b 100644 --- a/Unity/Assets/Scripts/TH1_Logic/Steam/SteamLobbyManager.cs +++ b/Unity/Assets/Scripts/TH1_Logic/Steam/SteamLobbyManager.cs @@ -86,6 +86,12 @@ namespace TH1_Logic.Steam private const string LobbyHasPasswordKey = "HasPassword"; private const string LobbyPasswordKey = "Password"; private const string LobbyIsPublicKey = "IsPublic"; + private const string LobbyOwnerSteamIdKey = "OwnerSteamId"; + private const string LobbyReportCountKey = "ReportCount"; + private const string LobbyRoomNameKey = "RoomName"; + private const string LobbyGameStateKey = "GameState"; + private const int LobbyReportRenameThreshold = 5; + private const string ReportedLobbyDefaultRoomName = "Default"; private string _pendingLobbyPassword = ""; private bool _pendingLobbyIsPublic = true; private CSteamID _pendingJoinLobby = CSteamID.Nil; @@ -96,6 +102,7 @@ namespace TH1_Logic.Steam // 房间列表 private List _lobbyListInfos; public List LobbyListInfos => _lobbyListInfos; + private readonly HashSet _lobbyReporters = new HashSet(); // 事件委托 public event System.Action OnLobbyCreatedEvent; // 房间创建成功 @@ -988,11 +995,13 @@ namespace TH1_Logic.Steam OwnerId = _selfID.m_SteamID, OwnerName = SteamMatchmaking.GetLobbyData(CurrentLobby, "Owner"), - RoomName = SteamMatchmaking.GetLobbyData(CurrentLobby, "RoomName"), + RoomName = SteamMatchmaking.GetLobbyData(CurrentLobby, LobbyRoomNameKey), Version = SteamMatchmaking.GetLobbyData(CurrentLobby, "Version"), CurrentPlayers = SteamMatchmaking.GetNumLobbyMembers(CurrentLobby), MaxPlayers = SteamMatchmaking.GetLobbyMemberLimit(CurrentLobby), + GameState = GetLobbyGameStateFromData(CurrentLobby), HasPassword = LobbyHasPassword(CurrentLobby), + ReportCount = GetLobbyReportCountFromData(CurrentLobby), }; var data = new InviteMessage(); data.LobbyInfo = lobbyInfo; @@ -1017,6 +1026,98 @@ namespace TH1_Logic.Steam LogSystem.LogInfo($"Game invite sent to: {targetSteamId}"); return true; } + + public bool ReportLobby(LobbyListInfo lobbyInfo) + { + if (lobbyInfo == null || lobbyInfo.LobbyId == 0) + { + LogSystem.LogError("Report lobby failed: invalid lobby info"); + NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyOperationFailed); + return false; + } + + if (lobbyInfo.OwnerId == 0) + { + LogSystem.LogError($"Report lobby failed: missing owner Steam ID, lobby={lobbyInfo.LobbyId}"); + NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyOperationFailed); + return false; + } + + var selfId = GetSelfMemberId(); + if (selfId == 0) + { + LogSystem.LogError("Report lobby failed: missing self Steam ID"); + NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyOperationFailed); + return false; + } + + if (lobbyInfo.OwnerId == selfId) + { + LogSystem.LogInfo($"Report lobby skipped: cannot report own lobby, lobby={lobbyInfo.LobbyId}"); + NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyOperationFailed); + return false; + } + + var targetId = new CSteamID(lobbyInfo.OwnerId); + if (!targetId.IsValid()) + { + LogSystem.LogError($"Report lobby failed: invalid owner Steam ID, owner={lobbyInfo.OwnerId}, lobby={lobbyInfo.LobbyId}"); + NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyOperationFailed); + return false; + } + + var data = new LobbyReportMessage + { + LobbyId = lobbyInfo.LobbyId, + ReporterId = selfId, + Version = ConfigManager.Instance.VersionCfg.CurVersionInfo.Version, + }; + var bytes = MemoryPackSerializer.Serialize(data); + if (!SimpleP2P.Instance.SendToWithOutConnect(targetId, bytes)) + { + LogSystem.LogError($"Report lobby failed: send to owner failed, lobby={lobbyInfo.LobbyId}, owner={lobbyInfo.OwnerId}"); + NetworkPlayerTipManager.Instance.Request(NetworkPlayerTipType.LobbyOperationFailed); + return false; + } + + LogSystem.LogInfo($"Report lobby sent: lobby={lobbyInfo.LobbyId}, owner={lobbyInfo.OwnerId}, reporter={selfId}"); + return true; + } + + public void OnReceivedLobbyReport(LobbyReportMessage message) + { + if (message == null) return; + if (!IsLobbyOwner()) return; + if (!CurrentLobby.IsValid() || message.LobbyId != CurrentLobby.m_SteamID) + { + LogSystem.LogWarning($"Ignore lobby report for unmatched lobby: current={CurrentLobby.m_SteamID}, report={message.LobbyId}"); + return; + } + + if (message.ReporterId == 0 || message.ReporterId == GetSelfMemberId()) + { + LogSystem.LogWarning($"Ignore invalid lobby report: lobby={message.LobbyId}, reporter={message.ReporterId}"); + return; + } + + if (_lobbyReporters.Contains(message.ReporterId)) + { + LogSystem.LogInfo($"Ignore duplicate lobby report: lobby={message.LobbyId}, reporter={message.ReporterId}"); + return; + } + + _lobbyReporters.Add(message.ReporterId); + var reportCount = GetLobbyReportCountFromData(CurrentLobby) + 1; + SteamMatchmaking.SetLobbyData(CurrentLobby, LobbyReportCountKey, reportCount.ToString()); + LogSystem.LogInfo($"Lobby report received: lobby={message.LobbyId}, reporter={message.ReporterId}, count={reportCount}"); + + if (reportCount <= LobbyReportRenameThreshold) return; + + SteamMatchmaking.SetLobbyData(CurrentLobby, LobbyRoomNameKey, ReportedLobbyDefaultRoomName); + RoomName = ReportedLobbyDefaultRoomName; + EventManager.Publish(new UpdateUIOutsideMultiplayLobbyList()); + LogSystem.LogInfo($"Lobby report count exceeded threshold, room name reset: lobby={message.LobbyId}, count={reportCount}"); + } // 搜索房间 public void SearchPublicLobbies(ELobbyDistanceFilter filter = ELobbyDistanceFilter.k_ELobbyDistanceFilterWorldwide, @@ -1052,20 +1153,9 @@ namespace TH1_Logic.Steam 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")), - HasPassword = LobbyHasPassword(lobbyId), - }); + var lobbyInfo = new LobbyListInfo { LobbyId = lobbyId.m_SteamID }; + RefreshLobbyListInfo(lobbyInfo, lobbyId); + _lobbyListInfos.Add(lobbyInfo); } // 触发UI刷新事件 @@ -1078,16 +1168,43 @@ namespace TH1_Logic.Steam 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")); - lobbyInfo.HasPassword = LobbyHasPassword(cSteamId); + RefreshLobbyListInfo(lobbyInfo, cSteamId); } } + private void RefreshLobbyListInfo(LobbyListInfo lobbyInfo, CSteamID lobbyId) + { + if (lobbyInfo == null) return; + lobbyInfo.LobbyId = lobbyId.m_SteamID; + lobbyInfo.OwnerId = GetLobbyOwnerSteamIdFromData(lobbyId); + lobbyInfo.OwnerName = SteamMatchmaking.GetLobbyData(lobbyId, "Owner"); + lobbyInfo.RoomName = SteamMatchmaking.GetLobbyData(lobbyId, LobbyRoomNameKey); + lobbyInfo.Version = SteamMatchmaking.GetLobbyData(lobbyId, "Version"); + lobbyInfo.CurrentPlayers = SteamMatchmaking.GetNumLobbyMembers(lobbyId); + lobbyInfo.MaxPlayers = SteamMatchmaking.GetLobbyMemberLimit(lobbyId); + lobbyInfo.GameState = GetLobbyGameStateFromData(lobbyId); + lobbyInfo.HasPassword = LobbyHasPassword(lobbyId); + lobbyInfo.ReportCount = GetLobbyReportCountFromData(lobbyId); + } + + private static ulong GetLobbyOwnerSteamIdFromData(CSteamID lobbyId) + { + var ownerIdData = SteamMatchmaking.GetLobbyData(lobbyId, LobbyOwnerSteamIdKey); + return ulong.TryParse(ownerIdData, out var ownerId) ? ownerId : 0; + } + + private static int GetLobbyReportCountFromData(CSteamID lobbyId) + { + var reportCountData = SteamMatchmaking.GetLobbyData(lobbyId, LobbyReportCountKey); + return int.TryParse(reportCountData, out var reportCount) && reportCount > 0 ? reportCount : 0; + } + + private static int GetLobbyGameStateFromData(CSteamID lobbyId) + { + var gameStateData = SteamMatchmaking.GetLobbyData(lobbyId, LobbyGameStateKey); + return int.TryParse(gameStateData, out var gameState) ? gameState : 0; + } + // 过滤房间名称,只保留中文、英文字母、数字和常用符号 public static string FilterRoomName(string input, int maxLength = 20) { @@ -1221,6 +1338,7 @@ namespace TH1_Logic.Steam } CurrentLobby = new CSteamID(data.m_ulSteamIDLobby); + _lobbyReporters.Clear(); if (!TrySteamApi("OnLobbyCreatedCallback.GetSteamID", SteamUser.GetSteamID, out CachedOwner)) { ResetLobbyState(); @@ -1232,11 +1350,13 @@ namespace TH1_Logic.Steam // 设置房间基础数据 SteamMatchmaking.SetLobbyData(CurrentLobby, "Game", "TOHOTOPIA"); SteamMatchmaking.SetLobbyData(CurrentLobby, "Owner", SelfName); + SteamMatchmaking.SetLobbyData(CurrentLobby, LobbyOwnerSteamIdKey, _selfID.m_SteamID.ToString()); 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, LobbyRoomNameKey, SelfName + MultilingualManager.Instance.GetMultilingualText(Table.Instance.TextDataAssets.OutsideMultiplayRoomNameSuffix)); SteamMatchmaking.SetLobbyData(CurrentLobby, "RoomCode", GenerateRoomCode()); - SteamMatchmaking.SetLobbyData(CurrentLobby, "GameState", "0"); + SteamMatchmaking.SetLobbyData(CurrentLobby, LobbyGameStateKey, "0"); + SteamMatchmaking.SetLobbyData(CurrentLobby, LobbyReportCountKey, "0"); SteamMatchmaking.SetLobbyData(CurrentLobby, LobbyHasPasswordKey, string.IsNullOrEmpty(_pendingLobbyPassword) ? "false" : "true"); SteamMatchmaking.SetLobbyData(CurrentLobby, LobbyPasswordKey, _pendingLobbyPassword); SteamMatchmaking.SetLobbyData(CurrentLobby, LobbyIsPublicKey, _pendingLobbyIsPublic ? "true" : "false"); @@ -1474,6 +1594,7 @@ namespace TH1_Logic.Steam _pendingLobbyPassword = ""; _pendingLobbyIsPublic = true; ClearPendingJoinLobby(); + _lobbyReporters.Clear(); // 清除Rich Presence SteamFriends.ClearRichPresence(); @@ -1886,5 +2007,6 @@ namespace TH1_Logic.Steam public int MaxPlayers; public int GameState; public bool HasPassword; + public int ReportCount; } } diff --git a/Unity/Assets/Scripts/TH1_Logic/Steam/SteamObjectSerializer.cs b/Unity/Assets/Scripts/TH1_Logic/Steam/SteamObjectSerializer.cs index 65b2dc0b7..a0a52fee0 100644 --- a/Unity/Assets/Scripts/TH1_Logic/Steam/SteamObjectSerializer.cs +++ b/Unity/Assets/Scripts/TH1_Logic/Steam/SteamObjectSerializer.cs @@ -59,6 +59,8 @@ namespace TH1_Logic.Steam InviteMessage = 16, // 网络压测工具消息 NetworkStress = 17, + // 房间举报 + LobbyReport = 18, } public enum NetworkStressMessageKind : byte @@ -89,6 +91,7 @@ namespace TH1_Logic.Steam [MemoryPackUnion(15, typeof(ChatMessage))] [MemoryPackUnion(16, typeof(InviteMessage))] [MemoryPackUnion(17, typeof(NetworkStressMessage))] + [MemoryPackUnion(18, typeof(LobbyReportMessage))] public abstract partial class BaseMessage { public abstract P2PMsgType MessageType { get; } @@ -231,6 +234,16 @@ namespace TH1_Logic.Steam } + [MemoryPackable] + public partial class LobbyReportMessage : BaseMessage + { + public override P2PMsgType MessageType => P2PMsgType.LobbyReport; + public ulong LobbyId { get; set; } + public ulong ReporterId { get; set; } + public string Version { get; set; } + } + + [MemoryPackable] public partial class NetworkStressMessage : BaseMessage {