601 lines
23 KiB
C#
601 lines
23 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
using ChessGameDomain;
|
|
using ChessGameService.Param;
|
|
using Microsoft.AspNetCore.SignalR;
|
|
using Microsoft.Extensions.Logging;
|
|
using RedisService;
|
|
using XAct;
|
|
|
|
namespace ChessGameService
|
|
{
|
|
public class ChessGameHub : Hub
|
|
{
|
|
public const string LobbyGroup = "chess_lobby";
|
|
private readonly ILogger<ChessGameHub> _logger;
|
|
private readonly IChessGameService _chessService;
|
|
private readonly IRedisService _redisService;
|
|
|
|
public ChessGameHub(ILogger<ChessGameHub> logger, IChessGameService chessService, IRedisService redisService)
|
|
{
|
|
_logger = logger;
|
|
_chessService = chessService;
|
|
_redisService = redisService;
|
|
}
|
|
|
|
private async Task JoinLobbyAsync(string connectionId)
|
|
{
|
|
var cid = string.IsNullOrWhiteSpace(connectionId) ? Context.ConnectionId : connectionId;
|
|
await Groups.AddToGroupAsync(cid, LobbyGroup);
|
|
}
|
|
|
|
private async Task LeaveLobbyAsync(string connectionId)
|
|
{
|
|
var cid = string.IsNullOrWhiteSpace(connectionId) ? Context.ConnectionId : connectionId;
|
|
await Groups.RemoveFromGroupAsync(cid, LobbyGroup);
|
|
}
|
|
|
|
private async Task JoinLobbyBatchAsync(IEnumerable<string> connectionIds)
|
|
{
|
|
foreach (var connectionId in connectionIds.Distinct())
|
|
{
|
|
try
|
|
{
|
|
await Groups.AddToGroupAsync(connectionId, LobbyGroup);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "[ChessLobby] 加入大厅组失败 cid={Cid}", connectionId);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public override async Task OnConnectedAsync()
|
|
{
|
|
var cid = Context.ConnectionId;
|
|
var reconnectUserId = Context.GetHttpContext()?.Request?.Query["userId"].ToString();
|
|
|
|
var room = !string.IsNullOrWhiteSpace(reconnectUserId)
|
|
? _redisService.GetAllChessRooms()?.FirstOrDefault(r => r.users.Any(u => u.userId == reconnectUserId && (r.state == 1 || r.state == 2 || r.IsGameEnded)))
|
|
: _chessService.GetUserRoom(cid);
|
|
if (room != null)
|
|
{
|
|
var user = room.users.FirstOrDefault(c => c.Cid == cid || c.userId == reconnectUserId);
|
|
if (user != null)
|
|
{
|
|
if (!user.IsLeave && user.Cid != cid)
|
|
{
|
|
await Clients.Caller.SendAsync("GameError", "用户已在其他连接中在线");
|
|
|
|
await base.OnConnectedAsync();
|
|
return;
|
|
}
|
|
|
|
user.Cid = cid;
|
|
user.DisconnectTime = 0;
|
|
user.IsLeave = false;
|
|
room.robotCount = Math.Clamp(room.robotCount, 0, Math.Max(0, 2 - room.users.Count));
|
|
_redisService.SetChessRoom(room);
|
|
|
|
var otherUser = room.users.FirstOrDefault(c => c.Cid != cid);
|
|
if (otherUser != null)
|
|
{
|
|
await Clients.Group(room.RoomId).SendAsync("Reconnect", new
|
|
{
|
|
userId = user.userId,
|
|
userName = user.userName
|
|
});
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
await Clients.Caller.SendAsync("BeginGame", room);
|
|
|
|
await Groups.AddToGroupAsync(cid, room.RoomId);
|
|
}
|
|
else
|
|
{
|
|
await JoinLobbyAsync(cid);
|
|
}
|
|
}
|
|
|
|
public override async Task OnDisconnectedAsync(Exception? exception)
|
|
{
|
|
var cid = Context.ConnectionId;
|
|
|
|
var room = _chessService.GetUserRoom(cid);
|
|
if (room != null)
|
|
{
|
|
var user = room.users.FirstOrDefault(c => c.Cid == cid);
|
|
|
|
if (user != null)
|
|
{
|
|
if (room.state == 2 && room.users.Count == 2)
|
|
{
|
|
user.DisconnectTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
|
user.IsLeave = true;
|
|
var timeoutSeconds = room.TimeoutSeconds > 0 ? room.TimeoutSeconds : 60;
|
|
user.Cid = string.Empty;
|
|
|
|
if (room.users.All(u => u.IsLeave))
|
|
{
|
|
var affectedUserIds = room.users.Select(u => u.userId).Distinct().ToList();
|
|
_chessService.ExitRoom(room);
|
|
await Task.WhenAll(affectedUserIds.Select(id => _chessService.UpdateUserGameType(id, 0)));
|
|
await Clients.Group(LobbyGroup).SendAsync("ExitRoomNotice", room);
|
|
}
|
|
else
|
|
{
|
|
_redisService.SetChessRoom(room);
|
|
|
|
await Clients.Group(room.RoomId).SendAsync("Disconnect", new
|
|
{
|
|
userId = user.userId,
|
|
disconnectTime = user.DisconnectTime,
|
|
timeoutSeconds = timeoutSeconds
|
|
});
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
var affectedUserIds = room.createUserId == user.userId
|
|
? room.users.Where(u => u != null).Select(u => u.userId).Distinct().ToList()
|
|
: new List<string> { user.userId };
|
|
var (_, _, updatedRoom) = _chessService.ExitRoom(room.RoomId, user.userId);
|
|
await Task.WhenAll(affectedUserIds.Select(id => _chessService.UpdateUserGameType(id, 0)));
|
|
await Groups.RemoveFromGroupAsync(Context.ConnectionId, room.RoomId);
|
|
if (updatedRoom != null && updatedRoom.users != null && updatedRoom.users.Count > 0)
|
|
{
|
|
await Clients.Group(updatedRoom.RoomId).SendAsync("BeginGame", updatedRoom);
|
|
}
|
|
await Clients.Group(LobbyGroup).SendAsync("ExitRoomNotice", updatedRoom ?? room);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task JoinGame(string gameId, string pwd, string userId)
|
|
{
|
|
string cid = Context.ConnectionId;
|
|
var (success, message, room) = await _chessService.JoinGameAsync(gameId, pwd, userId, cid);
|
|
|
|
if (!success)
|
|
{
|
|
await Clients.Caller.SendAsync("GameError", message);
|
|
return;
|
|
}
|
|
|
|
await _chessService.UpdateUserGameType(userId, 3, room.RoomId);
|
|
|
|
await Groups.AddToGroupAsync(cid, room.RoomId);
|
|
await LeaveLobbyAsync(cid);
|
|
await Clients.Caller.SendAsync("JoinRoom", room);
|
|
await Clients.Group(room.RoomId).SendAsync("BeginGame", room);
|
|
await Clients.Group(room.RoomId).SendAsync("RoomStateNotice", room);
|
|
await Clients.Group(LobbyGroup).SendAsync("JoinRoomNotice", room);
|
|
if (room.users.Count == 2 && room.state == 1)
|
|
{
|
|
var redUser = room.users.FirstOrDefault(u => u.type == 2);
|
|
await Clients.Group(room.RoomId).SendAsync("GoldConfigRequired", new
|
|
{
|
|
targetUserId = redUser?.userId,
|
|
room
|
|
});
|
|
}
|
|
}
|
|
|
|
public async Task StartGame(string gameId, string userId)
|
|
{
|
|
if (!IsCaller(gameId, userId)) return;
|
|
var (success, message, room) = _chessService.StartGame(gameId, userId);
|
|
if (!success)
|
|
{
|
|
await Clients.Caller.SendAsync("GameError", message);
|
|
return;
|
|
}
|
|
|
|
var redUser = room.users.FirstOrDefault(u => u.type == 2);
|
|
await Clients.Group(room.RoomId).SendAsync("GoldConfigRequired", new
|
|
{
|
|
targetUserId = redUser?.userId,
|
|
room
|
|
});
|
|
}
|
|
|
|
public async Task GameReady(string gameId, string userId)
|
|
{
|
|
if (!IsCaller(gameId, userId)) return;
|
|
var (success, message, allReady) = _chessService.GameReady(gameId, userId);
|
|
|
|
if (!success)
|
|
{
|
|
await Clients.Caller.SendAsync("GameError", message);
|
|
return;
|
|
}
|
|
|
|
var room = _chessService.GetRoom(gameId);
|
|
await Clients.Groups(room.RoomId).SendAsync("ReadyNotice", userId);
|
|
|
|
if (allReady)
|
|
{
|
|
await Clients.Groups(room.RoomId).SendAsync("AllBeginTime", room);
|
|
}
|
|
}
|
|
|
|
public async Task EndGame(string gameId, string winUserId)
|
|
{
|
|
if (!IsCallerInRoom(gameId)) return;
|
|
var settledConnectionIds = GetRoomConnectionIds(gameId);
|
|
var (success, message, room) = await _chessService.EndGame(gameId, winUserId);
|
|
|
|
if (!success)
|
|
{
|
|
await Clients.Caller.SendAsync("GameError", message);
|
|
return;
|
|
}
|
|
|
|
await Clients.Group(room.RoomId).SendAsync("GameEnd", new
|
|
{
|
|
winUserId = winUserId,
|
|
room = room
|
|
});
|
|
await Clients.Group(LobbyGroup).SendAsync("ExitRoomNotice", room);
|
|
await JoinLobbyBatchAsync(settledConnectionIds);
|
|
await RemoveConnectionsFromRoomGroupAsync(room.RoomId, settledConnectionIds, "EndGame");
|
|
}
|
|
|
|
public async Task RestartGame(string gameId, string userId)
|
|
{
|
|
if (!IsCaller(gameId, userId))
|
|
{
|
|
var room0 = _chessService.GetRoom(gameId);
|
|
if (room0 == null || room0.createUserId != userId)
|
|
{
|
|
await Clients.Caller.SendAsync("GameError", "无权操作");
|
|
return;
|
|
}
|
|
}
|
|
var (success, message, room) = _chessService.RestartGame(gameId, userId);
|
|
|
|
if (!success)
|
|
{
|
|
await Clients.Caller.SendAsync("GameError", message);
|
|
return;
|
|
}
|
|
|
|
await Groups.AddToGroupAsync(Context.ConnectionId, room.RoomId);
|
|
await LeaveLobbyAsync(Context.ConnectionId);
|
|
await Clients.Caller.SendAsync("RestartGameSuccess", room);
|
|
await Clients.Group(LobbyGroup).SendAsync("RestartRoomNotice", room);
|
|
}
|
|
|
|
public async Task RequestEndGame(string gameId, string userId, string winUserId)
|
|
{
|
|
if (!IsCaller(gameId, userId)) return;
|
|
if (string.IsNullOrEmpty(userId))
|
|
{
|
|
await Clients.Caller.SendAsync("GameError", "参数异常");
|
|
return;
|
|
}
|
|
|
|
var settledConnectionIds = GetRoomConnectionIds(gameId);
|
|
var (success, message, room, allConfirmed) = await _chessService.RequestEndGame(gameId, userId, winUserId);
|
|
|
|
if (!success)
|
|
{
|
|
await Clients.Caller.SendAsync("GameError", message);
|
|
return;
|
|
}
|
|
|
|
if (allConfirmed)
|
|
{
|
|
await Clients.Group(room.RoomId).SendAsync("GameEnd", new
|
|
{
|
|
winUserId = winUserId,
|
|
room = room
|
|
});
|
|
await Clients.Group(LobbyGroup).SendAsync("ExitRoomNotice", room);
|
|
await JoinLobbyBatchAsync(settledConnectionIds);
|
|
await RemoveConnectionsFromRoomGroupAsync(room.RoomId, settledConnectionIds, "RequestEndGame");
|
|
}
|
|
else
|
|
{
|
|
var otherUserId = room.users.FirstOrDefault(u => u.userId != userId)?.userId;
|
|
if (!string.IsNullOrEmpty(otherUserId))
|
|
{
|
|
await Clients.Group(room.RoomId).SendAsync("OpponentRequestEnd", userId);
|
|
}
|
|
await Clients.Caller.SendAsync("EndGameRequestSent", message);
|
|
}
|
|
}
|
|
|
|
public async Task Heartbeat(string gameId)
|
|
{
|
|
var userId = Context.GetHttpContext()?.Request?.Query["userId"].ToString();
|
|
if (string.IsNullOrEmpty(userId))
|
|
{
|
|
return;
|
|
}
|
|
if (!IsCaller(gameId, userId)) return;
|
|
|
|
await _chessService.UpdateUserActiveTime(gameId, userId);
|
|
}
|
|
|
|
public async Task SetPlayerAfk(string gameId, string userId)
|
|
{
|
|
if (!IsCaller(gameId, userId)) return;
|
|
await _chessService.SetPlayerAfk(gameId, userId);
|
|
await Clients.Group(gameId).SendAsync("PlayerAfkNotice", new { userId });
|
|
var room = _chessService.GetRoom(gameId);
|
|
if (room != null)
|
|
{
|
|
await Clients.Group(gameId).SendAsync("RoomStateNotice", room);
|
|
}
|
|
}
|
|
|
|
public async Task CancelPlayerAfk(string gameId, string userId)
|
|
{
|
|
if (!IsCaller(gameId, userId)) return;
|
|
await _chessService.CancelPlayerAfk(gameId, userId);
|
|
await Clients.Group(gameId).SendAsync("PlayerCancelAfkNotice", new { userId });
|
|
var room = _chessService.GetRoom(gameId);
|
|
if (room != null)
|
|
{
|
|
await Clients.Caller.SendAsync("BeginGame", room);
|
|
await Clients.Group(gameId).SendAsync("RoomStateNotice", room);
|
|
}
|
|
}
|
|
|
|
public async Task CheckTimeout(string gameId)
|
|
{
|
|
if (!IsCallerInRoom(gameId)) return;
|
|
var beforeEndRoom = _redisService.GetChessRoom(gameId);
|
|
var settledConnectionIds = GetRoomConnectionIds(beforeEndRoom);
|
|
var (isTimeout, loserUserId) = await _chessService.CheckRoomTimeout(gameId);
|
|
|
|
if (isTimeout)
|
|
{
|
|
var room = _redisService.GetChessRoom(gameId);
|
|
if (room != null && beforeEndRoom != null)
|
|
{
|
|
await Clients.Group(room.RoomId).SendAsync("GameEnd", new
|
|
{
|
|
winUserId = beforeEndRoom.users.FirstOrDefault(u => u.userId != loserUserId)?.userId ?? "",
|
|
room = room
|
|
});
|
|
await Clients.Group(LobbyGroup).SendAsync("ExitRoomNotice", room);
|
|
await JoinLobbyBatchAsync(settledConnectionIds);
|
|
await RemoveConnectionsFromRoomGroupAsync(room.RoomId, settledConnectionIds, "CheckTimeout");
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task ExitRoom(string gameId, string userId)
|
|
{
|
|
if (!IsCaller(gameId, userId)) return;
|
|
var beforeExitRoom = _chessService.GetRoom(gameId);
|
|
var beforeExitUserIds = beforeExitRoom?.users?
|
|
.Where(u => u != null).Select(u => u.userId).Distinct().ToList()
|
|
?? new List<string> { userId };
|
|
var beforeExitConnectionIds = GetRoomConnectionIds(beforeExitRoom);
|
|
var (success, message, room) = _chessService.ExitRoom(gameId, userId);
|
|
|
|
if (!success)
|
|
{
|
|
await Clients.Caller.SendAsync("GameError", message);
|
|
return;
|
|
}
|
|
|
|
var affectedUserIds = room.users == null || room.users.Count == 0
|
|
? beforeExitUserIds
|
|
: new List<string> { userId };
|
|
await Task.WhenAll(affectedUserIds.Select(id => _chessService.UpdateUserGameType(id, 0)));
|
|
await Groups.RemoveFromGroupAsync(Context.ConnectionId, gameId);
|
|
await JoinLobbyAsync(Context.ConnectionId);
|
|
|
|
if (room.users == null || room.users.Count == 0)
|
|
{
|
|
await Clients.Group(LobbyGroup).SendAsync("ExitRoomNotice", room);
|
|
}
|
|
else
|
|
{
|
|
await Clients.Group(room.RoomId).SendAsync("ExitRoomSuccess", userId);
|
|
await Clients.Group(room.RoomId).SendAsync("BeginGame", room);
|
|
await Clients.Group(room.RoomId).SendAsync("RoomStateNotice", room);
|
|
await Clients.Group(LobbyGroup).SendAsync("ExitRoomNotice", room);
|
|
}
|
|
|
|
}
|
|
public async Task CreateRoom(CreateRoomParam param)
|
|
{
|
|
string cid = Context.ConnectionId;
|
|
var (success, message, room) = await _chessService.CreateRoomAsync(param, cid);
|
|
|
|
if (!success)
|
|
{
|
|
await Clients.Caller.SendAsync("GameError", message);
|
|
return;
|
|
}
|
|
|
|
await _chessService.UpdateUserGameType(param.userId, 3, room.RoomId);
|
|
|
|
await Clients.Caller.SendAsync("BeginGame", room);
|
|
await Groups.AddToGroupAsync(cid, room.RoomId);
|
|
await LeaveLobbyAsync(cid);
|
|
await Clients.Group(LobbyGroup).SendAsync("CreateRoomNotice", room);
|
|
|
|
}
|
|
|
|
public async Task GoldConfig(string gameId, long gold, string userId)
|
|
{
|
|
if (!IsCaller(gameId, userId)) return;
|
|
var (success, message, room) = _chessService.GoldConfig(gameId, gold, userId);
|
|
|
|
if (!success)
|
|
{
|
|
await Clients.Caller.SendAsync("GameError", message);
|
|
return;
|
|
}
|
|
|
|
await Clients.Groups(room.RoomId).SendAsync("GoldConfigSuccess", room);
|
|
await Clients.Group(room.RoomId).SendAsync("GoldConfigRoomNotice", room);
|
|
}
|
|
|
|
public async Task GetRoomList(int partition = 0)
|
|
{
|
|
await JoinLobbyAsync(Context.ConnectionId);
|
|
var list = _chessService.GetRoomList(partition, 100);
|
|
await Clients.Caller.SendAsync("GetRoomList", list);
|
|
}
|
|
|
|
public async Task GetRoomState(string gameId)
|
|
{
|
|
var room = string.IsNullOrWhiteSpace(gameId) ? null : _chessService.GetRoom(gameId);
|
|
if (room == null || !IsCallerInRoom(gameId))
|
|
{
|
|
await Clients.Caller.SendAsync("GameError", "房间不存在或当前连接不在房间中");
|
|
return;
|
|
}
|
|
|
|
await Clients.Caller.SendAsync("RoomStateNotice", room);
|
|
}
|
|
|
|
public async Task KickPlayer(string gameId, string targetUserId)
|
|
{
|
|
var room = string.IsNullOrWhiteSpace(gameId) ? null : _chessService.GetRoom(gameId);
|
|
var caller = room?.users?.FirstOrDefault(u => u.Cid == Context.ConnectionId && !u.IsLeave);
|
|
if (room == null || caller == null)
|
|
{
|
|
await Clients.Caller.SendAsync("GameError", "房间不存在或当前连接不在房间中");
|
|
return;
|
|
}
|
|
if (room.createUserId != caller.userId)
|
|
{
|
|
await Clients.Caller.SendAsync("GameError", "只有房主可以踢人");
|
|
return;
|
|
}
|
|
if (room.state == 2)
|
|
{
|
|
await Clients.Caller.SendAsync("GameError", "游戏进行中不能踢人");
|
|
return;
|
|
}
|
|
if (string.IsNullOrWhiteSpace(targetUserId) || targetUserId == caller.userId)
|
|
{
|
|
await Clients.Caller.SendAsync("GameError", "不能踢出自己");
|
|
return;
|
|
}
|
|
|
|
var target = room.users.FirstOrDefault(u => u.userId == targetUserId);
|
|
if (target == null)
|
|
{
|
|
await Clients.Caller.SendAsync("GameError", "目标玩家不在房间中");
|
|
return;
|
|
}
|
|
|
|
var targetCid = target.Cid;
|
|
var (success, message, updatedRoom) = _chessService.ExitRoom(gameId, targetUserId);
|
|
if (!success)
|
|
{
|
|
await Clients.Caller.SendAsync("GameError", message);
|
|
return;
|
|
}
|
|
|
|
await _chessService.UpdateUserGameType(targetUserId, 0);
|
|
if (!string.IsNullOrWhiteSpace(targetCid))
|
|
{
|
|
await Clients.Client(targetCid).SendAsync("KickedFromRoom", new { gameId, userId = targetUserId });
|
|
await Groups.RemoveFromGroupAsync(targetCid, gameId);
|
|
await JoinLobbyAsync(targetCid);
|
|
}
|
|
|
|
await Clients.Group(gameId).SendAsync("RoomStateNotice", updatedRoom);
|
|
await Clients.Group(gameId).SendAsync("BeginGame", updatedRoom);
|
|
await Clients.Group(LobbyGroup).SendAsync("ExitRoomNotice", updatedRoom);
|
|
}
|
|
|
|
public async Task SyncGameData(string gameId, string data)
|
|
{
|
|
var room = _redisService.GetChessRoom(gameId);
|
|
var otherUser = room?.users?.FirstOrDefault(u => u.Cid != Context.ConnectionId);
|
|
if (otherUser != null && !otherUser.IsLeave)
|
|
await Clients.Client(otherUser.Cid).SendAsync("SyncGameData", data);
|
|
}
|
|
|
|
public async Task GameOperation(string gameId, int type, string data)
|
|
{
|
|
var userId = Context.GetHttpContext()?.Request?.Query["userId"].ToString();
|
|
if (!string.IsNullOrEmpty(userId))
|
|
{
|
|
if (!IsCaller(gameId, userId)) return;
|
|
await _chessService.UpdateUserActiveTime(gameId, userId);
|
|
}
|
|
|
|
if (type == 2 && !string.IsNullOrEmpty(userId))
|
|
{
|
|
var room = _redisService.GetChessRoom(gameId);
|
|
if (room != null && room.state == 2)
|
|
{
|
|
var winUserId = room.users.FirstOrDefault(u => u.userId != userId)?.userId ?? "";
|
|
await EndGame(gameId, winUserId);
|
|
return;
|
|
}
|
|
}
|
|
|
|
await Clients.Group(gameId).SendAsync("GameOperation", new { type, data });
|
|
}
|
|
|
|
private bool IsCaller(string gameId, string userId)
|
|
{
|
|
var room = string.IsNullOrWhiteSpace(gameId) ? null : _chessService.GetRoom(gameId);
|
|
return room?.users?.Any(u => u.userId == userId && u.Cid == Context.ConnectionId && !u.IsLeave) == true;
|
|
}
|
|
|
|
private bool IsCallerInRoom(string gameId)
|
|
{
|
|
var room = string.IsNullOrWhiteSpace(gameId) ? null : _chessService.GetRoom(gameId);
|
|
return room?.users?.Any(u => u.Cid == Context.ConnectionId && !u.IsLeave) == true;
|
|
}
|
|
|
|
private List<string> GetRoomConnectionIds(string gameId)
|
|
{
|
|
return GetRoomConnectionIds(_redisService.GetChessRoom(gameId));
|
|
}
|
|
|
|
private static List<string> GetRoomConnectionIds(ChessGameRoom? room)
|
|
{
|
|
return room?.users?
|
|
.Where(user => !string.IsNullOrWhiteSpace(user.Cid))
|
|
.Select(user => user.Cid)
|
|
.Distinct()
|
|
.ToList() ?? new List<string>();
|
|
}
|
|
|
|
private async Task RemoveConnectionsFromRoomGroupAsync(
|
|
string roomId, IEnumerable<string> connectionIds, string reason)
|
|
{
|
|
foreach (var connectionId in connectionIds.Distinct())
|
|
{
|
|
try
|
|
{
|
|
await Groups.RemoveFromGroupAsync(connectionId, roomId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex,
|
|
"[ChessGroup] 结算退组失败 roomId={RoomId} cid={Cid} reason={Reason}",
|
|
roomId, connectionId, reason);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|