房间与结算
房间进程里发生的事:票据换座位、准备与开局、结束与结算、掉线与重连,以及投票、系列赛、观战和迟到。
房间进程只跑一局。它由大厅拉起、端口由大厅分配、票据由大厅签发;自己不做匹配、不写档案, 只把这一局的座位、过程与结果管好。
票据换座位
客户端旅行进房间时带着 JoinToken,房间用同一份 JoinTokenSecret 校验:
<instance>.<player>.<role>.<team>.<expiry>.<sig>| 角色 | 房间怎么对待 |
|---|---|
Player | 找空座位坐下,队伍按票据里的 team |
LateJoinPlayer | 中途加入:按人数找有位的队伍(bAllowLateJoin) |
Spectator | 不占座位,进观战列表(bAllowSpectators) |
ReconnectPlayer | 按 player 找回原座位;找不到座位才算新进 |
票据过期(JoinTokenTtlSeconds,默认 300 秒)或签名不符 → 4002 BadToken。
票据由客户端在旅行时提交(ServerSubmitJoinToken),房间接受后回 ClientJoinTokenAccepted。
座位与准备
Login 校验 → SeatAssigned → Roster → ReadyChanged → GameStartSetPlayerReady(PS, bReady):准备状态复制给所有人- 大厅在分配后推
ClientStartReadyCheck;bAutoAcceptReadyCheck为真时客户端自动确认 ReadyCheckSeconds(默认 12 秒)内没人确认 →6007 ReadyTimeout,这一组解散回队列
容量相关查询:
int32 Max = Room->GetMaxPlayers();
int32 Teams = Room->GetTeamCount();
int32 Seated = Room->GetSeatedCount();
int32 Watching = Room->GetSpectatorCount();结束一局
三种结束方式,区别在带不带结果:
Room->EndGame(TEXT("Aborted")); // 中止,不算结果
Room->EndGameWithResult(TEXT("Completed"), ECrossingMatchResult::TeamAWin);
Room->EndGameWithWinner(TEXT("Completed"), /*Team=*/0); // 多队模式结束前可以把每人的数据带上:
Room->ReportPlayerStats(PlayerId, Stats); // kills/deaths/assists/score/placement
Room->ReportPlayerOutcome(PlayerId, Outcome); // 项目自定义个人结果
Room->MarkPlayerForfeit(PlayerId); // 掉线判负
Room->PlayerConcede(PS); // 个人投降ECrossingMatchResult:TeamAWin / TeamBWin / TeamWin(多队,胜队见 WinnerTeam)/ Draw /
Remake / Aborted。
结算怎么保证不丢
EndGame → 结算 Elo / 档案 → RESULT(含 matchId)
→ 后台线程发送 → 大厅落盘 SettlementJournal 并回 ACK
→ 房间收到 ACK 停止重试(最多 3 次)
→ 大厅重启后从 Settlements.log 恢复,按 matchId 幂等| 行为 | 日志 |
|---|---|
| 发出结果 | [Room] event=SettlementPush |
| 收到 ACK | [Room] event=SettlementAcked |
| ACK 超时 | [Room] event=SettlementAckTimeout |
| 大厅侧落盘 | [Session] event=SettlementRecorded |
| 重复结算 | [Session] event=SettlementDuplicate |
| 落盘失败 | [Session] event=SettlementPersistFailed |
| 房间关了没结算 | [Session] event=SettlementMissing(只报告) |
ACK 是乐观 ACK:大厅先回 ACK 再异步落盘,所以极端情况下「ACK 已回但进程崩」仍可能丢一局,
SettlementMissing 会把这种情况记下来,供人工补。
掉线与重连
玩家掉线 → 房间保留座位 + 推 DISCONNECT(带 expires)到大厅
→ 大厅登记重连表
客户端回来 → 大厅 ConsumeReconnect → 签发 role=ReconnectPlayer 的票据
→ 分配回原实例 → 旅行 → 房间 TryReclaimSeat| 配置 | 默认 | 作用 |
|---|---|---|
ReconnectWindowSeconds | 120.0 | 座位保留多久 |
bBotTakeoverOnDisconnect | false | 掉线时 Bot 顶替,回来让位 |
窗口之外回来按新进处理;这局的对局结果里这个人是 Forfeit 还是 NoContest 由房间规则决定。
团队投降
Room->RequestSurrender(TeamId); // 发起
Room->VoteSurrender(PlayerId, bYes); // 表决
bool bSafe = Room->IsSafeToLeave();bEnableSurrenderVote=True
SurrenderVoteMinSeconds=900.0 ; 开局多少秒后才允许发起
SurrenderVoteYesRatio=0.7 ; 通过比例日志:SurrenderVoteStarted → SurrenderVote → SurrenderPassed / SurrenderRejected。
Remake、安全离开、AFK
| 机制 | 配置 | 日志 |
|---|---|---|
| 开局减员重开 | bEnableRemake、RemakeWindowSeconds(180) | RemakeTriggered |
| 可安全离开 | bSafeToLeaveOnAbandon、IsSafeToLeave() | SafeToLeave |
| AFK 检测 | bEnableAfkDetection、AfkTimeoutSeconds(120) | AfkDetectionArmed / PlayerAfk |
客户端用 ServerReportActivity 上报活动(输入、操作时调用)。
系列赛
Room->StartSeries(ECrossingSeriesFormat::BestOf, 3);
Room->EndRound(WinnerTeam);
int32 Wins = Room->GetTeamWins(0);| 格式 | 说明 |
|---|---|
Auto | 按配置推断 |
BestOf | BO3/BO5,先到 WinsNeeded |
PointsRace | 积分赛,目标分 |
RoundRobin | 循环赛 |
SingleElim | 单败淘汰 |
相关配置在「07 赛季」与「05 对局规则」两类里。日志:SeriesArmed → RoundEnded →
RoundAdvanced → SeriesFinished。
观战与迟到
| 能力 | 配置 | 入口 |
|---|---|---|
| 观战 | bAllowSpectators、MaxSpectators | 客户端 ServerRequestSpectate |
| 中途加入 | bAllowLateJoin、bLateJoinRated、bLateJoinReplaceBots | 客户端 ServerRequestLateJoin |
观战只做座位隔离,相机与输入由项目实现;迟到按人数找位,不按 Elo。
健康与自退
房间在 GetHealthPort() 上监听,回 OK;<players>;<state>;<uptime>。
大厅超过 LobbyLostTimeoutSeconds 不在,房间自退(LobbyLostArmed → LobbyLost),
不让实例变成孤儿。