Files
youle_cocos/docs/protocol/04-数据结构.md
T
joywayer 35216f75e7 feat: integrate room framework and isolated subgame bundles
Complete room UI and protocol integration, move game definitions and resources behind bundle entries, publish authoritative version XML, and document single-game builds. Include all current resource changes and experiment artifacts.
2026-09-09 03:13:18 +08:00

21 KiB
Raw Blame History

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):

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<Player> 座位玩家数组(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<int> 同意解散的座位号列表(07_Desk.js:10)
agreefree [] array<int> 解散投票各座位 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)。非设备登录时这些信息由微信授权链路另行获取。

✅ 真机联调实测确认(2026-06-28,YouleNexus + 本地测试服):成功登录回包(单层 {app,route,rpc,data},见 01 §3.2)data 实测为:

{"state":0,"playerid":430511,"agentid":"…","channelid":"…","nickname":"…","avatar":"…","openid":"…","sex":0,"unionid":"…","roomcard":3,"bean":0,"score":0,"invitecode":null,"advanced":0,"taskstate":1,"ip":"127.0.0.1","bankpower":1,"bank":0,"sign":null,"tel":null,"initCard":"3","initBean":"0","bankpwd":0,
 "agentname":"进贤","agentmode":2,"gameversion":41}

上表 A 组字段均得到印证。另含文档此前未列的三个字段(旧客户端 Desk.login 未读、属代理/版本信息):

  • agentname string —— 代理商名称(实测"进贤")。
  • agentmode int —— 代理模式(实测 2)。
  • gameversion int —— 服务器侧游戏版本(实测 41)。

同时确认 请求 player_login.data.version 字段须为数字 versionCode(实测发 10000 通过;发字符串 "1.1" 被 kick_server「检查到新版本」拒绝),与源码 data.version=GameData.versionCode 一致。

B. 房间恢复(在房 / 断线重连时才下发)

仅当 _msg.data.roomcode 存在时进入此分支(07_Desk.js:282),代表玩家原本就在房间内。

字段 类型 说明 出处
roomcode string / number 房号;响应可为数字,见下方入口类型说明 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 房号 string 或非负安全整数;见下方入口类型说明
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 失败时:是否显示错误 / 错误文案

roomcode 入口类型说明

实际 create_room 响应可返回数字房号(2026-09-08 联调报文类型证据);旧前端直接接收该字段,服务端子游戏入口也按数值解析 roomcode。不能根据 Desk 初始值 "" 推断所有响应都只返回字符串。

Cocos 在创建、加入及登录恢复的响应解析入口统一把数字房号转为内部字符串标识;字符串原样保留,包括前导零。数字必须为非负安全整数,非法类型显式报错,不补默认房号。原始响应 raw 不改写,roomtype 继续原样透传;此适配不要求服务器改动。

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 创建房间,仅供参考):

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