# 04 · 核心数据结构 > 这些结构是协议 `data` 的承载体,也是双向数据包共用的数据模型。新前端可据此定义自己的数据模型,字段名须与协议一致。 > > 本篇负责**壳框架核心数据结构**:`C_Player`、`Player(seat)`、`Desk`、`player_login` 响应、`roomtype` 配置、`GameData` 相关字段。 > **不含子游戏对局态结构**:`deskinfo` 内部结构由各子游戏定义,壳框架只把它当作"子游戏对局快照"原样透传给 `Game_Modify.Reconnect / DeskInfo`,用于断线重连(详见 05 章)。 --- ## C_Player(本地玩家对象) 来源:`06_Player.js` `function Player(seat)`(`06_Player.js:1`)。 全局唯一的本地玩家实例 `C_Player = new Player(-1)`(`12_Logic.js:480`),即初始 `seat = -1`。登录与各推送会写入这些字段。 逐字段与构造函数初值核对(`06_Player.js:2`–`33`): | 字段 | 初值 | 类型 | 说明 | |------|------|------|------| | openid | "" | string | 微信 openid | | playerid | -1 | int | 玩家ID | | nickname | "" | string | 昵称 | | avatar | "" | string | 头像URL | | sex | 0 | int | 性别 0未知/1男/2女 | | ip | "" | string | IP 地址 | | province | "" | string | 省(微信) | | city | "" | string | 市(微信) | | roomcard | -1 | int | 房卡数量 | | taskstate | 0 | int | 任务状态 | | unionid | 0 | string \| number | 开放平台唯一标识。⚠️ 构造初值为 `0`(number),但 `SetWxInfo` 用服务器下发字符串赋值(`06_Player.js:81`),运行期实际为 string | | seat | seat(参数) | int | 座位号(C_Player 为 -1=大厅,≥0=房间内) | | score | 0 | int | 积分 | | state | -1 | int | 解散投票状态,见下方"state 语义重载"说明 | | status | 0 | int | 身份 0默认/1房主/2非房主 | | canexit | 1 | int | 是否可直接退出 1是/0否,见下方说明 | | onstate | 0 | int | 在线状态 0在线/1离线/2通话中 | | addr | null | object \| null | 定位信息(含 province/city/errorCode) | | invitecode | "" | string | 邀请码 | | isStart | false | bool | 能否点击按钮开始游戏 | | bean | 0 | int | 豆豆(游戏币 / 星星) | | initialBean | 0 | int | 进房时豆豆初始值 | | isprepare | 0 | int | 准备状态 0未/1已 | | advanced | 0 | int | 是否有高级选项 0无/1有 | | paycode | "" | string | 吱口令 | | wareHouseStarCount | 0 | int | 仓库库存星星数 | | bankpower | 0 | int | 是否有仓库权限 | | bankpwd | 0 | int | 是否已设仓库密码 | | charm | undefined | int \| undefined | 魅力值(构造时显式 `undefined`,`06_Player.js:31`) | | sign | "" | string | 签名 | | tel | "" | string | 绑定手机号 | > **注意**:构造函数中 `offline` 字段被注释掉(`06_Player.js:17`),因此 `C_Player`(及 `new Player()` 出来的座位对象)初始**不带 `offline` 字段**;`offline` 仅在调用 `Init()` 后才被赋值(见下文 Player 节)。 ### state 语义重载(重点订正) `state` 默认值为 **-1**(非 0)。各取值含义以方法实际赋值为准(`06_Player.js`): | 取值 | 含义 | 赋值来源 | |------|------|----------| | -1 | 默认 / 无投票状态 | 构造函数 `06_Player.js:15`、`BreakRoom()` `06_Player.js:312` | | 0 | 申请解散(发起方)/ 未同意 | `ApplyBreakRoom()` `06_Player.js:321` | | 1 | 同意解散 | `AgreeBreakRoom()` `06_Player.js:327` | | 2 | 拒绝解散 | `RefuseBreakRoom()` `06_Player.js:333` | ⚠️ **`0` 是语义重载值**,不能绝对化为"申请": - `ApplyBreakRoom()` 把 state 设为 `0`(申请解散); - 但 `self_refuse_free_room` / `other_refuse_free_room` 在拒绝后会把**所有玩家** `state` **重置为 0**(`07_Desk.js:849`、`07_Desk.js:860`),此处 `0` 是"重置/默认"语义; - `login` 重连恢复投票时,`agreefree[i]==0` 也把对应玩家 state 写成 `0`(`07_Desk.js:409`),表示"未同意"。 > 源码 `06_Player.js:15` 的行内注释把 `-1` 写成"申请解散",是**错误注释**;以上述四个方法的实际赋值为准。 ### canexit 推断规则 `ChangeExit(v)` 设置(`06_Player.js:338`)。在 `login` / 进房 / 开战流程中: - `isbet == 0` → `ChangeExit(1)`(可退出);`isbet != 0` → `ChangeExit(0)`(不可退出)(`07_Desk.js:381`); - `infinite == 1`(无限局)→ 强制 `ChangeExit(1)`(始终可退出)(`07_Desk.js:386`)。 --- ## Player(座位对象 / 房内其他玩家) 来源:`06_Player.js` `function Player(seat)`。`Desk.PlayerList[]` 中每个元素都是一个 `Player`,由 `SetDeskInfo(data, bTemp)` 从服务器数据填充。其字段表与 C_Player 完全相同(同一构造函数),差异仅在于哪些字段来自服务器、哪些本地维护。 ### ① `SetDeskInfo(_data, bTemp)` 实际读取的服务器字段(权威,`06_Player.js:262`) | 字段 | 类型 | 说明 | |------|------|------| | playerid | int | 玩家ID | | nickname | string | 昵称 | | avatar | string | 头像URL | | sex | int | 性别 | | ip | string | IP | | onstate | int | 在线状态 0在线/1离线/2通话中 | | bean | int | 豆豆数 | | isprepare | int | 准备状态 0/1 | | paycode | string | 吱口令(`_data.paycode` 有才读,否则置 "",`06_Player.js:277`) | | charm | int | 魅力值 | | sign | string | 签名 | **initialBean 的赋值依赖 `bTemp`(重点订正)**(`06_Player.js:271`): ```js if(!bTemp){ this.initialBean = _data.bean; } // 正常入座:用本局豆豆做初始值 else { this.initialBean = _data.initialBean; } // 临时填充:保留原始 initialBean ``` - `bTemp` 缺省(`undefined`/`false`):`initialBean = _data.bean`(如 `login`/`self_join_room`/`other_join_room` 填充,调用时**不传** bTemp)。 - `bTemp == true`:`initialBean = _data.initialBean`,即**不**用 bean 覆盖。典型场景是 **换座** `change_seat`(`07_Desk.js:183`),两座玩家通过 `getDeskInfo()` 互换数据时以 `bTemp=true` 调用,保留各自原始初值。 - 因此原文档"无条件把 bean 复制为 initialBean"的描述是**错误**的,须按上面分支理解。 > `other_join_room` 推送中,座位号在外层 `data.seat`,其余玩家字段与上表同级**平铺**在 `data` 上(`07_Desk.js:730`)。 ### ② 客户端本地维护、不来自 `SetDeskInfo` 的字段 由其它包写入或本地推断,新前端按本地状态处理即可: - `seat`:进房时分配(`SetSeat`)。 - `status`:0默认/1房主/2非房主,由 `isowner` 或 `seat==0` 推断(`07_Desk.js:325`)。 - `state`:解散投票 -1/0/1/2,由 apply/agree/refuse 包写入。 - `canexit`:由 isbet/infinite 推断。 - `offline`:0否/1离线。⚠️ **不在构造函数中**,仅 `Init(bTemp)` 会写入 `offline = 0`(`06_Player.js:53`);房内离线/上线则改 `onstate`(`07_Desk.js:889`、`07_Desk.js:895`),实际在线状态以 `onstate` 为准。 - `tel`、`initialBean`(见上)、`isStart`、`roomcard`、`unionid`、`taskstate`、`advanced`、`bankpower`、`bankpwd`、`wareHouseStarCount`、`addr`、`invitecode`:座位对象一般用不到,仅 C_Player 维护。 --- ## Desk(牌桌/房间对象) 来源:`07_Desk.js:3`–`29` 顶部定义。表示当前房间整体状态,全部字段如下(逐字段核对,共 25 项): | 字段 | 初值 | 类型 | 说明 | |------|------|------|------| | PlayerList | [] | array\ | 座位玩家数组(`07_Desk.js:5`) | | roomcode | "" | string | 房号(`07_Desk.js:6`) | | stage | 0 | int | 牌桌阶段 0未开局/1已开局(`07_Desk.js:7`) | | state | 0 | int | 解散状态 0正常/1申请解散中(`07_Desk.js:8`) | | applyresult | -1 | int | 投票结果 -1无/0不通过/1通过(`07_Desk.js:9`) | | AgreeList | [] | array\ | 同意解散的座位号列表(`07_Desk.js:10`) | | agreefree | [] | array\ | 解散投票各座位 state 数组(来自 `agreefree.state`,`07_Desk.js:11`/`394`) | | roomtype | [] | array | 房间类型配置(透传,见下) | | warcnt | 0 | int | 开战条件(满足人数等,来自 `makewar`,`07_Desk.js:13`) | | playercnt | 0 | int | 当前玩家总数(`07_Desk.js:14`) | | count | 0 | int | 总局数(来自 `asetcount`,`07_Desk.js:15`) | | deskfree | null | object \| null | 解散结算快照(来自 `free_room` 的 `deskfree`,`07_Desk.js:16`/`868`) | | starCount | 0 | int | 投降扣除星星数(来自 `beanlimit`,见下方 ⚠️,`07_Desk.js:17`/`66`) | | roomMode | 0 | int | 0普通场/1星星场(`07_Desk.js:18`) | | needprepare | 0 | int | 是否需要准备(`07_Desk.js:19`) | | myInfo | null | Player \| null | 自己的信息(`07_Desk.js:20`) | | infinite | 0 | int | 是否无限局 0否/1是(`07_Desk.js:21`) | | isSystem | 1 | int | 是否系统房间,初值 1(`07_Desk.js:22`) | | shortcode | null | string \| null | 房间短号,初值 **null**(`07_Desk.js:23`/`93`) | | videoConfig | null | object \| null | 视频房选项(`07_Desk.js:24`) | | ownerNotice | null | string \| null | 短号房房主留言(`07_Desk.js:25`/`45`) | | videoDes | "" | string | 视频房描述(由 `setVideoConfig` 拼接,`07_Desk.js:26`/`108`) | | rebateNumber | 0 | int | 房间抽成数量(`07_Desk.js:27`) | | rebateMode | 0 | int | 抽成类型/方式(`07_Desk.js:28`) | | rebateType | 0 | int | 抽成对象——抽金币还是魅力值,⚠️ 数字取值见下(`07_Desk.js:29`) | > 另有方法 `Desk.setMyInfo / setRoom / Create / Init / login / create_room / self_join_room / other_join_room` 等填充逻辑,详见对应推送章节。 --- ## 登录响应(Desk.login / player_login) `route=agent, rpc=player_login` 的内层 `data`。`Desk.login(_msg)` 解析(`07_Desk.js:212`)。 `state==0` 为成功;字段分两组:A. 账号与资产(始终下发)、B. 房间恢复(在房 / 重连才下发)。 ### A. 账号与资产(始终下发) | 字段 | 类型 | 说明 | 出处 | |------|------|------|------| | state | int | 0成功,非0失败 | `07_Desk.js:214` | | playerid | int | 玩家ID(`SetMyInfo` 读取) | `07_Desk.js:268` | | score | int | 积分(缺省补 0,`07_Desk.js:243`) | | | bean | int | 豆豆 | `SetMyInfo` | | roomcard | int | 房卡 | `SetMyInfo` | | taskstate | int | 任务状态 | `SetMyInfo` | | ip | string | 玩家IP | `SetMyInfo` | | bankpower | int | 仓库权限 | `SetMyInfo` | | bank | int | 仓库库存星星数(→ wareHouseStarCount) | `06_Player.js:97` | | bankpwd | int | 是否已设仓库密码 | `06_Player.js:102` | | charm | int | 魅力值 | `SetMyInfo` | | sign | string | 签名 | `SetMyInfo` | | tel | string | 绑定手机号 | `06_Player.js:109`、`07_Desk.js:228` | | agentid | string | 代理ID(→ GameData.AgentId) | `07_Desk.js:276` | | channelid | string | 渠道ID(→ GameData.ChannelId) | `07_Desk.js:277` | | invitecode | string | 邀请码(可选,有才设) | `07_Desk.js:246` | | initCard | int | 初始房卡(可选,→ GameData.initCard) | `07_Desk.js:257` | | initBean | int | 初始豆豆(可选,→ GameData.initBean) | `07_Desk.js:263` | | advanced | int | 是否有高级选项(可选,缺省 0) | `07_Desk.js:271` | | **openid** | string | 微信 openid(仅 `deviceLogin` 分支读取) | `07_Desk.js:220` | | **nickname** | string | 昵称(deviceLogin 分支) | `07_Desk.js:222` | | **avatar** | string | 头像URL(deviceLogin 分支,作 `headimgurl`) | `07_Desk.js:221` | | **sex** | int | 性别(deviceLogin 分支) | `07_Desk.js:223` | | **city** | string | 城市(deviceLogin 分支) | `07_Desk.js:224` | | **province** | string | 省份(deviceLogin 分支) | `07_Desk.js:225` | | **unionid** | string | 开放平台ID(deviceLogin 分支) | `07_Desk.js:226` | > `openid/nickname/avatar/sex/city/province/unionid/tel` 仅在 `GameData.sysConfig.deviceLogin` 为真时被读取并通过 `SetWxInfo` 写入 C_Player(`07_Desk.js:215`–`230`)。非设备登录时这些信息由微信授权链路另行获取。 ### B. 房间恢复(在房 / 断线重连时才下发) 仅当 `_msg.data.roomcode` 存在时进入此分支(`07_Desk.js:282`),代表玩家原本就在房间内。 | 字段 | 类型 | 说明 | 出处 | |------|------|------|------| | roomcode | string | 房号 | `07_Desk.js:282` | | roomtype | array | 房间类型配置(透传) | `07_Desk.js:284` | | asetcount | int | 总局数(→ Desk.count) | `07_Desk.js:323` | | isbattle | int | 0未开局/1已开局(→ Desk.stage) | `07_Desk.js:324` | | makewar | int | 开战条件(→ Desk.warcnt) | `07_Desk.js:321` | | seat | int | 自己的座位 | `07_Desk.js:320` | | isowner | int | 1房主/0非房主(→ C_Player.status) | `07_Desk.js:325` | | players | array | 房内玩家数组(按座位下标,元素为 SetDeskInfo 字段集,可含 null) | `07_Desk.js:338` | | roommode | int | 0普通/1星星场(→ Desk.roomMode) | `07_Desk.js:295` | | beanlimit | int | ⚠️ → `setStarCount`(投降扣除星星数),见下方说明 | `07_Desk.js:298` | | needprepare | int | 是否需准备 | `07_Desk.js:301` | | infinite | int | 0普通/1无限局 | `07_Desk.js:304` | | rebateNumber | int | 抽成数量 | `07_Desk.js:307` | | rebateMode | int | 抽成方式/类型 | `07_Desk.js:308` | | rebateType | int | 抽成对象(⚠️ 取值见下) | `07_Desk.js:309` | | sign | string | 签名(→ C_Player.setSign) | `07_Desk.js:310` | | ownerNotice | string | 短号房房主留言 | `07_Desk.js:311` | | videoConfig | object | 视频房配置 | `07_Desk.js:312` | | shortcode | string | VIP 房短号 | `07_Desk.js:313` | | match | object | 比赛信息(→ GameData.matchInfo) | `07_Desk.js:285` | | matchid | string | 比赛ID(→ GameData.matchId) | `07_Desk.js:290` | | agreefree | object | 解散投票信息 `{ state:int[], countdown }`,存在即表示恢复进行中的投票 | `07_Desk.js:389` | | isbet | int | 0可退出/1不可退出(→ ChangeExit) | `07_Desk.js:381` | | deskinfo | object | 子游戏对局快照,存在即触发重连(见下方 ⚠️) | `07_Desk.js:419` | #### ⚠️ B 组存疑/订正点 - **deskinfo 与 isbattle 的关系**:`login()` 仅判断 `if(_msg.data.deskinfo)`(`07_Desk.js:419`)是否存在来决定是否调用 `Game_Modify.Reconnect`,**并不**判断 `isbattle`。原文档"isbattle=1 时用于重连"是推断;准确表述应为 **"当响应含 `deskinfo` 时触发子游戏重连(服务器通常仅在对局进行中才下发该字段)"**。 - **beanlimit 语义**:源码为 `if(_msg.data.beanlimit){ Desk.setStarCount(_msg.data.beanlimit); }`(`07_Desk.js:298`),`setStarCount` 写入 `Desk.starCount`,而 `starCount` 注释为"投降扣除星星数量"(`07_Desk.js:17`)。因此 `beanlimit` 倾向于 **"星星场投降扣除数"** 而非泛指"豆豆限制",⚠️ 确切业务含义待后端确认。 - **rebateType 取值**:`07_Desk.js:29` 仅注释"抽金币还是魅力值",源码**无明确数字定义**。原文档标注的"0金币/1魅力值"⚠️ **待后端确认**。 - **deskwar 不在 login 中**:`login()` 函数体内**未读取** `deskwar`。`deskwar`(是否自动开战)由 `self_join_room`(`07_Desk.js:634`)、`other_join_room`(`07_Desk.js:747`)、`player_prepare`(`07_Desk.js:1137`)读取。因此 deskwar **不属于登录响应字段**,已移至下方"创建/进入响应"表(见 ⚠️ 标注)。 --- ## 房间创建 / 进入响应公共字段 `create_room`(`07_Desk.js:454`)/ `self_join_room`(`07_Desk.js:529`)响应内层 `data`,字段集与登录 B 组高度一致: | 字段 | 说明 | 备注 | |------|------|------| | state | 0成功,非0失败 | self_join_room 另有 99=房间不存在 | | roomcode | 房号 | | | seat | 自己座位 | | | isowner | 是否房主 0/1 | | | roomtype | 房间类型配置数组(透传) | | | makewar | 开战条件 | | | asetcount | 总局数 | | | players | 房内玩家数组(join 时有) | | | roommode / beanlimit / needprepare / infinite | 房间模式相关 | beanlimit 语义同上 ⚠️ | | rebateNumber / rebateMode / rebateType | 抽成相关 | rebateType 取值待确认 ⚠️ | | shortcode | 短号 | | | match / matchid | 比赛信息 | | | videoConfig | 视频房配置 | | | ownerNotice / ownerNote | 房主留言 / 备注(ownerNote 仅 self_join_room,`07_Desk.js:575`) | | | paycode | 吱口令(join 时可选,`07_Desk.js:584`) | | | **deskwar** | 是否自动开战(join 时可选,`07_Desk.js:634`)⚠️ 此字段属"进入"而非登录响应 | | | deskinfo | 子游戏对局快照(join 时可选,重连用,`07_Desk.js:647`) | | | showerror / error | 失败时:是否显示错误 / 错误文案 | | --- ## roomtype(房间类型配置数组)⚠️ 子游戏自定义 `Desk.setRoom` 接收并直接保存到 `Desk.roomtype`(`07_Desk.js:166`),创建/进入时原样透传给 `Game_Modify.onCreateDesk` / `Game_Modify.setRoomDes`(`07_Desk.js:358`、`508`、`629`)。**壳框架本身不解析该数组的各位含义**。 示例(来自 `01_SubGame_modify.js` 创建房间,仅供参考): ```js roomtype: [1, 4, 1, 2, 2, [1,1,[1,2000,10],null,null,1], [1,0,5]] ``` - 这是一个**嵌套数组**,各位含义(局数、人数、玩法选项、星星场配置、抽成配置等)由**具体子游戏**约定。 - **服务器按这套数组解析房间规则**,新前端创建房间时必须发送与原子游戏**完全一致的 roomtype 结构**。 - 上述示例数组各位精确含义属**子游戏范畴**,需对照目标子游戏的创建房间界面逐项核对,本壳框架未给出通用定义。 --- ## GameData(全局数据,相关字段) 来源:`04_Data.js`(`var GameData = GameData || {}`,`04_Data.js:24`)。login / 进房流程写入的相关键: | 字段 | 来源 | 说明 | |------|------|------| | AgentId | login `agentid`(`07_Desk.js:276`) | 代理ID | | ChannelId | login `channelid`(`07_Desk.js:277`) | 渠道ID | | initCard | login `initCard`(`07_Desk.js:258`) | 初始房卡 | | initBean | login `initBean`(`07_Desk.js:264`) | 初始豆豆 | | matchInfo | login/进房 `match`(`07_Desk.js:286`) | 比赛场信息 | | matchId | login/进房 `matchid`(`07_Desk.js:291`) | 比赛ID | | starName | 配置(`04_Data.js:189`,默认"星星") | 货币显示名 | | infoSeat | `04_Data.js:225`(默认 -1) | 信息面板焦点座位 | | sysConfig | `04_Data.js:440` | 子游戏系统配置(含 `deviceLogin` 等开关) | | isLogin / hallLogin / isReconnect / vipRoomJump | login 流程置位(`07_Desk.js:241` 等) | 登录/重连状态标记 | > GameData 字段众多,此处仅列与本篇数据流(登录、房间恢复)直接相关者。完整 GameData 配置项见配置文档。 --- ## 其它推送数据结构 | rpc | data 结构 | 出处 | |-----|-----------|------| | update_bean | `{ bean, change?, seat?, type?, text }` | `07_Desk.js:1201` | | update_roomcard | `{ roomcard, text, change? }`(无 change 时刷新本地) | `06_Player.js:142` | | update_charm | `{ seatlist:[ {seat, charm} ] }` | `07_Desk.js:1260` | | change_star | `{ state, star1, star2, msg?, count?, error? }` | `07_Desk.js:1238` | | broadcast | `{ msgtype?:0框/1滚动, msgcontent }` | `07_Desk.js:1112` | | show_message | `{ msg, time }` | `07_Desk.js:1173` | | kick_server | `{ msg }` | `07_Desk.js:1108` | | kick_offline | `{ fromOther?, gameid? }` | `07_Desk.js:945` | | free_room | `{ freeNow?, deskfree?, roomcard?, seats?, tips?, time? }` | `07_Desk.js:864` |