# 03 · 平台层协议 — room 路由(房间服务器) > 信封:`{ app:"youle", route:"room", rpc:"<下列名称>", data:{...} }` > 本篇覆盖 **route=room 的全部平台层数据包**(房间生命周期 / 解散投票 / 开局 / 房内社交 / 服务器切换)。不含子游戏对局内协议(见 05 章)。 > 房间内操作通用请求字段(除特别说明外,C→S 请求恒含这四项):`agentid`、`gameid`、`playerid`、`roomcode`。 > 「响应/推送」均指服务器返回内层 `data` 字段。座位号 `seat` 通常为 0 起整数;`Logic.ChangeToStatus(C_Player.seat, seat)` 把绝对座位转为以自己为基准的相对视角。 > > **图例**:🐛 源码 bug;⚠️ 待后端确认;— 无特别说明。 > > **条目格式**: > `rpc名` · route=room · <方向> — 场景;请求字段(C→S);响应/推送字段(S→C);源码 发送/接收;备注。 > > **路由说明(交叉引用)**:发送(C→S)走 `RouteList.room`,但少数推送虽在 room 场景内消费,其上行请求实际走 `agent` 路由——下文逐条标注;这类项归档于 [02 · agent 路由](02-协议-agent路由.md),此处仅记录其在房内的接收语义。 --- ## 房间生命周期 ### self_break_room · route=room · C→S 请求 + S→C 响应 - 场景:房主在**未开局**前主动解散房间。 - 请求字段(C→S):仅通用四字段。 - 响应字段(S→C):`roomcode`(可选,用于从本地"我的房间"列表移除)。处理:清空牌桌、回大厅。 - 源码:发送 `09_Net.js:266`(`Send_self_break_room`)/接收 `09_Net.js:271` → `07_Desk.js:707`(`Desk.self_break_room`)。 - 备注:— ### other_break_room · route=room · S→C 推送 - 场景:他人(房主)解散房间,推送给房内其余玩家。 - 请求字段(C→S):无(纯推送)。 - 响应字段(S→C):无业务字段;触发 `Func.exitRoom()`、清桌、回大厅、提示"房主已解散房间!"。 - 源码:接收 `09_Net.js:277` → `07_Desk.js:721`(`Desk.other_break_room`)。 - 备注:常伴随 **connect_agentserver** 切服包(`data.opt == other_break_room`,见下文)。 ### self_exit_room · route=room · C→S 请求 + S→C 响应 - 场景:自己在**未开局**前退出房间。 - 请求字段(C→S):仅通用四字段(发送处见 `08_Utl_Output.js:993`)。 - 响应字段(S→C):`isowner`(可选,==1 且非无限局时把房间加回本地列表), `seat`(可选,传给 `Game_Modify.myExitRoom`), `roomcode`(可选)。 - 源码:发送 `09_Net.js:289`(`Send_self_exit_room`)/接收 `09_Net.js:294` → `07_Desk.js:753`(`Desk.self_exit_room`)。 - 备注:— ### other_exit_room · route=room · S→C 推送 - 场景:其他玩家(未开局)退出房间。 - 请求字段(C→S):无(纯推送)。 - 响应字段(S→C):`seat`(离开者座位)。处理:清该座位、`playercnt--`;若 `seat==0` 且非无限局,提示房主已离开。 - 源码:接收 `09_Net.js:301` → `07_Desk.js:785`(`Desk.other_exit_room`)。 - 备注:— ### player_prepare · route=room · C→S 请求 + S→C 推送 - 场景:玩家点击准备(`needprepare==1` 的房间)。 - 请求字段(C→S):仅通用四字段。 - 响应/推送字段(S→C):`seat`(准备者座位), `deskwar`(可选;为真表示满足开战条件 → `HideStartScene` + `Game_Modify.StartWar`)。推送给房内所有人。 - 源码:发送 `09_Net.js:629`(`Send_player_prepare`)/接收 `09_Net.js:632` → `07_Desk.js:1126`(`Desk.player_prepare`)。 - 备注:— ### change_room · route=room · C→S 请求 → S→C 响应(change_seat) · 跨座换桌 - 场景:玩家请求换座 / 换桌。 - 请求字段(C→S):仅通用四字段(**不带目标座位**,由服务器决定换到哪个空位)。发送处 `11_GameUI.js:1945`、`08_Utl_Output.js:1006`。 - 响应字段(S→C):rpc 名改为 **`change_seat`**,`data`:`seat1`, `seat2`(两个互换的座位号)。处理:交换两座 Desk 信息,若自己在其中则 `C_Player.SetSeat` 更新。 - 源码:发送 `09_Net.js:678`(`Send_change_room`)/接收 `09_Net.js:682`(`Net.change_seat`) → `07_Desk.js:178`(`Desk.change_seat`)。 - 备注:上行 rpc=`change_room`,下行 rpc=`change_seat`,二者成对。 ### share_room · route=room · C→S 请求 + S→C 响应 - 场景:把房间分享到世界房列表。 - 请求字段(C→S):通用四字段;可选 `roomlist`、`roomtype`、`shareType`(高级房分享时携带,见 `11_GameUI.js:1734`/`11_GameUI.js:1891`)。 - 响应字段(S→C):无业务字段;提示"已成功分享至平台!"。 - 源码:发送 `09_Net.js:635`(`Send_share_room`)/接收 `09_Net.js:638` → `07_Desk.js:1152`(`Desk.share_room`)。 - 备注:— --- ## 进房推送(他人视角) ### other_join_room · route=room · S→C 推送 - 场景:其他玩家加入当前房间。 - 请求字段(C→S):无(纯推送;自己进房用 `self_join_room`,走 agent 路由,见 02 章)。 - 响应/推送字段(S→C):`seat`(新玩家座位) + 该玩家完整对象(直接传给 `Player.SetDeskInfo`,见 [04 · Player 座位对象](04-数据结构.md#player-座位对象)): `playerid`, `nickname`, `avatar`, `sex`, `ip`, `onstate`, `bean`, `charm`, `sign` 等;另含 `needprepare`(可选), `deskwar`(可选;为真直接走 `Desk.makewar` 开战)。 - 源码:接收 `09_Net.js:281` → `07_Desk.js:729`(`Desk.other_join_room`);字段写入见 `06_Player.js:263`(`Player.SetDeskInfo`)。 - 备注:⚠️ `charm`/`sign` 是否每次必含、其余字段是否完整以 `Player.SetDeskInfo` 实际读取为准,详见 [04 章](04-数据结构.md#player-座位对象)。 ### other_offline · route=room · S→C 推送 - 场景:其他玩家离线。 - 请求字段(C→S):无(纯推送)。 - 响应字段(S→C):`seat`。处理:该座 `onstate=1`,刷新 UI。 - 源码:接收 `09_Net.js:390` → `07_Desk.js:887`(`Desk.other_offline`)。 - 备注:— ### other_online · route=room · S→C 推送 - 场景:其他玩家重新上线。 - 请求字段(C→S):无(纯推送)。 - 响应字段(S→C):`seat`, `ip`。处理:该座 `onstate=0`、更新 ip。 - 源码:接收 `09_Net.js:395` → `07_Desk.js:893`(`Desk.other_online`)。 - 备注:— --- ## 解散投票(开局后) > 流程:某玩家 apply → 全员收到 `other_apply_free_room`(带各座状态与倒计时)→ > 各玩家 agree/refuse → 最终 `free_room` 广播结果。 ### self_apply_free_room · route=room · C→S 请求 + S→C 响应 - 场景:自己申请解散房间。 - 请求字段(C→S):仅通用四字段。 - 响应字段(S→C):`agreefree` { `state`:[各座同意状态数组], `countdown`:倒计时 }。 - 源码:发送 `09_Net.js:308`/接收 `09_Net.js:313` → `07_Desk.js:804`(`Desk.self_apply_free_room`)。 - 备注:— ### other_apply_free_room · route=room · S→C 推送 - 场景:他人申请解散。 - 请求字段(C→S):无(纯推送)。 - 响应字段(S→C):`seat`(申请者), `agreefree` { `state`[], `countdown` }。 - 源码:接收 `09_Net.js:319` → `07_Desk.js:817`(`Desk.other_apply_free_room`)。 - 备注:— ### self_agree_free_room · route=room · C→S 请求 + S→C 响应 - 场景:自己同意解散。 - 请求字段(C→S):仅通用四字段。 - 响应字段(S→C):无业务字段;本地把自己加入同意列表、刷新投票 UI。 - 源码:发送 `09_Net.js:323`/接收 `09_Net.js:328` → `07_Desk.js:829`(`Desk.self_agree_free_room`)。 - 备注:— ### other_agree_free_room · route=room · S→C 推送 - 场景:他人同意解散。 - 请求字段(C→S):无(纯推送)。 - 响应字段(S→C):`seat`(同意者)。 - 源码:接收 `09_Net.js:333` → `07_Desk.js:836`(`Desk.other_agree_free_room`)。 - 备注:— ### self_refuse_free_room · route=room · C→S 请求 + S→C 响应 - 场景:自己拒绝解散。 - 请求字段(C→S):仅通用四字段。 - 响应字段(S→C):无业务字段;清空同意列表、投票结果置不通过、弹"已拒绝"结果。 - 源码:发送 `09_Net.js:338`/接收 `09_Net.js:343` → `07_Desk.js:842`(`Desk.self_refuse_free_room`)。 - 备注:— ### other_refuse_free_room · route=room · S→C 推送 - 场景:他人拒绝解散(任一人拒绝即否决本轮)。 - 请求字段(C→S):无(纯推送)。 - 响应字段(S→C):`seat`(拒绝者)。 - 源码:接收 `09_Net.js:348` → `07_Desk.js:853`(`Desk.other_refuse_free_room`)。 - 备注:— ### free_room · route=room · S→C 推送 - 场景:解散投票最终结果。 - 请求字段(C→S):无(纯推送)。 - 响应字段(S→C): | 字段 | 说明 | |------|------| | `freeNow` | true=立即解散;false=投票通过待确认 | | `deskfree` | 解散结算信息对象(可选) | | `roomcard` | 解散后房卡数(可选,写回 `C_Player.setRoomcard`) | | `tips` | 提示文本(`freeNow==true` 时) | | `time` | 提示显示时长(`freeNow==true` 时) | | `seats` | 投票通过的座位数组(`freeNow==false` 时传给 `OpenApplyResult`) | - 源码:接收 `09_Net.js:353` → `07_Desk.js:864`(`Desk.free_room`)。 - 备注:可伴随 **connect_agentserver** 切服包(`data.opt == free_room`,见下文)。 ### beanroom_surrender · route=room · C→S 请求 + S→C 响应 - 场景:豆豆房(金币房)投降。 - 请求字段(C→S):通用四字段 + `count`(投降数量 = `GameData.surrendCount`,发送处 `11_GameUI.js:1245`)。 - 响应字段(S→C):`state`(0=成功 → `Game_Modify.onSurrender(_msg)`); 失败时 `showerror`(==1 则弹) / `error`(错误文本)。 - 源码:发送 `09_Net.js:613`(`Send_beanroom_surrender`)/接收 `09_Net.js:617`(`Net.beanroom_surrender`,逻辑直接在 Net 内处理)。 - 备注:⚠️ 未见成对的 `other_xxx` 推送,是否向房内其他玩家广播他人投降待后端确认。 --- ## 开局 ### self_makewar · route=room · C→S 请求 → S→C 响应(self_makewar) - 场景:房主主动开局。 - 请求字段(C→S):仅通用四字段。 - 响应字段(S→C):rpc=`self_makewar`,无业务字段;触发 `Desk.self_makewar` → `Game_Modify.StartWar(_msg)`。 - 源码:发送 `09_Net.js:470`(`Send_self_makewar`)/接收 `09_Net.js:475` → `07_Desk.js:987`(`Desk.self_makewar`)。 - 备注:— ### other_makewar · route=room · S→C 推送 - 场景:开局广播(房主开局或满员自动开局)给房内其他玩家。 - 请求字段(C→S):无(纯推送)。 - 响应字段(S→C):开局信息对象 → `Desk.makewar` → `Game_Modify.StartWar(msg)`。 - 源码:接收 `09_Net.js:480`(`Net.other_makewar`) → `07_Desk.js:1020`(`Desk.makewar`)。 - 备注:实际发牌等对局数据在此之后通过**子游戏内协议**下发(见 05 章)。 --- ## 房间内社交 ### send_text · route=room · C→S 请求 + S→C 推送 · 文字聊天 - 场景:文字聊天 / 全服公告 / 预定义常用语。 - 请求字段(C→S):通用四字段 + `text`(内容;点常用语时为 `Game_Config.Info.TextContent[spid-206]` 文本) + `type`(0=普通 / 1=全服公告;按是否勾选公告设 0/1) + `info`(可选;**有 info 时 `type` 改为 2**)。发送处 `11_GameUI.js:1170`(输入框)、`11_GameUI.js:2717`(常用语)。 - 推送字段(S→C):`type`(0=普通 / 1=全服公告 / 2=机器人 / 3=预定义文字), `text`(内容;**type==3 时为 `Game_Config.Info.TextContent` 的 1 基索引**,客户端按 `(idx-1)%len` 取文本), `seat`(发送者座位,type≠1 时使用), `info`(type==2 时附加)。 - 源码:发送 `09_Net.js:400`(`Send_send_text`)/接收 `09_Net.js:404` → `07_Desk.js:900`(`Desk.send_text`)。 - 备注:百人场(`vipInfinite`)只处理 type==1 公告分支。 ### receive_chat · route=room · S→C 推送(⚠️) - 场景:聊天推送的另一可能 rpc 名(与 `send_text` 成对)。 - 请求字段(C→S):无。 - 推送字段(S→C):未知(疑似同 `send_text` 推送结构)。 - 源码:常量 `02_Const.js:38`(`RpcList.receive_chat`) 已定义;**客户端 `Net` / `Desk` 未注册同名处理函数**。 - 备注:⚠️ `02_Const.js` 已定义但客户端无处理函数;服务器聊天推送究竟用 `send_text` 还是 `receive_chat` 待后端确认。 ### send_voice · route=room · C→S 请求 + S→C 推送 · 语音 - 场景:发送语音消息。 - 请求字段(C→S):通用四字段 + `voiceurl`(已上传音频地址) + `time`(时长) + `info`(可选) + `type`(可选,有 info 时为 2)。发送处 `05_Func.js:1714`、`05_Func.js:2984`。 > 语音需先上传得到 `voiceurl` 再随包发送,客户端不直接传音频二进制。 - 推送字段(S→C):`type`(0=普通 / 2=机器人), `seat`(发送座位), `voiceurl`, `time`, `info`(type==2 时)。 - 源码:发送 `09_Net.js:492`(`Send_send_voice`)/接收 `09_Net.js:495` → `07_Desk.js:1073`(`Desk.send_voice`)。 - 备注:— ### play_voice · route=room · S→C 推送(⚠️) - 场景:与 `send_voice` 成对,疑为语音**播放**推送。 - 请求字段(C→S):无(`send_voice` 上行,`play_voice` 疑为下行播放推送)。 - 推送字段(S→C):未知。 - 源码:常量 `02_Const.js:36` 与 `02_Const.js:54`(重复定义 `RpcList.play_voice`) 已定义;**客户端 `Net` / `Desk` 未注册同名处理函数**(注:`GameUI.play_voice` 是本地播放方法,非网络处理器)。 - 备注:⚠️ `02_Const.js` 已定义但客户端无网络处理函数;服务器是否下发 `play_voice` 待后端确认。 ### send_gift · route=room · C→S 请求 + S→C 推送 · 互动/送礼 - 场景:向指定座位送互动礼物。 - 请求字段(C→S):通用四字段 + `giftid`(=`spid_up - 255`,按钮精灵号算出,发送处 `11_GameUI.js:2725`) + `receiveseat`(=`GameData.InteractPlayer`) + `info`(可选) + `type`(有 info 时为 2)。 - 推送字段(S→C):`type`(0=普通 / 2=机器人), `giftid`(客户端做 `(giftid-1)%4+1` 归一为 1~4 动画), `sendseat`(发送座位), `receiveseat`(接收座位), `info`(type==2 时)。 - 源码:发送 `09_Net.js:484`(`Send_send_gift`)/接收 `09_Net.js:487` → `07_Desk.js:1060`(`Desk.send_gift`)。 - 备注:— ### other_send_gift · route=room · S→C 推送(⚠️) - 场景:与 `send_gift` 成对,疑为他人送礼推送。 - 请求字段(C→S):无。 - 推送字段(S→C):未知(疑似同 `send_gift` 推送结构)。 - 源码:常量 `02_Const.js:52`(`RpcList.other_send_gift`) 已定义;**客户端 `Net` / `Desk` 未注册同名处理函数**。 - 备注:⚠️ `02_Const.js` 已定义但客户端无处理函数;服务器送礼推送用 `send_gift` 还是 `other_send_gift` 待后端确认。 ### send_phiz · route=room · C→S 请求 + S→C 推送 · 表情 - 场景:发送表情动画。 - 请求字段(C→S):通用四字段 + `text`(=`up_id`,表情按钮序号 1~`ConstVal.Emotion.count`,发送处 `11_GameUI.js:815`) + `info`(可选) + `type`(有 info 时为 2)。 - 推送字段(S→C):`type`(0=普通 / 2=机器人), `text`(表情 ID,客户端按 `(text-1)%ConstVal.Emotion.src_list.length+1` 归一), `seat`(发送座位), `info`(type==2 时)。 - 源码:发送 `09_Net.js:539`(`Send_send_phiz`)/接收 `09_Net.js:543` → `07_Desk.js:1086`(`Desk.send_phiz`)。 - 备注:— ### call_phone · route=room · C→S 请求 + S→C 推送 · 拨打电话 - 场景:拨打/接听电话,置玩家 `onstate=2`(通话中)。 - 请求字段(C→S):仅通用四字段(发送处 `06_Player.js:380` / `:388` / `:396`,对应接起/电话进来/去电三种触发)。 - 推送字段(S→C):rpc=`call_phone`,`seat`(拨打者座位 → 该玩家 `onstate=2`)。 - 源码:发送 `09_Net.js:452`(`Send_call_phone`)/接收 `09_Net.js:456` → `07_Desk.js:967`(`Desk.call_phone`)。 - 备注:— ### other_callphone · route=room · S→C 推送(⚠️) - 场景:与 `call_phone` 成对,疑为他人拨打电话推送。 - 请求字段(C→S):无。 - 推送字段(S→C):未知(疑似含 `seat`)。 - 源码:常量 `02_Const.js:45`(`RpcList.other_callphone`) 已定义;**客户端 `Net` / `Desk` 未注册同名处理函数**。 - 备注:⚠️ `02_Const.js` 已定义但客户端无处理函数;服务器是否用 `other_callphone` 单独推送他人拨号待后端确认(当前客户端用 `call_phone` 推送统一处理本人与他人)。 ### hangup_phone · route=room · C→S 请求 + S→C 推送 · 挂断电话 - 场景:挂断电话,置玩家 `onstate=0`。 - 请求字段(C→S):仅通用四字段(发送处 `06_Player.js:372`)。 - 推送字段(S→C):rpc=`hangup_phone`,`seat`(挂断者座位 → 该玩家 `onstate=0`)。 - 源码:发送 `09_Net.js:461`(`Send_hangup_phone`)/接收 `09_Net.js:465` → `07_Desk.js:977`(`Desk.hangup_phone`)。 - 备注:— ### other_hangup · route=room · S→C 推送(⚠️) - 场景:与 `hangup_phone` 成对,疑为他人挂断电话推送。 - 请求字段(C→S):无。 - 推送字段(S→C):未知(疑似含 `seat`)。 - 源码:常量 `02_Const.js:47`(`RpcList.other_hangup`) 已定义;**客户端 `Net` / `Desk` 未注册同名处理函数**。 - 备注:⚠️ `02_Const.js` 已定义但客户端无处理函数;服务器是否用 `other_hangup` 单独推送待后端确认。 --- ## 房内可收的其它推送(接收语义在 room 场景,发送走 agent 路由) > 以下推送在房间内被消费,但其上行请求实际走 `agent` 路由,详细发送定义见 [02 · agent 路由](02-协议-agent路由.md);此处仅记录房内接收语义,避免遗漏。 ### update_bean · S→C 推送(房内他人充值场景) - 场景:房间内**他人**充值豆豆/金币,刷新该座余额。 - 推送字段(S→C):`seat`(充值者座位;无 `seat` 则为自己充值,走 `C_Player.update_bean`), `bean`(新余额), `type`(==6 时播放金币音效 `Logic.playCoinMp3()`);另含 `change`(存在时为大厅购买分支,不在房内座位场景)。 - 源码:接收 `09_Net.js:598`(`Net.update_bean`) → `07_Desk.js:1201`(`Desk.update_bean`)。 - 备注:与 [02 章的 update_bean](02-协议-agent路由.md) 为**同名推送**,此处侧重"房间内座位刷新"分支(`seat` 已定义时)。 ### update_charm · S→C 推送(房内座位魅力更新) - 场景:批量更新房内座位魅力值。 - 推送字段(S→C):`seatlist`:[ { `seat`, `charm` }, ... ],逐项写入对应座位 `Player.setCharm`。 - 源码:接收 `09_Net.js:731`(`Net.update_charm`) → `07_Desk.js:1260`(`Desk.update_charm`)。 - 备注:⚠️ 交叉引用——**发送走 agent 路由**(`09_Net.js:727` `Send_update_charm` → `RouteList.agent`),归档于 02 章;本篇仅记录房内接收。 ### broadcast · S→C 推送(房内跑马灯 / 弹窗) - 场景:房内可收到的全局广播(跑马灯或顶部弹窗)。 - 推送字段(S→C):`msgtype`(0=顶部即时弹窗 `ShowiMessage` / 1=跑马灯 `addBroadcast`), `msgcontent`(内容文本)。 - 源码:接收 `09_Net.js:534`(`Net.broadcast`) → `07_Desk.js:1112`(`Desk.broadcast`)。 - 备注:交叉引用——**发送走 agent 路由**(`09_Net.js:530` `Send_broadcast` → `RouteList.agent`),归档于 02 章;本篇仅记录房内接收。 --- ## 服务器切换(room 侧) ### connect_roomserver · route=room · C→S 请求 + S→C 推送 · 切到房间服 - 场景:从大厅服切换到房间服务器。 - 请求字段(C→S):由 `Send_connect_roomserver` 发起(走 room 路由)。 - 推送字段(S→C):`data.roomserver`(新房间服地址)。处理:置 `GameData.ConnectType=true`、`GameData.ConnectRpc=connect_roomserver`、`GameData.Server=data.roomserver`,关闭当前连接并用新地址重连。 - 源码:发送 `09_Net.js:500`(`Send_connect_roomserver`)/接收 `09_Net.js:503`(`Net.connect_roomserver`)。 - 备注:— ### connect_agentserver · S→C 推送(切回大厅服;上行走 agent 路由) - 场景:解散/退房后从房间服切回大厅服(agent)。 - 推送字段(S→C):`data.opt`(切服原因,值为 `other_break_room` 或 `free_room`;命中其一时 `GameUI.StartLoad()`), `data.agentserver`(新大厅服地址)。处理:置 `GameData.ConnectRpc=connect_agentserver`、`GameData.Server=data.agentserver`,关连接重连。 - 源码:接收 `09_Net.js:518`(`Net.connect_agentserver`);上行 `09_Net.js:515`(`Send_connect_agentserver` → `RouteList.agent`)。 - 备注:交叉引用——**上行请求走 agent 路由**,归档于 02 章;本篇记录其作为 `other_break_room`/`free_room` 后续切服推送的语义(原文档"服务器切换"仅写了 `connect_roomserver`,此处补全 `connect_agentserver`)。