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.
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
# 框架文档
|
||||
|
||||
返回[文档首页](../README.md)。
|
||||
|
||||
框架提供网络、配置解析、会话状态、公共房间流程、UI 能力与构建校验。具体游戏负责玩法编码、创建页面、牌局状态和玩法渲染。应用层负责注册与装配。
|
||||
|
||||
## 两条开发操作指南
|
||||
|
||||
- [如何构建](build/README.md):构建文档导航。
|
||||
- [单游戏构建操作](build/single-game-build.md):gameKey、Bundle 勾选、切换游戏、导出与产物检查。
|
||||
- [如何接入子游戏](integration/README.md):注册、目录约定、生命周期与资源绑定;包含[创建房间接入](integration/create-room.md)。
|
||||
|
||||
## 分类
|
||||
|
||||
- architecture:总体设计、房间生命周期、实施边界。
|
||||
- sdk:子游戏可用契约、旧接口迁移、资源释放约定。
|
||||
- protocol:传输、平台 agent/room RPC、数据结构、游戏消息桥接。
|
||||
- build:专门的构建指南目录,包含 XML 身份来源、预览、打包与旧脚手架说明。
|
||||
- integration:专门的子游戏接入指南目录,包含通用接入和创建房间流程。
|
||||
|
||||
## 入口
|
||||
|
||||
1. [当前架构与实施状态](architecture/current-status.md)
|
||||
2. [房间架构设计](architecture/room-platform.md)
|
||||
3. [SDK 契约](sdk/contracts.md)
|
||||
4. [协议目录](protocol/README.md)
|
||||
5. [单游戏构建](build/single-game-build.md)与[版本身份配置](build/version-and-build.md)
|
||||
|
||||
框架维护者增加公共能力时应使用注入的能力契约,不增加具体 gameKey/gameid 分支;子游戏开发者使用这些能力,不访问框架内部 Store、Runtime、WebSocket。
|
||||
@@ -0,0 +1,30 @@
|
||||
# 当前架构与实施状态
|
||||
|
||||
返回[框架目录](../README.md)。核对日期:2026-09-09。
|
||||
|
||||
## 目录职责
|
||||
|
||||
- 工程内 assets/framework:可复用的协议、状态、SDK、公共展示能力。
|
||||
- 工程内 assets/app:通用 Bundle 入口解析、游戏选择、XML 身份组装,不导入具体游戏。
|
||||
- 工程内 assets/games:当前实际的子游戏代码与资源。
|
||||
- 工程内 assets/scripts/platform-login:当前引擎 UI 适配和公共组件,包括 CreateRoomPage、GameComposition;虽然路径有历史名称,也不能为每款游戏增加具体实现分支。
|
||||
- 工程 extensions:构建扩展,不侵入运行时框架。
|
||||
|
||||
这里的目录名称用于定位开发职责,完整接入说明见[子游戏指南](../integration/README.md)。
|
||||
|
||||
## 已实现
|
||||
|
||||
各子游戏通过自己的 GameDefinition 和 GameEntry_<gameKey> 提供工厂、配置及组件类型,SubgameAssets 按 gameKey 动态加载入口及两份私有 prefab。公共承载场景仍在启动组件中绑定。平台接收注入的身份和入口,不内置具体游戏 ID。XML value 提供数值 versionCode,name 提供展示 version。
|
||||
|
||||
创建页面继承 CreateRoomPage,仅提交子游戏编码的 roomtype。平台保存和转发不透明 roomtype,具体游戏解释玩法。房间模块使用 room/messages/ui/scope 能力,结束时解除其资源和订阅。
|
||||
|
||||
构建扩展读取实际启动场景的游戏选择,验证 XML 与资源包,向 Web 根目录复制相同字节的 version.xml。
|
||||
|
||||
## 尚未完成
|
||||
|
||||
- gameKey 尚未自动同步构建 Bundle 勾选项;当前需在构建面板显式保留公共 resources、目标游戏及版本包。
|
||||
- 发布环境配置仍有历史 profiles.ts 来源,尚需迁移至应用发布配置;不能将每次改框架配置当作正式多游戏构建方案。
|
||||
- 二七王创建房间与房间入口已接通,完整牌局消息、结算、deskinfo 画面恢复未完成。
|
||||
- 房间音频所有权能力尚未接入。
|
||||
|
||||
[房间架构设计](room-platform.md)与[旧 API 迁移说明](../sdk/legacy-api-migration.md)保留详细设计;其中规划项需对照本页,不能承诺为现有功能。
|
||||
@@ -0,0 +1,391 @@
|
||||
# 房间平台与子游戏接入架构设计
|
||||
|
||||
版本:1.1 · 日期:2026-09-08 · 状态:平台核心已实施;验收与未支持边界见[实施记录](current-status.md)。
|
||||
|
||||
本文保留设计契约与目标示例;具体 API 名称、落地位置及兼容期以实施记录和代码为准。音频所有权、完整子游戏玩法与原生设备验收不因平台核心落地而自动视为完成。网络协议、原生接口与序列化资源约束继续以仓库规定及对应权威文档为准。
|
||||
|
||||
## 文档导航
|
||||
|
||||
- 源码审查、方案优缺点与原流程依据
|
||||
- [旧接口迁移与新接入契约](current-status.md)
|
||||
- [分阶段实施计划与验收](current-status.md)
|
||||
- 平台协议目录
|
||||
- [二七王当前玩法协议](../../subgames/erqiwang/protocol/packet_protocol.md)
|
||||
|
||||
## 1. 目标与非目标
|
||||
|
||||
### 1.1 目标
|
||||
|
||||
1. 原服务器零修改即可兼容,公开协议字段、route/rpc、数据类型与重连快照保持不变。
|
||||
2. 平台不依赖具体子游戏;子游戏不访问平台内部 Store、场景和网络实现。
|
||||
3. 纯逻辑无需启动 Cocos 即可测试;资源装配与真实交互必须另行在编辑器/构建中验证。
|
||||
4. 子游戏只开发规则、协议映射、游戏模型和展示,不重复实现大厅、聊天、投票、设置、原生桥。
|
||||
5. 房间生命周期、消息顺序、资源所有权、失败处理都具有明确契约。
|
||||
6. 对已证实的全量重建、重复头像资源等问题做增量优化,性能结论以基线测量为准。
|
||||
|
||||
### 1.2 非目标
|
||||
|
||||
- 不重新实现服务器,不添加前端方便但服务器不存在的字段或 requestId。
|
||||
- 不将原 Game_Modify/Utl 全量搬成同名 TypeScript 类。
|
||||
- 不在本阶段引入 ECS、Worker、全局响应流框架、反射容器或远程热插件系统。
|
||||
- 不以此文档承诺完成二七王叫分、出牌、计分、回放或结算画面。
|
||||
- 不自动修复已经用错误 roomtype 创建的服务器房间,不静默改写用户缓存。
|
||||
|
||||
### 1.3 “零耦合”的可验证定义
|
||||
|
||||
零具体游戏依赖:除应用组合根外,公共代码不得 import games/*。
|
||||
|
||||
零内部访问:游戏只能引用 sdk/contracts 和自身模块,不引用平台 Store、Router、PlatformStartup。
|
||||
|
||||
零引擎依赖的逻辑:房间应用层、状态与游戏规则/模型不 import cc,不读取 window、localStorage 或原生全局对象。
|
||||
|
||||
允许必要契约依赖:游戏和平台共同依赖小而稳定的 SDK。完全没有任何依赖既不可实现,也不是本方案目标。
|
||||
|
||||
## 2. 权威数据与兼容边界
|
||||
|
||||
### 2.1 平台数据
|
||||
|
||||
身份、连接/认证状态、房间公共元数据、成员、座位、在线/准备、公告、聊天、投票结果,以各自协议来源为准。平台负责公共协议字段的校验、归一化和状态提交。
|
||||
|
||||
归一化必须有实际协议依据、在入口单点执行。例如此前数字房号的兼容不能推广成“所有字段都 String/Number 一遍”。出站类型仍按对应 rpc 契约生成。
|
||||
|
||||
### 2.2 子游戏载荷
|
||||
|
||||
roomtype、deskinfo、deskwar、deskfree 及游戏 route 的数据,由游戏解释。平台只保存、传递、按协议判断载荷是否存在,不读取内部选项、手牌或分数。
|
||||
|
||||
拟议的通用可传输类型:
|
||||
|
||||
```ts
|
||||
type JsonValue = null | boolean | number | string
|
||||
| readonly JsonValue[]
|
||||
| { readonly [key: string]: JsonValue };
|
||||
|
||||
type Roomtype = JsonValue;
|
||||
```
|
||||
|
||||
运行时仅允许有限数字、无循环的 JSON 值,不允许函数、BigInt、引擎对象和 undefined。这是传输限制,不是玩法格式限制。每个外层协议若规定字段必需,则必须存在;不能把缺失字段改成 null。
|
||||
|
||||
“原样”指保持 JSON 数据结构、值、类型、数组顺序和未知字段;不要求保留 JSON 文本空格或对象键的序列化顺序。若协议某个字段本身是字符串编码,则字符串内容必须完整保留。
|
||||
|
||||
二七王创建编码仍由其编码器生成 11 位字符串;模板可以发送数组。平台不得为了接入方便转换任何一种格式。
|
||||
|
||||
### 2.3 游戏事实与公共展示
|
||||
|
||||
玩法包有时才包含下一局、参与状态和局内分数。因此平台不能要求所有公共展示变化只能来自平台 route,也不能允许游戏任意改 Store。
|
||||
|
||||
采用单向的 GameRoomProjection:游戏模型解析自己的协议后发布只读投影;公共展示选择器组合平台快照与游戏投影。
|
||||
|
||||
必须区分:
|
||||
|
||||
- 服务端公共 room stage 与游戏自己的 round phase,不用同一个字段存两者。
|
||||
- 账户余额与局内分数,不用 changeBean 写同一份值。
|
||||
- 公共准备状态与玩法参与/等待下一局状态,不把派生按钮显隐反写准备值。
|
||||
- 平台规则已禁止的动作与游戏附加限制:游戏可以收紧权限,不可扩大平台明确禁止的权限。
|
||||
|
||||
确有玩法包需要同步公共协议状态时,建立具名、带来源的映射,并在对应游戏适配器与协调器测试;禁止 patchRoom/setPlayerState 等任意写入接口。
|
||||
|
||||
## 3. 模块结构与依赖
|
||||
|
||||
以下为目标职责分布,不要求第一批任务一次搬完目录:
|
||||
|
||||
```text
|
||||
assets/
|
||||
app/ 唯一知道具体游戏的组合入口
|
||||
bootstrap/
|
||||
composition/
|
||||
framework/
|
||||
sdk/contracts/ 公共纯类型与稳定端口
|
||||
net/ 保留 WireClient、心跳、连接代际
|
||||
protocol/ 公共信封与协议适配
|
||||
platform/room/
|
||||
room-coordinator.ts 流程与事务编排
|
||||
room-scope.ts 会话作用域和清理
|
||||
room-mailbox.ts 有界业务消息等待队列
|
||||
room-entry.ts 不同进入来源的描述
|
||||
room-policy.ts 平台公共动作策略
|
||||
platform/stores/ 保留并渐进拆分公共状态
|
||||
presentation/ 无 Cocos 的展示模型与选择器
|
||||
adapters/ 网络、原生、存储等端口实现
|
||||
ui/room/ 公共 Cocos 面板与场景适配
|
||||
games/<game>/
|
||||
definition.ts 唯一游戏定义
|
||||
protocol/ 玩法解析、rpc 类型和入口映射
|
||||
rules/ roomtype、座位、描述等纯规则
|
||||
model/ 游戏状态和同步输入处理
|
||||
presentation/ 游戏展示模型
|
||||
cocos/ 创建页、房间 View、资源
|
||||
```
|
||||
|
||||
依赖关系:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
App[应用组合入口] --> Platform[平台房间应用层]
|
||||
App --> Game[具体游戏模块]
|
||||
App --> Cocos[Cocos 与设备适配]
|
||||
Platform --> SDK[共享契约]
|
||||
Game --> SDK
|
||||
Cocos --> SDK
|
||||
Platform --> Protocol[公共协议与状态]
|
||||
Game --> Rules[游戏规则与游戏模型]
|
||||
```
|
||||
|
||||
SDK 不能反向 import 平台实现。当前部分能力类型声明在 game-host-adapter.ts,迁移时应提取到契约目录,不能让 presentation 因一个类型依赖具体适配器。
|
||||
|
||||
## 4. 游戏定义与资源装配
|
||||
|
||||
### 4.1 唯一游戏定义
|
||||
|
||||
一个权威定义提供:本地 key、游戏协议 route、身份配置关联、规则、模块工厂、创建页/房间展示引用、游戏内容和可选能力声明。
|
||||
|
||||
身份中的 agentid/channelid/gameid 继续由配置来源提供。游戏定义引用该配置关联,不在多个文件复制实际渠道身份。
|
||||
|
||||
Cocos 组件可以引用生成的资源定义或唯一的装配资源,但不得与 TypeScript 定义分别维护两份 gameKey、route、prefab。实施时优先选择构建阶段验证明确静态引用的方式;不要运行时按名称扫描并猜测页面类型。
|
||||
|
||||
### 4.2 装配验证
|
||||
|
||||
- 每个创建 prefab 恰好一个创建页契约实现。
|
||||
- 每个房间 prefab 恰好一个房间展示契约实现。
|
||||
- 所有必需资源可解析,引用目标与声明的游戏一致。
|
||||
- 声明的协议 route 不与平台保留 route 冲突。
|
||||
- 不通过“随便创建再销毁一个游戏实例”验证声明;在真实创建时验证实例契约,避免工厂预检产生业务副作用。
|
||||
- 未使用的可选能力无需假实现;声明了必需能力却未绑定时装配失败。
|
||||
|
||||
最近二七王 prefab 中模板脚本残留的问题,必须由上述检查自动发现,而不是等创建 RPC 发出去才发现。
|
||||
|
||||
## 5. SDK 开发者接口
|
||||
|
||||
### 5.1 原则
|
||||
|
||||
SDK 面向开发者只暴露少量分组能力:room、messages、ui、audio、scope。内部实现可按职责拆分,不把每个旧函数变成独立服务。
|
||||
|
||||
禁止 getService(name)、任意 Store 写入、任意 app/route 发包、直接传平台节点。消息传输使用绑定当前游戏 route 的端口;通用房间命令由平台构建身份和房号。
|
||||
|
||||
### 5.2 核心形状
|
||||
|
||||
以下用于说明边界;完整字段按实施阶段测试补全,不是可直接粘贴的 SDK 实现:
|
||||
|
||||
```ts
|
||||
interface GameModel {
|
||||
// context 已绑定一个房间;不得缓存到跨房间全局。
|
||||
initialize(context: RoomContext): void;
|
||||
// 同步处理;不可返回等待动画结束的 Promise。
|
||||
handle(input: GameInput): void;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
interface RoomContext {
|
||||
readonly room: RoomReadPort;
|
||||
readonly commands: RoomCommandPort;
|
||||
readonly messages: GameMessagePort;
|
||||
readonly ui: GameUiPort;
|
||||
readonly audio: AudioPort;
|
||||
readonly scope: ScopePort;
|
||||
}
|
||||
|
||||
interface GameMessagePort {
|
||||
send(rpc: string, data: JsonValue): void;
|
||||
}
|
||||
```
|
||||
|
||||
GameMessagePort 的 void 表示传输操作本身,不表示服务器业务成功。实际回复仍按协议作为输入处理。游戏内部可以将 rpc 与 payload 建成强类型映射,公共平台不需要知道映射内容。
|
||||
|
||||
RoomReadPort 提供只读快照、按选择器订阅和座位映射。快照变更具有本地 revision;本地 revision 不加入网络包、不冒充服务器序列号。
|
||||
|
||||
### 5.3 输入事件保留来源
|
||||
|
||||
必须区分:
|
||||
|
||||
- created:成功创建结果,原 roomtype 和 infinite 等数据。
|
||||
- login-restored:登录恢复,携带原 deskinfo。
|
||||
- login-waiting:登录已有房间但无恢复快照。
|
||||
- joined-waiting:普通主动加入。
|
||||
- joined-snapshot:主动加入已有 deskinfo。
|
||||
- started:加入、准备、成员变化、self_makewar/makewar 引起的开战,携带 sourceRpc 和原整包。
|
||||
- player-ready/joined/left/online/offline/seat-changed:公共成员事件。
|
||||
- game-message:当前游戏 route 的原消息。
|
||||
- dissolution-settlement:服务端解散结果与原 deskfree,保留 freeNow/确认时机。
|
||||
|
||||
内部可用联合类型表示,不扩展服务器包。类型名称可以调整,但这些语义不能被一个 restore(unknown) 覆盖。
|
||||
|
||||
### 5.4 只读投影
|
||||
|
||||
游戏模型拥有 GameRoomProjection 并允许订阅;它只包含公共 HUD 真正需要的事实,不泄露完整手牌与内部模型。
|
||||
|
||||
第一阶段字段以模板和二七王实际需求为准:玩法阶段、自己的参与状态、按服务器座位的玩法分数、公共操作限制。投影是游戏状态的派生结果,不另设可任意写入的第二份状态。
|
||||
|
||||
RoomSnapshot revision 与 GameProjection revision 分别可追踪;在一次协议处理事务完成后向公共 UI 发布组合快照,避免 UI 看到同一消息的一半更新。
|
||||
|
||||
### 5.5 页面接口
|
||||
|
||||
创建页接收上次配置和 submit/cancel。编码由游戏完成,平台收到 roomtype 只封装与发送;未知或错误旧配置由游戏迁移策略处理,没有迁移策略就明确提示并允许用户显式重置。
|
||||
|
||||
房间 View 的职责只有挂载、根据模型绘制、将用户操作交给控制器、取消动画与卸载。它不读取 WebSocket、不直接恢复协议快照、不以节点可见性决定业务状态。
|
||||
|
||||
## 6. 生命周期与消息时序
|
||||
|
||||
### 6.1 分开建模
|
||||
|
||||
- Connection:连接中、已连接、重连中、停止。
|
||||
- Authentication:未登录、等待回复、已登录、拒绝/被踢。
|
||||
- RoomLifecycle:outside、entering、active、recovering、leaving、failed。
|
||||
- GamePhase:子游戏自己的轮次/行动阶段。
|
||||
- Dissolution/Settlement:投票和结果展示独立于 GamePhase。
|
||||
|
||||
这些维度存在明确约束,但不要混成一个巨大的状态枚举。
|
||||
|
||||
### 6.2 进入事务
|
||||
|
||||
1. 验证公共外层协议,保留原始载荷。
|
||||
2. 在游戏规则入口验证其拥有的配置并解析需要的座位约束;不在 UI 层补缺失数据。
|
||||
3. 完成公共状态构建的预检。已接受的新服务端房间不能在失败时偷偷恢复成“旧房间仍有效”。
|
||||
4. 使旧 scope 失效并取消旧任务,创建新的 generation,提交进入状态。
|
||||
5. 创建游戏模型、绑定能力,启动异步资源与界面挂载。
|
||||
6. 界面挂载就绪后,以正确来源发送进入输入;恢复使用原快照,不自行填快照字段。
|
||||
7. 标记可交互,按顺序处理进入后等待的业务消息。
|
||||
|
||||
失败策略:步骤 1–3 失败不创建半初始化 scope;步骤 4 后失败将新会话标为 failed 并完整释放,不伪装 active。是否重新登录恢复,由现有连接/恢复契约决定,不能对格式错误无休止自动重试。
|
||||
|
||||
### 6.3 异步就绪
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Net as 协议入口
|
||||
participant Room as 房间协调器
|
||||
participant View as Cocos 场景适配
|
||||
participant Game as 游戏模型
|
||||
Net->>Room: 接受创建/加入/登录结果
|
||||
Room->>Room: 预检并建立新 scope
|
||||
Room->>Game: initialize(context)
|
||||
Room->>View: mount(scope)
|
||||
Net->>Room: 后续房间业务消息
|
||||
Room->>Room: 暂存有界 FIFO
|
||||
View-->>Room: ready
|
||||
Room->>Game: 带来源的进入输入
|
||||
Room->>Game: 按序处理暂存消息
|
||||
```
|
||||
|
||||
model.initialize 只绑定上下文与初始模型,不要求 View 已可用。需要页面的操作通过能力端口等待就绪,且离房时可取消。禁止每种能力各自无界积压请求来弥补没有统一就绪契约。
|
||||
|
||||
旧 mainSceneLoaded 的通知时点与“可以访问节点”的内部就绪点分开。兼容映射保留原 create/login/join 的调用语义,不用新名字掩盖顺序变化。
|
||||
|
||||
### 6.4 队列规则
|
||||
|
||||
队列只保存当前已认证、当前 generation 的房间业务消息,FIFO;不改变原有登录屏障规则。换服尚未建立目标会话的消息按连接意图处理。
|
||||
|
||||
容量与最长等待时间由平台配置来源显式给出,先通过基线和压力测试确定;消费方不使用随意 fallback。溢出或超时导致明确诊断和恢复,不丢最旧的游戏包继续假装成功。
|
||||
|
||||
踢出、断线、用户取消等控制信号可立即使 scope 失效,不等待场景/动画。没有服务器序列号时,不按 rpc 名或 payload 相等对玩法事件去重。
|
||||
|
||||
### 6.5 准备和开战
|
||||
|
||||
准备请求发出只更新本地 pending,不提前更改权威 ready。回复提交 ready 后产生成员事件;如包中携带 deskwar,再提交相应阶段并触发 started,原整包保留。
|
||||
|
||||
同步处理状态不等待动画。动画队列属于 View,断线/恢复可以取消旧动画并直接渲染当前状态。开始按钮的条件来自统一策略,不由各按钮、摇一摇与子游戏分别实现。
|
||||
|
||||
### 6.6 解散与结算
|
||||
|
||||
投票倒计时不宣布结果。服务端拒绝结束本轮投票;普通通过等待结果确认后传递结算;freeNow 按协议立即传递。没有 deskfree 时按已核实的退出路径结束房间。
|
||||
|
||||
有 deskfree 时保留游戏与必要玩家上下文直到结算结束。明确区分 round-finished、room-finished 和 result-view-closed;不允许一个模糊 gameOver 同时决定分享、清空状态和跳场景。
|
||||
|
||||
### 6.7 退出与换服
|
||||
|
||||
requestExit 是请求,不是本地 leave。平台按当前模式和阶段决定退出/申请解散等合法动作,等待对应响应。
|
||||
|
||||
换服保留既定 connection intent 和重发信封;解散换服可能仍需保留结算 scope。这里不能用统一“换服就销毁游戏”替代原协议路径。
|
||||
|
||||
scope 失效是立即、幂等的;任何晚到回调只能释放它自己的资源,不能发包、提交状态或挂载节点。
|
||||
|
||||
## 7. 能力、错误与资源
|
||||
|
||||
### 7.1 输入与模态框
|
||||
|
||||
数字/文本/倍率输入返回明确的 confirmed/cancelled 结果。取消原因区分用户取消、房间结束、请求被替代;真实失败则拒绝并提供原因。
|
||||
|
||||
每种单例输入面板明确声明冲突策略。默认设计为:同一 scope 的重叠请求拒绝,调用方显式结束前一个后再开新请求;若现有业务必须替换,则旧请求必须得到 cancelled,不能丢失回调。
|
||||
|
||||
不会因为 Promise 方便就把没有业务回复的发包动作包装成“等待成功”。
|
||||
|
||||
### 7.2 Scope
|
||||
|
||||
拥有订阅、定时器、原生回调、资源租约、界面实例和未完成输入。释放顺序:停止接收/提交业务 → 取消异步输入与动画 → 释放游戏模型和 View → 释放能力资源与订阅。使用逆序登记的清理栈,异常逐项收集,不因一个 disposer 抛错而跳过其余清理。
|
||||
|
||||
共享头像/音频缓存不属于单个房间;房间只持有可释放租约。缓存有容量限制和明确逐出策略,不能以“缓存”名义永久持有所有资源。
|
||||
|
||||
### 7.3 错误分类
|
||||
|
||||
**Layer612_Tips 专用于玩家业务提示。** 游戏和平台的业务拒绝、输入校验、操作结果及可操作的超时提示可以使用;代码异常、堆栈、协议/配置校验失败、资源导入/加载错误、模块未接入与调试信息只能进入诊断日志,不得直接显示 error.message。禁止为了显示技术错误而改用踢出面板;踢出面板仅处理真实业务踢出。业务提示与诊断端口必须分开命名、分别注入。
|
||||
|
||||
- 协议/状态不一致:阻止当前事务,显式失败,记录来源和字段。
|
||||
- 业务拒绝:展示服务端约定信息、解除 pending,不终止正常连接。
|
||||
- 用户取消:正常结果,不上报 fatal。
|
||||
- 展示资源失败:局部能力按明确定义处理;没有 fallback 定义时报告资源错误,不能影响无关网络逻辑。
|
||||
- 清理失败:继续清理其余项,保留原始异常链。
|
||||
|
||||
诊断包含 direction、route/rpc、连接代际、房间 generation、生命周期阶段和原始 cause。完整包日志由 debugIO 控制;对外提交的错误报告与生产日志需要独立的敏感字段处理,不复用完整调试包。
|
||||
|
||||
## 8. 性能策略
|
||||
|
||||
优先处理已证实的结构性成本:
|
||||
|
||||
1. 聊天使用本地稳定消息 ID 维护行,追加不重建旧行。ID 不发送到服务器。
|
||||
2. 头像按 URL 去重加载、共享纹理租约;行复用时验证当前 URL/generation,避免旧图片写到新玩家。
|
||||
3. 选择器按最小相关状态通知;同帧合并的是绘制,不是业务消息。
|
||||
4. 投票保留按座位复用;小座位列表不引入不必要的虚拟化。
|
||||
5. 长聊天历史在测量后决定可变行高虚拟列表;上翻历史不被新消息强制滚底。
|
||||
6. 倒计时按来源建立截止时间并按差值绘制;后台恢复后重算,不累计定时器减法误差。
|
||||
7. 不在每个 UI 消费点递归 clone;入口隔离后结构共享。大型 deskinfo 的所有权策略应单独测量验证。
|
||||
|
||||
基线指标:进入/恢复至可交互耗时、主线程 p95、帧时间、长任务、节点数、纹理数、聊天追加开销、重复进退后的活跃订阅/计时器。
|
||||
|
||||
绝对耗时目标由目标设备测量确定。本版本不编造性能数字;结构性目标可以立即验收:一条消息不重建已有行,重复头像不重复创建纹理,100 次进入/退出后作用域资源数量回到基线。
|
||||
|
||||
## 9. 回放隔离
|
||||
|
||||
回放使用独立 ReplayContext、玩家列表、视角、时钟和消息源。可以复用游戏模型/展示,但不能共享在线 RoomStore、认证身份或真实网络发送能力。
|
||||
|
||||
未声明回放能力的游戏无需实现空回调。回放是后续独立阶段,本阶段只保证 SDK 不以全局状态阻止未来隔离。
|
||||
|
||||
## 10. 子游戏接入流程
|
||||
|
||||
1. 创建游戏定义并关联唯一配置身份与资源。
|
||||
2. 实现 roomtype 编码、读取、座位规则和纯描述。
|
||||
3. 将不同进入来源和游戏协议映射到自身模型输入。
|
||||
4. 创建页提交自己的配置;房间 View 订阅模型,操作走绑定能力。
|
||||
5. 声明需要的可选能力,不填写无意义的示例返回值。
|
||||
6. 运行接入测试和 prefab 唯一性检查,再在 Cocos 中验证真实按钮→实际包。
|
||||
|
||||
接入验收的核心是第二款游戏加入时不修改平台核心、公共 UI 或公共协议分发器。开发者不需要了解 PlatformStartup 内部字段,也不需要手动复制 agentid/playerid/roomcode。
|
||||
|
||||
## 11. 兼容与迁移策略
|
||||
|
||||
保留现有网络、状态缓存和会话租约,逐条迁移。每条消息在同一时刻只由一套业务处理器负责;严禁双跑旧新处理器发送重复命令或产生两份状态。
|
||||
|
||||
旧接口只在过渡适配层存在,并记录移除条件。新游戏不能新增 Utl 依赖。资源操作通过 Cocos MCP,主工作区已有修改不自动 stash、不覆盖、不批量恢复。
|
||||
|
||||
迁移检查从现有 framework/games 扩展到公共 scripts/ui,唯一豁免为明确的 app 组合根。CI 同时检查静态 import、契约测试和资源装配。
|
||||
|
||||
## 12. 风险与设计取舍
|
||||
|
||||
接口分层增加初期代码量,但减少后续游戏接入修改面;通过少量能力分组控制抽象数量。
|
||||
|
||||
原流程存在不合理顺序和混合状态,不能逐行复制。保留协议与业务效果,对改变的生命周期时点明确记录兼容映射及测试。
|
||||
|
||||
扩大 Roomtype 的平台承载类型需要检查全部消费方,防止 UI 或存储再次假设 length/index。未知格式不得在平台描述或转为字符串显示为玩法规则。
|
||||
|
||||
等待队列只解决界面加载窗口,不是事件日志系统。缺少服务器序列号时不承诺精确一次业务投递;重连依赖服务器权威快照及原协议屏障。
|
||||
|
||||
## 13. 完成标准
|
||||
|
||||
- 两种不同配置格式的游戏通过接入套件;新游戏无需修改平台核心。
|
||||
- 创建、加入、登录恢复、各开战入口的来源与载荷不丢失。
|
||||
- 加载中取消/踢出/换服、旧回调晚到、结算后释放均有测试。
|
||||
- 公共核心无 cc/window,游戏无平台内部依赖;装配检查发现重复页面。
|
||||
- 房间命令不由游戏手工拼公共身份;roomtype 不被平台解释或转换。
|
||||
- 聊天与头像增量策略通过资源计数/真实交互验证。
|
||||
- 文档明确未实现玩法,不以空函数或单元测试替代真实重连显示。
|
||||
|
||||
## 14. 待实施阶段收敛的参数
|
||||
|
||||
队列容量/等待时间、缓存容量、性能目标设备和绝对耗时阈值,由 P0/P3 基线确定并写入单一配置来源。第一阶段按平台核心与契约优先安排;二七王完整玩法和回放另立范围,不隐含在此次重构中。
|
||||
@@ -0,0 +1,20 @@
|
||||
# 构建指南
|
||||
|
||||
返回[框架目录](../README.md)。本目录专门说明如何选择子游戏、配置版本、预览和导出 Web 包。
|
||||
|
||||
## 推荐阅读顺序
|
||||
|
||||
1. [单游戏构建操作](single-game-build.md):二七王与模板的完整构建步骤、Bundle 勾选、切换游戏、配置复用、产物检查和排错。
|
||||
2. [版本配置与身份来源](version-and-build.md):XML 字段、网页与原生身份、版本包、构建扩展和发布环境限制。
|
||||
3. [旧构建目录与脚手架说明](legacy-project-layout.md):识别旧独立工程流程,避免使用错误的构建命令。
|
||||
4. 新接入游戏先完成[子游戏接入指南](../integration/README.md)和[创建房间接入](../integration/create-room.md)。
|
||||
|
||||
## 当前构建流程
|
||||
|
||||
保存启动场景中的 gameKey → 核对游戏入口、版本 XML 和 Bundle 元数据 → 构建面板仅保留公共包与目标游戏包 → 使用独立输出目录构建 → 检查代码、资源、XML → 通过 HTTP 运行导出网页。
|
||||
|
||||
本目录说明的是同一个 YouleNexus 工程内选择一款子游戏。切换现有游戏不修改框架代码、框架资源或引擎源码,也不依赖构建后删除其他游戏目录。
|
||||
|
||||
## 当前边界
|
||||
|
||||
已支持按 gameKey 动态加载游戏入口与私有 prefab,以及自动输出原生兼容 XML。构建时在 Bundle 列表保留公共 resources、目标游戏和目标版本包;当前还未自动同步 gameKey 与构建勾选项。正式发布环境注入仍待完善。
|
||||
@@ -0,0 +1,29 @@
|
||||
# 旧独立子工程目录的作用与迁移
|
||||
|
||||
返回[构建指南](README.md)。当前操作见[单游戏构建](single-game-build.md)。旧目录核查与迁移日期:2026-09-08。
|
||||
|
||||
## 原来作用
|
||||
|
||||
顶层 cocoscreator_projects/games 是旧“每款游戏单独 Cocos 工程”脚手架预留的目录,与当前 YouleNexus/assets/games 完全不同。
|
||||
|
||||
旧 new-game 脚本会复制工程种子到该目录,创建 assets/game 代码骨架,并通过 assets/framework junction 共享宿主框架;setup-links 重建链接;build-game 将独立工程实体化到临时构建工作区,再调用 Cocos CLI。check-skin、check-cocos-version、bump-cocos 也保留对该旧目录的支持。
|
||||
|
||||
当前 Bundle 入口、运行时与 version.xml 构建扩展都使用 YouleNexus 内的子游戏,不依赖这个顶层目录。
|
||||
|
||||
## 实际内容与处理
|
||||
|
||||
删除前逐项核查:只有 .gitkeep 和三份二七王文档,无 package.json、assets、游戏代码、资源或 reparse/junction 链接。因此它实际上没有可构建的独立子工程。
|
||||
|
||||
三份文档已逐文件校验后迁入当前文档树:
|
||||
|
||||
- [创建房间接入](../../subgames/erqiwang/create-room-integration.md)
|
||||
- [玩法设计](../../subgames/erqiwang/design/design.md)
|
||||
- [玩法协议](../../subgames/erqiwang/protocol/packet_protocol.md)
|
||||
|
||||
迁移后删除 .gitkeep,再确认无遗留文件,删除空旧目录。真正使用的 YouleNexus/assets/games 未删除或移动。
|
||||
|
||||
## 剩余旧工具
|
||||
|
||||
本次没有删除旧脚手架脚本、模板或测试,也没有把它们冒充成当前架构的工具。旧 new-game 命令仍可能重新创建顶层目录,不能用于新接入;旧 build-game 也不是当前单工程选择构建入口。
|
||||
|
||||
当前操作以[单游戏构建](single-game-build.md)为准。是否整体移除旧脚手架及相关 npm 入口属于后续工具清理,不影响当前同工程构建。
|
||||
@@ -0,0 +1,153 @@
|
||||
# 单游戏构建操作
|
||||
|
||||
返回[构建目录](README.md)。关联文档:[版本配置](version-and-build.md)、[子游戏接入](../integration/README.md)。核对日期:2026-09-09,Cocos Creator 3.8.8。
|
||||
|
||||
## 1. 当前能力与边界
|
||||
|
||||
一个 YouleNexus 工程可以包含多个子游戏。公共 app/framework/scripts 不静态导入具体子游戏,启动时按 `gameKey` 加载目标游戏 Bundle 的入口,并加载该游戏的创建页面和房间 prefab。
|
||||
|
||||
构建时使用 Creator 自身的 Bundle 选择能力,使未选游戏的代码和资源不进入产物。不修改引擎源码,不先构建全部游戏再手动删除。
|
||||
|
||||
**目前需要配合完成两项选择:保存启动场景的 gameKey;在构建面板勾选对应的 Bundle。两者尚未自动同步。** 修改 gameKey 不会自动改变构建面板,导入构建配置也不会修改场景 gameKey。
|
||||
|
||||
当前真实验收范围是二七王 Web Mobile:产物无模板游戏实现及私有资源,导出网页进入登录页,二七王创建页与房间 prefab 可实例化,XML 版本正确。此结果不代表完整牌局、服务器重连或原生壳已重新验收。模板的同类步骤如下,但不能将独立实验工程的双向验证当作两款正式游戏均已完成导出运行验收。
|
||||
|
||||
## 2. 一次性资源准备
|
||||
|
||||
以下位置均以 YouleNexus 工程为基准。资源元数据、prefab 和场景使用编辑器配置;AI 操作走 Cocos MCP,不手改序列化文件。
|
||||
|
||||
公共 `assets/resources` 包名为 `resources`,当前优先级 8,包含 `dev-config` 等公共运行资源。它不是任何一款子游戏的私有目录,当前开发构建必须保留。
|
||||
|
||||
二七王的配置:
|
||||
|
||||
- 游戏目录:`assets/games/erqiwang`;包名 `game-erqiwang`;优先级 **1**。
|
||||
- 版本目录:`assets/games/erqiwang/resources`;包名 `version-erqiwang`;优先级 **2**。
|
||||
- 入口类名:`GameEntry_erqiwang`;static definition 提供本游戏定义。
|
||||
- 版本资源:`assets/games/erqiwang/resources/erqiwang/version.xml`。
|
||||
|
||||
模板的配置:
|
||||
|
||||
- 游戏目录:`assets/games/template`;包名 `game-Game_Surface_3`;优先级 **1**。
|
||||
- 版本目录:`assets/games/template/resources`;包名 `version-Game_Surface_3`;优先级 **2**。
|
||||
- 入口类名:`GameEntry_Game_Surface_3`。
|
||||
- 版本资源:`assets/games/template/resources/Game_Surface_3/version.xml`。
|
||||
|
||||
**版本包优先级必须高于游戏根包。** 两者相同时,嵌套 XML 可能被父包收走,产生“版本包存在,但包内没有 version”的错误。修正元数据归属,不增加另一条运行时加载路径。
|
||||
|
||||
新增或移动脚本后,确认编辑器完成导入,资源数据库能查到脚本且已生成 `.meta`。磁盘上存在 GameEntry.ts,并不等于构建已能包含它。
|
||||
|
||||
## 3. 构建二七王 Web Mobile
|
||||
|
||||
1. 打开 YouleNexus 的 `assets/scenes/PlatformStartup.scene`。
|
||||
2. 找到 `SubgameAssets`,设置 `gameKey = erqiwang`,保留公共 `roomScene = TemplateRoom.scene`,保存场景。不要再绑定 createRoom、room;这两个 prefab 由目标入口自动加载。
|
||||
3. 核对二七王 XML;版本字段与原生读取约定见[版本配置](version-and-build.md)。
|
||||
4. 在项目扩展管理中启用 **Youle Version Manifest**。
|
||||
5. 打开“项目 → 构建发布”,选择 **Web Mobile**,启动场景选择 **PlatformStartup**。
|
||||
6. 参与构建的场景包含 PlatformStartup、PlatformLoading、PlatformLogin、PlatformLobby、TemplateRoom。TemplateRoom 是公共空白承载场景,名称包含 Template 不代表打入模板玩法。
|
||||
7. 在 Bundles 取消全选,选择 `resources`、`game-erqiwang`、`version-erqiwang`。`main`、`internal` 为正常构建自动保留的公共包。排除 `game-Game_Surface_3`、`version-Game_Surface_3` 以及所有其他游戏的包。
|
||||
8. 当前工程采用 debug/local 配置,保留 Debug。取消 Debug 不会自动切换生产配置,详见[开发与发布环境](version-and-build.md#6-开发构建与正式发布的区别)。
|
||||
9. 使用新的独立输出目录,例如 `build/erqiwang-web-check`。在未验证跨游戏增量清理前,不把另一款游戏的旧产物目录直接用作发布目录。
|
||||
10. 执行构建。构建期间不要修改启动场景、XML 和相关 Bundle 元数据。
|
||||
11. 确认构建成功,且日志出现 `[youle-version-manifest] erqiwang: .../version.xml`。然后执行下文产物检查与 HTTP 运行验证。
|
||||
|
||||
无需更改 defaults.ts、公共注册表、框架 prefab 或引擎安装目录。
|
||||
|
||||
## 4. 切换到模板或新增游戏
|
||||
|
||||
构建模板时,把启动场景 `gameKey` 改为 **Game_Surface_3** 并保存;保留公共场景与 `resources`,选择 `game-Game_Surface_3` 和 `version-Game_Surface_3`,排除二七王两个包,使用新的模板输出目录。
|
||||
|
||||
模板目录名是 template,但 gameKey 是 Game_Surface_3。不要把目录名直接当作 gameKey;游戏入口名、Bundle 名和版本资源路径必须一致。
|
||||
|
||||
新游戏先按[接入指南](../integration/README.md)在自己的目录中提供定义、入口、prefab 和 XML。完成后,构建步骤相同:选择 `<gameKey>`,保留 `game-<gameKey>`、`version-<gameKey>` 及公共包,排除所有其他游戏包。构建选择属于应用配置,不是在框架代码中增加游戏分支。
|
||||
|
||||
## 5. 保存与复用构建配置
|
||||
|
||||
在构建面板配置好平台、场景、Bundle 和输出位置后,使用 **Export** 保存配置;以后用 **Import** 恢复。建议每款游戏保留独立的构建配置,使用时仍核对启动场景已保存的 gameKey。
|
||||
|
||||
下列仅展示二七王配置的 Bundle 部分,**不是完整可导入文件**;其余字段由当前 Creator 构建面板导出,不手工拼凑启动场景 UUID。
|
||||
|
||||
```json
|
||||
{
|
||||
"bundleConfigs": [
|
||||
{"root": "db://assets/resources", "name": "resources", "output": true},
|
||||
{"root": "db://assets/games/erqiwang", "name": "game-erqiwang", "output": true},
|
||||
{"root": "db://assets/games/erqiwang/resources", "name": "version-erqiwang", "output": true},
|
||||
{"root": "db://assets/games/template", "name": "game-Game_Surface_3", "output": false},
|
||||
{"root": "db://assets/games/template/resources", "name": "version-Game_Surface_3", "output": false}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`root` 使用 **db:// 资源目录路径,不使用 UUID**。构建工具应在提交构建任务时提供选择结果;当前版本 XML 扩展不负责修改 Bundle 勾选项,也不保证发现所有代码或资源泄漏。
|
||||
|
||||
当前 `npm run build-game` 对应旧的独立子工程实体化脚本,不是上述同工程 Bundle 构建的一键入口。参见[历史脚手架说明](legacy-project-layout.md)。
|
||||
|
||||
## 6. 检查实际产物
|
||||
|
||||
二七王构建的关键目录应为:
|
||||
|
||||
```text
|
||||
<输出目录>/
|
||||
index.html
|
||||
version.xml
|
||||
assets/
|
||||
internal/
|
||||
main/
|
||||
resources/
|
||||
game-erqiwang/
|
||||
version-erqiwang/
|
||||
src/
|
||||
settings.json
|
||||
```
|
||||
|
||||
还会有引擎代码、样式等公共文件。检查以下内容:
|
||||
|
||||
- `assets` 不存在 game-Game_Surface_3、version-Game_Surface_3 或其他未选游戏目录。
|
||||
- `src/settings.json` 的 assets.projectBundles 只列出当前公共包与二七王两个包;数组顺序不是验收条件。
|
||||
- 检查所有生成 JS,不能出现 createTemplateGame、TemplateCreateRoomView、TemplateRoomView、GameEntry_Game_Surface_3 等模板实现。仅删除 Bundle 文件夹不能清除已混入公共脚本的代码。
|
||||
- 版本包的资源路径包含 `erqiwang/version`,不能只检查版本包文件夹是否存在。
|
||||
- 输出根目录 version.xml 与源 XML 字节或 SHA256 一致。保持原生兼容文件名,不改成 gameKey.xml。
|
||||
|
||||
在 cocoscreator_projects 目录使用 PowerShell 检查某次输出,按实际目录修改第一行:
|
||||
|
||||
```powershell
|
||||
$webOutput = Resolve-Path 'YouleNexus/build/erqiwang-web-check'
|
||||
Get-ChildItem (Join-Path $webOutput 'assets') -Directory | Select-Object -ExpandProperty Name
|
||||
$settings = Get-Content (Join-Path $webOutput 'src/settings.json') -Raw | ConvertFrom-Json
|
||||
$settings.assets.projectBundles
|
||||
Get-FileHash 'YouleNexus/assets/games/erqiwang/resources/erqiwang/version.xml'
|
||||
Get-FileHash (Join-Path $webOutput 'version.xml')
|
||||
rg -n 'createTemplateGame|TemplateCreateRoomView|TemplateRoomView|GameEntry_Game_Surface_3' $webOutput -g '*.js'
|
||||
```
|
||||
|
||||
最后一条没有匹配时,rg 返回码为 1,表示未检出这些标记。标记扫描只是辅助检查,不代替依赖边界和实际运行验证。
|
||||
|
||||
## 7. 运行与回归验证
|
||||
|
||||
通过 HTTP 服务或 Creator 构建预览打开导出目录,不用 file:// 双击 index.html。查看浏览器 Network 和控制台:
|
||||
|
||||
1. 只请求公共包、game-erqiwang、version-erqiwang,没有未选游戏包请求或 404。
|
||||
2. 进入登录页,版本来自目标 XML,启动没有 terminal/fatal 错误。
|
||||
3. 成功登录后,验证创建页与房间视图是二七王;验证创建房间、服务器拒绝和重连等真实业务路径。这一步需要测试账号与运行中的服务器,本次解耦验证没有代替完整牌局回归。
|
||||
|
||||
源码检查从 cocoscreator_projects 运行:
|
||||
|
||||
```powershell
|
||||
node scripts/check-import-boundaries.mjs
|
||||
node scripts/check-room-assets.mjs
|
||||
node --experimental-transform-types --test framework-tests/architecture/subgame-entry.test.ts
|
||||
node --test scripts/test/version-manifest-build.test.mjs
|
||||
node node_modules/typescript/bin/tsc -p tsconfig.framework.json --noEmit
|
||||
```
|
||||
|
||||
类型和源码测试通过不等于产物通过;正式发布以本次实际生成的目录和运行结果为准。
|
||||
|
||||
## 8. 常见故障
|
||||
|
||||
- **Missing subgame entry**:核对 gameKey、入口 ccclass 名、游戏包勾选及 GameEntry.ts 的编辑器导入状态。不要添加模板回退入口。
|
||||
- **Load assets/game-.../index.js failed**:检查目标游戏包是否进入构建、部署路径是否完整;若只在编辑器场景进程发生,用浏览器预览或导出包确认,不能直接当成浏览器运行结果。
|
||||
- **Bundle version-... doesn't contain .../version**:检查嵌套包优先级,确认 XML 实际归属版本包,而非父游戏包。
|
||||
- **本地配置读取失败,dev-config 路径不能解析**:检查公共 resources 是否被排除,同时核对当前是否采用 debug/local 配置。
|
||||
- **旧游戏仍进入产物**:检查 Bundle 是否全选、公共代码是否静态导入游戏、公共 prefab/场景是否引用私有游戏脚本或资源,以及输出目录是否来自旧构建。
|
||||
- **切换 gameKey 后仍请求旧游戏**:保存真正用于构建的启动场景,重新构建;检查浏览器缓存或部署缓存,不能只改编辑器中未保存的 Inspector。
|
||||
- **输出缺少根 version.xml**:确认项目扩展启用并完成构建,查看具体扩展错误。扩展失败的产物不要通过手动补文件冒充成功。
|
||||
@@ -0,0 +1,95 @@
|
||||
# 版本配置与身份来源
|
||||
|
||||
返回[构建目录](README.md)。具体操作见[单游戏构建](single-game-build.md)。本文维护版本字段、身份来源及 XML 构建扩展的职责。
|
||||
|
||||
## 1. version.xml 是身份和版本唯一配置文件
|
||||
|
||||
源文件在各子游戏目录中:二七王为 `assets/games/erqiwang/resources/erqiwang/version.xml`,模板为 `assets/games/template/resources/Game_Surface_3/version.xml`。路径相对于 YouleNexus,本文不复制第二份配置。
|
||||
|
||||
结构保持原生既有格式:XML 声明、game 根节点,以及 agent/game/channel/version 子节点。
|
||||
|
||||
- agent@id:网页默认 agentid。
|
||||
- channel@id:网页默认 channelid。
|
||||
- 子节点 game@id:服务器 gameid。
|
||||
- version@value:数值 versionCode,发送到登录包的 version 字段。
|
||||
- version@name:字符串展示版本 version,登录页显示为 v 加该字符串。
|
||||
- agent/game/channel 的 name 属性保留供原生读取,必须显式提供。
|
||||
|
||||
网页和构建共用这一份源 XML。构建出的副本由工具产生,不手工维护,不反向当作源码编辑。
|
||||
|
||||
不要把展示字符串(例如 1.8)作为登录数值版本发送。通用协议部分早期类型描述与真实登录校验存在历史差异,数值约定及实包依据见[数据结构说明](../protocol/04-数据结构.md)。
|
||||
|
||||
## 2. 网页与原生身份来源
|
||||
|
||||
网页预览从选中游戏的 XML 生成启动身份。原生环境 agentid/channelid/marketid 继续通过既有 settings/uAgent_3 接口读取,缺失时显式报错;gameid 与版本仍来自 XML。
|
||||
|
||||
原 XML 没有 marketid。网页的唯一 marketid 配置在应用层 `build-identity.ts` 的 WEB_MARKET_ID,当前为 4。不要为此擅自改变原生 XML 格式。
|
||||
|
||||
现有启动解析器仍支持显式 URL agentid/channelid/marketid/version 调试覆盖;gameid 不接受 URL 覆盖。普通预览不传身份参数即可使用 XML。URL version 如使用,含义是数值协议版本,而不是展示版本。
|
||||
|
||||
当前 XML 保留当前开发注册身份和 versionCode=1;展示版本二七王为 1.8、模板为 1.1。正式部署要在这份源 XML 中核对真实发布身份与版本,不照搬旧工程过期注册值。
|
||||
|
||||
## 3. 版本资源包设置
|
||||
|
||||
每个游戏的 resources 文件夹在编辑器中配置 Asset Bundle:
|
||||
|
||||
版本 Bundle 优先级为 2,游戏根目录 `game-<gameKey>` Bundle 优先级为 1。不要使用相同优先级,否则嵌套 XML 可能归入父包,版本包变为空包。
|
||||
|
||||
- 二七王目录 `assets/games/erqiwang/resources`:bundleName 为 `version-erqiwang`。
|
||||
- 模板目录 `assets/games/template/resources`:bundleName 为 `version-Game_Surface_3`。
|
||||
|
||||
运行时加载 `version-<gameKey>`,再加载包内 `<gameKey>/version` TextAsset。嵌套的 resources 目录不会自动进入全局 resources 包,必须完成上述配置。文件扩展名仍是 .xml,加载路径不带扩展名。
|
||||
|
||||
game-config.ts 只定位资源,不保存第二份 gameid 或版本。包名、路径和已保存的场景 gameKey 不一致时应修正来源,不能通过运行时备用路径掩盖。
|
||||
|
||||
## 4. 构建操作入口
|
||||
|
||||
按[单游戏构建操作](single-game-build.md)选择 gameKey、场景和 Bundle,并校验实际输出。游戏包与版本包必须配对选择,同时保留公共 resources。
|
||||
|
||||
Web Mobile 已做真实构建验证;Web Desktop 注册同一 hook 并通过接口测试,但不把这一点当作所有平台都完成了真实验收。
|
||||
|
||||
## 5. 构建扩展做了什么
|
||||
|
||||
项目扩展名为 **Youle Version Manifest**,实现目录为 `extensions/youle-version-manifest`。新编辑器环境需要确认已启用。
|
||||
|
||||
扩展读取构建任务真正的 startScene,而不是猜测当前编辑器正在显示哪个场景。它查找场景中唯一且启用的 SubgameAssets,然后在各游戏目录中查找唯一 `resources/<gameKey>/version.xml`。
|
||||
|
||||
构建前验证 XML、版本资源包元数据并保留快照。构建后验证启动场景未变、所选版本 bundle 确实包含在产物、源 XML/场景/元数据未变,再把 XML 字节原样写到实际输出根目录。
|
||||
|
||||
无效 XML、重复来源、磁盘场景中的无效选择、缺失所选版本 bundle、构建中来源变化都会阻止构建。扩展只读取已保存场景,不能替用户保存或识别 Inspector 中尚未保存的游戏切换。
|
||||
|
||||
扩展不会自动勾选游戏包、自动排除其他游戏、检查版本包内所有资源路径或扫描所有生成代码。看到 XML 发布成功日志,仍需完成单游戏产物检查。失败产物不能当成可发布结果。
|
||||
|
||||
## 6. 开发构建与正式发布的区别
|
||||
|
||||
当前 `profiles.ts` 是 debug、本地配置、连接本机 127.0.0.1:3088。刚构建出的包不会因为 Cocos 取消 Debug 就自动切换正式服务器;本地配置加载器也不允许在非调试构建使用本地配置。工程的脚本 loose 设置保持 false,避免对标准集合遍历产生不正确的降级编译结果。
|
||||
|
||||
目前环境配置仍位于框架历史 profiles.ts,这是尚未完成的应用层配置迁移,不符合未来“发布时无需修改框架”的完整目标。不要把修改它当作每个子游戏接入的步骤。正式发布前,需要完成应用发布环境注入或作为独立框架任务迁移这处配置,并验证 remote/release 的真实来源。
|
||||
|
||||
## 7. 当前限制与后续构建方向
|
||||
|
||||
- gameKey 选择游戏入口和私有 prefab;roomScene 保持公共承载场景绑定。
|
||||
- XML 选择和根目录输出已经自动化。
|
||||
- 公共应用层已解除具体游戏静态导入。构建需显式保留公共 resources、目标游戏和目标版本 Bundle;gameKey 尚未自动同步构建面板勾选项。
|
||||
- 原有仓库 `scripts/build-game.mjs` 使用另外的子工程实体化流程,不是当前 YouleNexus 同工程游戏选择的一键入口;不要直接把它当成这里的构建命令。
|
||||
- 未来统一构建入口应在应用/工具层完成选择、资源装配、环境注入与产物裁剪,并验证导出包仅含目标游戏,不随游戏切换修改框架。
|
||||
|
||||
## 8. 验证命令与常见故障
|
||||
|
||||
从 cocoscreator_projects 目录运行:
|
||||
|
||||
```powershell
|
||||
node scripts/check-import-boundaries.mjs
|
||||
node scripts/check-room-assets.mjs
|
||||
node --test scripts/test/version-manifest-build.test.mjs
|
||||
node node_modules/typescript/bin/tsc -p tsconfig.framework.json --noEmit
|
||||
```
|
||||
|
||||
引擎运行验证脚本 `verify-version-xml-preview.mjs` 需通过 Cocos MCP 在 Game View 上下文执行,不能直接在普通 Node 中运行。它会尝试加载两款游戏,适用于包含两款游戏资源的开发预览,不适用于已排除其他游戏的单游戏发布包。单游戏产物按[导出运行验证](single-game-build.md#7-运行与回归验证)检查。
|
||||
|
||||
- `Missing subgame entry`:目标 Bundle 未注册 GameEntry_<gameKey>。
|
||||
- `Cannot load version-...`:版本目录未设为对应 bundle,或构建排除了它。
|
||||
- `Version resource does not match selected game`:game-config 中的路径与 gameKey 不一致。
|
||||
- 根目录没有 XML:确认构建扩展已启用、所选启动场景正确,并查看构建日志;不要手动复制文件后掩盖失败。
|
||||
- 切换游戏仍出现旧创建页面:检查本游戏 definition.resources 与 gameKey。
|
||||
- 登录包的版本错误:核对 XML value 及是否传了 URL version 调试参数,不把 XML name 发给服务器。
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
# 子游戏静态依赖解除实施计划
|
||||
|
||||
目标:公共框架与应用启动层不导入具体子游戏。沿用已经通过独立构建与导出网页验证的 Bundle 动态加载能力,不修改引擎或服务器。
|
||||
|
||||
1. 为入口解析补充失败测试,覆盖未知入口、键不匹配、资源越界;新增公共代码不得静态引用 games 的边界检查。
|
||||
2. 将游戏定义、聊天和菜单策略放入各自目录。每个游戏通过 `GameEntry_<gameKey>` 导出定义;公共入口只依赖 SDK 契约。
|
||||
3. SubgameAssets 按 gameKey 加载 `game-<gameKey>`,完成入口解析、私有 prefab 加载后才允许启动平台。版本仍使用各自 `version-<gameKey>` Bundle,XML 保持唯一身份来源。
|
||||
4. 使用编辑器 API 将游戏根目录设为 Bundle,将模板创建房间 prefab 移到模板游戏目录;清除启动场景的私有 prefab 引用,保留公共房间场景。
|
||||
5. 更新静态资源检查、测试夹具与接入说明。测试所有既有行为及失败边界;编辑器导入后验证资源引用和真实 Web 构建运行。
|
||||
|
||||
边界:本次解除运行时依赖;不更改房间协议、玩法与原生接口。构建选择器的自动化单独围绕现有 gameKey 实现,不引入第二份身份配置。
|
||||
@@ -0,0 +1,141 @@
|
||||
# 子游戏接入与目录约定
|
||||
|
||||
返回[文档首页](../../README.md)。后续阅读:[创建房间](create-room.md)、[版本与构建](../build/version-and-build.md)。
|
||||
|
||||
## 1. gameKey、目录、gameid、route 分别是什么
|
||||
|
||||
`gameKey` 是子游戏入口和 Bundle 的选择键,负责选择游戏实现和版本资源。目录只是文件位置。`gameid` 是服务器游戏身份,由 XML 提供。`route` 是游戏入口的路由标识,必须遵循实际消息契约,不能因重命名目录而修改。
|
||||
|
||||
当前二七王:
|
||||
|
||||
- 注册键和 definition.key:`erqiwang`。
|
||||
- 源码目录:`assets/games/erqiwang`。
|
||||
- gameid:读取该游戏 XML 的 `game/game@id`,不是字符串 `erqiwang`。
|
||||
- 当前 definition.route:`erqiwang`;不要因此把平台创建房间请求的 route 改为 erqiwang,该请求仍是 `agent/create_room`。
|
||||
|
||||
当前模板:
|
||||
|
||||
- 注册键和 definition.key:`Game_Surface_3`。
|
||||
- 源码目录:`assets/games/template`。
|
||||
- 版本路径仍使用 `Game_Surface_3`,不是 template。
|
||||
|
||||
两者通过子游戏入口定义和 Bundle 名称建立关联,所以目录和 gameKey 目前允许不同。不能只把模板 gameKey 改成 template;现有注册项、资源路径与 bundle 名称不会自动跟随。
|
||||
|
||||
新游戏建议统一 `目录名 = gameKey = definition.key`。这是前端命名约定,不影响服务器 gameid 或协议路由。当前构建扩展允许 gameKey 使用英文字母、数字、下划线、连字符。
|
||||
|
||||
## 2. 建议目录
|
||||
|
||||
以下是新游戏 mygame 的组织示例,不表示该游戏已经存在:
|
||||
|
||||
```text
|
||||
YouleNexus/assets/
|
||||
app/composition/
|
||||
game-definitions.ts # 通用入口解析,不引用具体游戏
|
||||
SubgameAssets.ts # 动态加载目标游戏及版本
|
||||
games/mygame/
|
||||
game-config.ts # 定位版本 XML 和私有资源根
|
||||
game-definition.ts # 本游戏定义
|
||||
GameEntry.ts # 注册 GameEntry_mygame
|
||||
mygame-game.ts # GameBinding / GameEntry / GameModule
|
||||
mygame-room-rules.ts # 自己解释和生成 roomtype
|
||||
room-chat-config.ts # 自己的聊天配置
|
||||
MyGameCreateRoomView.ts
|
||||
MyGameCreateRoom.prefab
|
||||
MyGameRoomView.ts
|
||||
MyGameRoom.prefab
|
||||
resources/ # 编辑器设置为 version-mygame bundle
|
||||
mygame/version.xml # 身份和版本唯一来源
|
||||
```
|
||||
|
||||
框架中不添加 mygame 的游戏 ID、玩法开关、节点查找路径或特殊 RPC 分支。需要扩展公共契约时,应作为独立框架能力设计,而不是每接入一个游戏就改框架。
|
||||
|
||||
## 3. 配置与应用注册
|
||||
|
||||
游戏配置可参照 [erqiwang/game-config.ts](../sdk/contracts.md):
|
||||
|
||||
```ts
|
||||
export const gameConfig = Object.freeze({
|
||||
versionResource: 'mygame/version',
|
||||
assetRoot: 'games/mygame',
|
||||
});
|
||||
```
|
||||
|
||||
当前运行时要求 versionResource 精确等于 `<gameKey>/version`。XML 和资源包准备见[构建文档](../build/version-and-build.md)。
|
||||
|
||||
在自己的 `assets/games/mygame/game-definition.ts` 导入本游戏工厂、配置和聊天配置,导出只包含本游戏的 `GAME_DEFINITIONS`。公共应用层不添加注册项。需要提供的完整字段以 [GameDefinition](../sdk/contracts.md) 为准:
|
||||
|
||||
- `key`:与注册键及场景 gameKey 一致。
|
||||
- `route`:核对既有游戏路由,不自动从目录推导。
|
||||
- `config`:自己的版本资源定位配置。
|
||||
- `createGame(gameId)`:接收已解析的游戏身份,返回 GameBinding,不另写一份 gameid。
|
||||
- `roomMenu`:显式提供 mainSceneButton、vipInfinite 两个布尔策略。
|
||||
- `chat`:按 [RoomChatConfig](../sdk/contracts.md) 提供该游戏聊天配置。
|
||||
- `createPageClass`、`roomViewClass`:相应 Cocos 组件类名,注意 `@ccclass` 名称。
|
||||
- `resources.createRoom`、`resources.room`、`resources.roomScene`:资源路径描述,参照已有注册项。
|
||||
|
||||
**createRoom 和 room 按定义从目标游戏 Bundle 自动加载,不再绑定到启动场景。** 创建页面运行时是通过 `CreateRoomPage` 基类取组件;仅填写 createPageClass 不能替代继承该基类及在游戏 prefab 挂载组件。房间视图按 roomViewClass 取组件。
|
||||
|
||||
## 4. 实现房间入口和生命周期
|
||||
|
||||
参考 [erqiwang-game.ts](../sdk/contracts.md),但不要把其中尚未实现的玩法处理当成完整游戏模板。
|
||||
|
||||
`GameBinding` 提供 entry、mount(view)、restoredSnapshot、roomProjection。`GameEntry` 提供 key、gameId、route,以及:
|
||||
|
||||
- `resolveSeatCount(roomtype)`:解析真实服务器返回的本游戏 roomtype,返回座位数;输入非法时显式报错。平台不负责解释玩法字段。
|
||||
- `createModule()`:为房间创建独立的 GameModule,不在模块间共享上一房间的可变状态。
|
||||
|
||||
[GameModule](../sdk/contracts.md) 生命周期:
|
||||
|
||||
- `attach(host)`:接收平台能力并订阅状态。
|
||||
- `handlePlatformEvent(event)`:处理平台转交的房间事件。
|
||||
- `handleGameMessage(message)`:按本游戏协议处理玩法消息。
|
||||
- `restore(deskinfo)`:按本游戏快照契约恢复状态和视图。只保存原始数据不等于完成重连恢复。
|
||||
- `dispose()`:解除订阅、定时器与界面引用,结束后不继续发送或响应旧会话操作。
|
||||
|
||||
通过 `requireRoomCapabilities(host)` 使用 room/messages/ui/scope 能力,不从游戏中访问 PlatformRuntime、平台 Store、WebSocket 或另一款游戏的模块。
|
||||
|
||||
- room:快照、订阅、座位映射、准备、退出、解散。
|
||||
- messages:发送符合本游戏协议的消息。
|
||||
- ui:业务提示、具有所有权的 loading、数字输入及倍数选择;输入取消是正常结果,应处理取消分支。
|
||||
- scope:注册房间结束时的清理逻辑。
|
||||
|
||||
完整接口见 [game-capabilities.ts](../sdk/contracts.md)。当前未提供可用的房间音频所有权能力,不假设它存在。
|
||||
|
||||
## 5. 编辑器绑定与选中游戏
|
||||
|
||||
打开 `assets/scenes/PlatformStartup.scene`,在挂有 `SubgameAssets` 的节点配置:
|
||||
|
||||
1. `gameKey`:目标游戏键。
|
||||
2. 创建页面 prefab 在本游戏定义中声明,根节点挂自己的 CreateRoomPage 子类。
|
||||
3. 房间 prefab 在本游戏定义中声明,根节点挂匹配 roomViewClass 的组件。
|
||||
4. `roomScene`:用于承载房间的 SceneAsset。
|
||||
5. 保存场景;构建扩展读取磁盘中已保存的场景,而不是未保存的 Inspector 状态。
|
||||
|
||||
两款现有游戏当前都使用 `TemplateRoom.scene` 作为承载场景,场景名称不意味着二七王使用模板的玩法逻辑。现有二七王房间视图继承 [CommonRoomViewBase](../sdk/contracts.md),公共控制通过已有属性绑定。
|
||||
|
||||
## 6. 接入验收
|
||||
|
||||
- 注册键、definition.key、gameKey、版本资源路径与 bundle 名称一致。
|
||||
- 创建页面重复打开/关闭不重复注册监听;忙碌时不能重复提交。
|
||||
- 抓取实际 create_room 出包确认 roomtype,而不仅检查 UI 选项。
|
||||
- 验证成功、服务器拒绝、重连已有房间、退出后再进房间。
|
||||
- 游戏解析自己真实的 deskinfo;公共平台不转换子游戏快照格式。
|
||||
- 切换至另一款游戏并验证其身份、创建页面与版本 XML,避免留下另一款游戏的绑定。
|
||||
- 控制台无异常,业务提示不承载调试输出,房间释放后无旧订阅和迟到回调。
|
||||
|
||||
当前二七王 handleGameMessage 与解散结算仍有显式“尚未接入”分支,房间视图标识“玩法尚未接入”并隐藏准备按钮。这些是后续子游戏工作,不应宣称创建房间成功就已完成牌局迁移。
|
||||
|
||||
## 7. Bundle 入口
|
||||
|
||||
游戏根目录设置为 `game-<gameKey>` Bundle,优先级 1;版本子目录保持 `version-<gameKey>` Bundle,优先级 2。版本包必须比游戏包优先,保证 XML 属于版本包,不被父目录包提前收走。
|
||||
|
||||
```ts
|
||||
import {_decorator} from 'cc';
|
||||
import {GAME_DEFINITIONS} from './game-definition.ts';
|
||||
@_decorator.ccclass('GameEntry_mygame')
|
||||
export class MyGameEntry {
|
||||
static readonly definition = GAME_DEFINITIONS.mygame;
|
||||
}
|
||||
```
|
||||
|
||||
启动等待目标 Bundle 加载完成,通过公开类注册 API 读取入口定义,然后加载私有 prefab 和版本 XML。入口缺失、键不匹配、资源越界或加载失败均显式报错,不回退到其他游戏。新增游戏不修改 app/framework/scripts 或公共资源。
|
||||
@@ -0,0 +1,130 @@
|
||||
# 子游戏创建房间接入
|
||||
|
||||
返回[接入指南](README.md)。本章对应原工程的“创建页面选择玩法 → create_room → 进入房间”流程。
|
||||
|
||||
## 1. 平台与子游戏的职责
|
||||
|
||||
子游戏负责创建页面布局、选项状态、房卡展示、已有配置回显、roomtype 编码和校验。平台负责弹窗生命周期、忙碌状态、统一发包、身份与设备环境注入、响应分发和房间进入。
|
||||
|
||||
创建页面只调用 `context.submit(roomtype)`,不自行拼接 agentid/playerid/gameid/ip/location,不直接创建 WebSocket,不自行调用 create_room RPC。
|
||||
|
||||
平台将 roomtype 作为不透明配置保存和转发。它会验证可用的数据形态,但不会把所有游戏限制成字符串、数组或同一长度。当前模板使用数组,二七王新请求使用 11 位字符串。禁止对通用 roomtype 使用 String()/JSON.stringify() 进行协议类型转换。
|
||||
|
||||
## 2. 页面契约
|
||||
|
||||
接口定义:[create-room-page.ts](../sdk/contracts.md)。引擎组件基类:[CreateRoomPage.ts](../sdk/contracts.md)。
|
||||
|
||||
```ts
|
||||
interface CreateRoomPageContext {
|
||||
readonly previousRoomtype: unknown;
|
||||
submit(roomtype: Roomtype): void;
|
||||
cancel(): void;
|
||||
reportError(message: string): void;
|
||||
}
|
||||
|
||||
interface CreateRoomPagePort {
|
||||
open(context: CreateRoomPageContext): void;
|
||||
setBusy(busy: boolean): void;
|
||||
close(): void;
|
||||
}
|
||||
```
|
||||
|
||||
该代码用于说明现有契约;实际开发应直接 import SDK 类型,不复制一份接口到子游戏。
|
||||
|
||||
自己的组件应继承 CreateRoomPage,并挂在创建 prefab 的根节点。现有参考:
|
||||
|
||||
- [ErqiwangCreateRoomView.ts](../sdk/contracts.md)
|
||||
- [TemplateCreateRoomView.ts](../sdk/contracts.md)
|
||||
|
||||
### open(context)
|
||||
|
||||
1. 先清理前一次打开遗留的监听和 context。
|
||||
2. 验证必要的编辑器绑定完整,缺少控件时显式报错。
|
||||
3. 由本游戏规则对象解释 previousRoomtype,恢复 UI 选中状态。
|
||||
4. 绑定选项、提交和取消操作,保存解除监听的函数。
|
||||
5. 更新消费展示和按钮状态。
|
||||
|
||||
`previousRoomtype === null` 表示没有历史配置,子游戏规则来源可以在此定义自己的首次创建默认值。已有但非法的历史配置不应悄悄替换为默认值;应明确暴露或实现有协议依据的版本迁移。
|
||||
|
||||
当前平台从 localStorage 的 `tsgame_roomtype_<gameid><agentid>` 读取历史 JSON 的 data,交给页面。页面不必重新读取本地存储,也不要按另一套 key 再维护一份历史。
|
||||
|
||||
### setBusy(busy)
|
||||
|
||||
平台请求期间禁用提交和会改变 roomtype 的选项,避免双击重复创建。取消/关闭策略按业务处理;二七王当前保留关闭入口。忙碌状态由平台驱动,不能以固定延时假装请求结束。
|
||||
|
||||
### close() / onDestroy()
|
||||
|
||||
解除所有事件、清空 context,重复调用安全。组件 onDestroy 调用 close。异步回调应检查 context 或会话是否仍有效,关闭后不能继续 submit。
|
||||
|
||||
`context.reportError` 当前进入控制台诊断,不是给玩家看的提示接口。编码错误、缺少组件、堆栈等不得通过 Layer612_Tips 展示。
|
||||
|
||||
## 3. roomtype 放在独立规则对象中
|
||||
|
||||
建议将规则逻辑放在本游戏的 room-rules.ts,页面只负责从控件调用 select/build。规则对象应能在不启动 Cocos 的情况下测试:
|
||||
|
||||
- 无历史时的明确默认值。
|
||||
- 合法历史的解析和回显。
|
||||
- 非法值拒绝。
|
||||
- UI 选项到线协议字段的映射。
|
||||
- 房卡文案所需计算;最终扣卡仍以服务器为准。
|
||||
- 新请求编码,以及服务器回包/重连输入的解析。
|
||||
|
||||
**请求编码与历史解析不一定采用相同的严格程度。** 必须以真实协议和服务端行为为依据,不能为了让旧数据通过而在平台增加通用兜底。
|
||||
|
||||
## 4. 二七王的具体参考
|
||||
|
||||
[ErqiwangRoomRules](../sdk/contracts.md) 将 UI 顺序映射到服务器位序。UI 顺序与线协议顺序不同,不可直接拼接各 Toggle 的排列顺序。
|
||||
|
||||
当前创建页面需要在编辑器序列化:
|
||||
|
||||
- 3 个 ToggleContainer。
|
||||
- 8 个 Toggle,以及对应 8 个选项 Label。
|
||||
- 1 个提交 Button、2 个关闭/取消 Button。
|
||||
- 1 个费用 Label。
|
||||
|
||||
前三组选项是局数、扣卡方式、查牌设置;后两个独立选项是傍王和爬坡。准确的位定义见[二七王玩法协议](../../subgames/erqiwang/protocol/packet_protocol.md),本指南不另维护一份位索引常量。
|
||||
|
||||
当前 build() 新请求输出:**11 位字符串,前 5 位为 0/1,后 6 位为 0**。例如所有开关取 0 时是字符串 `"00000000000"`。此例不是平台默认配置,也不能转换成数字 0。
|
||||
|
||||
已有/回包配置解析当前允许至少 5 位、前 5 位为 0/1的字符串;新请求仍固定输出 11 位。二七王座位数由自己的 resolveSeatCount 返回 3,平台不内置该人数。
|
||||
|
||||
## 5. 完整提交与响应链
|
||||
|
||||
```text
|
||||
大厅打开创建弹窗
|
||||
→ 从 SubgameAssets.createRoom 实例化 prefab
|
||||
→ 根节点取得 CreateRoomPage 组件
|
||||
→ page.open(context),回显 previousRoomtype
|
||||
→ 点击创建:本游戏 rules.build()
|
||||
→ context.submit(roomtype)
|
||||
→ 平台请求管理与 PlatformRuntime.createRoom
|
||||
→ 既有 agent/create_room RPC
|
||||
→ 服务器响应
|
||||
→ RuntimeSession 解析公共响应
|
||||
→ 本游戏 resolveSeatCount(真实 roomtype)
|
||||
→ 平台提交房间状态,进入房间会话并挂载视图
|
||||
```
|
||||
|
||||
发包信封保持 `app: youle`、`route: agent`、`rpc: create_room`。data 中的身份字段、ip、location 由平台按既有契约提供,roomtype 原样采用子游戏生成值。通用协议入口见[agent 协议 create_room](../protocol/02-协议-agent路由.md)。该文档早期数组描述针对模板,不代表二七王也应发送数组。
|
||||
|
||||
发送成功不代表创建成功。必须等待服务器返回:拒绝响应走平台业务拒绝流程;成功响应才进入房间。页面不要在点击提交时直接切换到房间场景。
|
||||
|
||||
## 6. 房间视图与重连接续
|
||||
|
||||
创建 prefab、房间 prefab 是不同资源。除了创建页面,还要完成应用注册中的工厂和 roomViewClass,在本游戏定义中声明 room 路径,在 SubgameAssets 保留公共 roomScene 绑定。
|
||||
|
||||
房间状态/准备/退出通过公共能力接口;玩法消息通过本游戏 GameModule;登录发现已有房间时,deskinfo 必须由本游戏 restore 解释。只验证 create_room 正常,不足以证明重连已接通。
|
||||
|
||||
不要在创建页面 open 中建立房间模块、订阅房间消息或提前发送准备指令;该页面处于入房之前。
|
||||
|
||||
## 7. 推荐验证顺序
|
||||
|
||||
1. 纯规则测试:选项组合、历史恢复、边界值、非法输入。
|
||||
2. 实际 prefab:字段绑定完整,重复打开关闭无重复监听,费用显示正确。
|
||||
3. 实际按钮提交:截获 context.submit 参数,确认类型与完整 roomtype 内容。二七王可覆盖全部 32 种开关组合。
|
||||
4. 平台出包:核对最终 agent/create_room 的类型和字段,不能只看规则单测。
|
||||
5. 成功与拒绝:忙碌状态正确释放,拒绝不进入房间,连续点击不重复发包。
|
||||
6. 房间接续:创建、加入、退出重进、登录重连、已有牌局 deskinfo 恢复。
|
||||
7. 资源释放:弹窗关闭及场景销毁后无事件、计时器和迟到提交。
|
||||
|
||||
实际服务器操作会创建房间或改变玩家状态,应使用明确的测试账号;纯规则和 prefab 验证可以通过注入 submit 回调完成,不需要真实发包。
|
||||
@@ -0,0 +1,234 @@
|
||||
# 00 · 框架架构设计(子游戏框架总览)
|
||||
|
||||
> 本章说明 `Game_Surface_3` 的**整体架构与设计思想**。它不是某一款游戏,而是一套
|
||||
> **「子游戏框架」**:平台壳(Surface)把登录、大厅、房间、网络、UI、社交等**通用能力**全部做好,
|
||||
> 一款具体游戏(SubGame)只需在固定的接入点里实现**自己的对局逻辑**。
|
||||
>
|
||||
> 理解这套分层,是用 CocosCreator 重写前端的前提——新前端应复刻"平台通用层 + 子游戏对局层"的边界,
|
||||
> 这样才能与服务器无缝对接(服务器只认协议,不关心前端用什么引擎)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 框架定位
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ 一个可发布的游戏 │
|
||||
│ │
|
||||
│ ┌─────────────────────────┐ ┌───────────────────────────┐ │
|
||||
│ │ Surface 平台壳 (00_) │ │ SubGame 子游戏 (01_) │ │
|
||||
│ │ 登录/大厅/房间/网络/UI │◄─►│ 仅实现「对局逻辑」 │ │
|
||||
│ │ 社交/支付/战绩/排行... │钩子│ 发牌/出牌/结算/界面 │ │
|
||||
│ └─────────────────────────┘ └───────────────────────────┘ │
|
||||
│ ▲ │
|
||||
│ │ 引擎回调分发 (gamemain.js) │
|
||||
│ ┌──────────────┴──────────────────────────────────────────┐ │
|
||||
│ │ gameabc 自研精灵引擎 (gameabc.min.js) + Spine 骨骼动画 │ │
|
||||
│ │ Canvas 渲染 / 触摸 / 定时器 / 资源加载 / WebSocket │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
└───────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- **平台壳**和**子游戏**用同一套代码骨架;换游戏时只替换 `01_SubGame/` 与美术布局数据。
|
||||
- 平台壳通过**钩子(Hook)**反向调用子游戏;子游戏通过调用平台 API(`Net.Send_*`、`GameUI.*`、`Desk`、`C_Player`、`set_self`)使用平台能力。
|
||||
|
||||
---
|
||||
|
||||
## 2. 四层结构
|
||||
|
||||
| 层 | 载体 | 职责 |
|
||||
|----|------|------|
|
||||
| **引擎层** | `gameabc.min.js`、`spine-canvas.js`、`SpineMgr.js` | Canvas 精灵渲染、触摸/绘制/定时/资源/网络底层回调、Spine 骨骼动画 |
|
||||
| **桥接层** | `gamemain.js`(`gameabc_face`) | 把引擎回调统一分发给上层三个消费者(GameUI / Game_Modify / gameCombat) |
|
||||
| **平台层** | `js/00_Surface/*`(12 个文件) | 登录、大厅、房间、解散、网络协议、玩家数据、UI、聊天、支付、战绩、排行、敏感词等 |
|
||||
| **子游戏层** | `js/01_SubGame/*`(3 个文件) | 子游戏配置、对局逻辑、自定义输入处理(接入点) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 引擎层:自研精灵(Sprite)系统
|
||||
|
||||
渲染不是 DOM,也不是常规游戏引擎,而是一套基于**精灵编号(spid) + 标签(tag) + 组(group)**的自研系统。
|
||||
美术界面在编辑器里排版后导出为 `output/gameabc_data.min.js`(精灵布局数据),运行时引擎据此渲染。
|
||||
|
||||
### 3.1 核心 API(子游戏与平台都用它操作界面)
|
||||
|
||||
| API | 作用 |
|
||||
|-----|------|
|
||||
| `set_self(spid, attr, value, ...)` | 设置某精灵的属性 |
|
||||
| `get_self(spid, attr, ...)` | 读取某精灵属性 |
|
||||
| `set_group(groupid, attr, value, ...)` | 对一组精灵批量操作(常用于整页显隐) |
|
||||
| `play_ani(...)` | 播放属性动画(位移/缩放/帧动画) |
|
||||
| `set_clip(...)` | 设置裁剪区域(滚动列表用) |
|
||||
| `ifast_addtospritefromspritecopy(fSpid, srcSpid, x, y, tag)` | 以模板精灵为原型,动态复制出带 tag 的子精灵(实现动态列表,如战绩/排行行项) |
|
||||
| `ifast_dllpritefromspritecopy(fSpid, tag)` | 删除动态复制的子精灵 |
|
||||
| `ifast_check_add(spid, x, y)` | 命中检测,返回被点中的子精灵 tag(动态列表点击) |
|
||||
| `ifast_mydrawbmp(...)` | 在 `gamemydraw` 回调里自绘位图(如勾选框的对勾) |
|
||||
|
||||
### 3.2 常见 attr 属性码(由源码用法归纳)
|
||||
|
||||
| attr | 含义 |
|
||||
|------|------|
|
||||
| 7 | 文本内容(`set_self(spid,7,"文字")`) |
|
||||
| 18 / 19 | x 坐标 / y 坐标 |
|
||||
| 20 / 21 | 宽 / 高 |
|
||||
| 37 | 显示/隐藏(1 显示 0 隐藏) |
|
||||
| 43 | 按钮状态/帧索引(多态按钮切换外观) |
|
||||
| 1 | 资源/图片绑定(`set_self(268,1,资源号)`,预加载/换图) |
|
||||
|
||||
> 这是平台与子游戏共享的"界面操作语言"。CocosCreator 重写时,这一层用 Cocos 的 Node/Sprite/Label/Widget 取代,
|
||||
> **不需要复刻 spid 体系**——只要最终把同样的协议数据展示出来即可。
|
||||
|
||||
---
|
||||
|
||||
## 4. 桥接层:`gamemain.js`(引擎 → 上层 的总分发)
|
||||
|
||||
引擎对象 `gameabc_face` 的所有回调都在此**广播**给三个消费者,顺序固定为 `GameUI → Game_Modify → gameCombat`:
|
||||
|
||||
| 引擎回调 | 时机 | 分发去向 |
|
||||
|----------|------|----------|
|
||||
| `gamestart(gameid)` | 引擎就绪 | **`Logic.AppStart()`**(应用总入口) |
|
||||
| `mousedown / mousedown_nomove / mouseup / mousemove` | 触摸 | `GameUI.utl*` + `Game_Modify.utl*`/`mouseup` + `gameCombat.utl*` |
|
||||
| `gamemydrawbegin / gamemydraw` | 每精灵绘制前/绘制 | 同上三方(子游戏在此自绘) |
|
||||
| `gamebegindraw / gameenddraw` | 每帧开始/结束 | `GameUI` |
|
||||
| `ontimer` | 定时器 | `GameUI.utlontimer` |
|
||||
| `ani_doend` | 动画结束 | `GameUI` + `gameCombat` |
|
||||
| `onloadurl` | 图片/资源加载完成 | `GameUI.onloadurl` |
|
||||
| `onresize` | 屏幕尺寸变化 | (预留) |
|
||||
| `tcpconnected/tcpmessage/tcpdisconnected/tcperror` | 引擎自带 TCP | **空**(实际网络走 `00_minhttp.js` 的 WebSocket 封装,不用引擎 TCP) |
|
||||
|
||||
> 关键:**子游戏不直接向引擎注册回调**,而是由 `gamemain.js` 转发。新前端可保留这种"统一事件总线 → 平台/子游戏"的模式。
|
||||
|
||||
---
|
||||
|
||||
## 5. 平台层模块清单(`js/00_Surface/`)
|
||||
|
||||
| 文件 | 模块 | 职责 |
|
||||
|------|------|------|
|
||||
| `02_Const.js` | `ConstVal` / `AppList` / `RouteList` / `RpcList` | 全局常量、协议名、UI 布局常量 |
|
||||
| `04_Data.js` | `GameData` | 全局运行时状态(服务器地址、连接状态、各种缓存) |
|
||||
| `08_Utl_Output.js` | `Utl` | 工具函数、本地存储、退出房间、部分发包 |
|
||||
| `07_Desk.js` | `Desk` | **牌桌/房间状态机** + 几乎所有房间类接收处理 |
|
||||
| `05_Func.js` | `Func` | 通用功能(HTTP、语音录制、截图分享、创建房间渲染等) |
|
||||
| `10_Game.js` | `Game` | 定位等少量游戏级杂项 |
|
||||
| `11_GameUI.js` | `GameUI` | **平台所有界面**(大厅/房间/聊天/战绩入口/弹窗/列表),最大文件 |
|
||||
| `12_Logic.js` | `Logic` | **应用生命周期 + 连接管理 + 消息分发 + 重连 + 桥接** |
|
||||
| `00_minhttp.js` | `min_tcp` / `min_http` | WebSocket 与 HTTP 底层封装 |
|
||||
| `09_Net.js` | `Net` | **协议收发层**(`Send_*` 发包 / `Net.<rpc>` 收包路由到 Desk/Player) |
|
||||
| `06_Player.js` | `Player` / `C_Player` | 玩家数据结构与玩家相关接收处理 |
|
||||
| `03_Banwords.js` | `banwords` | 敏感词库(聊天过滤) |
|
||||
|
||||
> 网络协议、数据结构的字段细节见 **01–04 章**。
|
||||
|
||||
---
|
||||
|
||||
## 6. 子游戏层(`js/01_SubGame/`)—— 这是开发一款游戏唯一要写的部分
|
||||
|
||||
| 文件 | 模块 | 职责 |
|
||||
|------|------|------|
|
||||
| `00_SubGame_Config.js` | `Game_Config` | 子游戏配置:房间人数、聊天/语音气泡位置、分享、客服、声音、调试开关等 |
|
||||
| `01_SubGame_modify.js` | `Game_Modify` / `gameCombat` | **子游戏实现**:输入处理、创建房间界面、战绩界面、对局渲染 |
|
||||
| `02_SubGame_Input.js` | `Game_Modify`(接口桩) / `gameHallImport` | 平台会回调、子游戏需实现的**钩子接口默认空实现** |
|
||||
|
||||
### 6.1 两类接入点
|
||||
|
||||
**A. 业务回调钩子**(平台 → 子游戏;定义在 `02_SubGame_Input.js`,子游戏按需重写):
|
||||
|
||||
| 钩子 | 调用时机(平台侧) |
|
||||
|------|-------------------|
|
||||
| `Game_Modify._ReceiveData(msg)` | 收到**游戏内协议包**(route 非 platform/agent/room)|
|
||||
| `Game_Modify.StartWar(msg)` | 开局(收到 makewar)|
|
||||
| `Game_Modify.Reconnect(deskinfo)` | 登录/进房响应**含 `deskinfo`** 时触发(还原牌局;模板为空实现,由子游戏自定义)|
|
||||
| `Game_Modify.DeskInfo(deskinfo)` | 进房响应含 `deskinfo` 但未自动开战(`deskwar` 假)时,传入 `deskinfo` |
|
||||
| `Game_Modify.createRoom / onCreateRoom` | 创建房间成功 |
|
||||
| `Game_Modify.myJoinRoom / playerJoinRoom(seat) / playerLeaveRoom(seat)` | 进/离房 |
|
||||
| `Game_Modify.playerOffline/Online(seat)` / `playerphonestate` | 在线/电话状态 |
|
||||
| `Game_Modify.onReady(seat)` / `changeSeat(s1,s2)` / `onSurrender(msg)` / `Free(msg)` | 准备/换座/投降/解散 |
|
||||
| `Game_Modify.updateScene / closeGameScene / onEnterMainScene / onExitMainScene / stopAllSounds` | 场景/声音管理 |
|
||||
| `Game_Modify.getRoomInfo/getFullRoomInfo/getStarLimit/getMult/getVideoByRoomType/getRoomMode...` | 平台**向子游戏取**房间展示信息(返回值)|
|
||||
|
||||
**B. 引擎事件钩子**(引擎 → 子游戏;定义在 `01_SubGame_modify.js`):
|
||||
|
||||
| 钩子 | 用途 |
|
||||
|------|------|
|
||||
| `Game_Modify.utlmousedown / mouseup / utlmousemove / utlmousedown_nomove` | 子游戏自己的触摸交互 |
|
||||
| `Game_Modify.gamemydraw / utlgamemydrawbegin` | 子游戏自绘 |
|
||||
|
||||
### 6.2 子游戏能调用的平台能力
|
||||
|
||||
- **发包**:`Net.Send_*(data)`(平台层 RPC,见 02/03 章);游戏内自定义包用 `Net._SendData(app, route, rpc, data)`
|
||||
- **房间/玩家状态**:`Desk.*`(roomcode、PlayerList、stage…)、`C_Player.*`、`Desk.GetPlayerBySeat(seat)`
|
||||
- **界面**:`set_self/get_self/set_group/play_ani` + `GameUI.*`(弹窗、提示、聊天气泡等)
|
||||
- **座位换算**:`Logic.ChangeToStatus(myseat, targetseat)`(把服务器绝对座位转成"以我为视角"的相对位置)
|
||||
- **配置**:`Game_Config.*`
|
||||
|
||||
---
|
||||
|
||||
## 7. 应用生命周期
|
||||
|
||||
```
|
||||
引擎就绪 → gameabc_face.gamestart() (gamemain.js)
|
||||
→ Logic.AppStart() (12_Logic.js:313)
|
||||
├─ 读取 URL/app 参数:渠道(channelid)、代理(agentid)、市场(marketid)、启动模式(LaunchMode)
|
||||
├─ Logic.setGameServer() → 仅解析配置(Game_Config.Debugger.gameserver);配置服 update_json 回调 ServerUrl_Succ → GameData.Server = urlserver
|
||||
├─ Desk.Create() → 建空牌桌(PlayerList)
|
||||
├─ C_Player = new Player(-1)
|
||||
├─ 加载敏感词、音频、定位(cityjson IP)
|
||||
└─ 连接:Logic.firstConnect/Connect → new WebSocket(ws://Server)
|
||||
→ onopen → Net.Send_login(...)(有条件:isLogin 重连,或读到 wxinfo cookie;见 01 章)
|
||||
→ Desk.login(资产+房间恢复) (见 04 章)
|
||||
├─ 有 roomcode:恢复房间;响应含 deskinfo → Game_Modify.Reconnect(deskinfo)
|
||||
└─ 无 roomcode:进大厅 GameUI.JumpMenuScene()
|
||||
大厅 → 创建/加入房间 → 房间(准备/开局) → 对局(游戏内协议) → 结算(over_game) → 回房间/大厅
|
||||
期间断线 → onclose → Logic.TryConnect()(轮换服务器)→ 重连后重发 login 恢复状态
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 游戏内对局协议的通道(子游戏自定义)
|
||||
|
||||
平台层协议(01–04 章)是固定的;**对局协议由子游戏定义**,复用同一条 WebSocket 与同一套信封:
|
||||
|
||||
- **下行**:服务器发 `route` 非 platform/agent/room 的包 → `Game_Modify._ReceiveData(msg)` → 子游戏按 `msg.rpc` 自行分发
|
||||
- **上行**:`Net._SendData("youle", "<游戏route>", "<游戏rpc>", {agentid,gameid,playerid,roomcode,seat, ...对局字段})`
|
||||
- **结算**:`over_game`;**重连快照**:登录/进房响应里的 `deskinfo`(结构由子游戏定义)
|
||||
|
||||
> `Game_Surface_3` 是**模板工程**:`02_SubGame_Input.js` 里 `_ReceiveData/StartWar/Reconnect` 等为**空实现**
|
||||
> (即未含具体玩法),所以本工程取不到某款游戏的对局字段。需从真实子游戏工程或抓包补全(见 05 章)。
|
||||
|
||||
### 真实数据结构样例(战绩,平台层 `get_player_grade1` 响应)
|
||||
|
||||
`01_SubGame_modify.js` 的 `gameCombat` 揭示了一个真实嵌套结构,可作为对局数据风格参考:
|
||||
```
|
||||
data = {
|
||||
asetcount: 总局数,
|
||||
gradeinfo: [ // 每场对局
|
||||
{
|
||||
overtime: "结束时间",
|
||||
roomcode: "房号",
|
||||
idx: 翻页索引,
|
||||
gameinfo1: { // 可能是 JSON 字符串,需二次 parse
|
||||
roundsum: 小局数,
|
||||
playerlist: [ [昵称, 总分], ... ],
|
||||
round: [ [ [座位, 该局分], ... ], ... ] // 每小局每人得分
|
||||
}
|
||||
}, ...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 给 CocosCreator 重写的架构映射
|
||||
|
||||
| 原框架 | CocosCreator 对应 | 说明 |
|
||||
|--------|-------------------|------|
|
||||
| gameabc 精灵引擎 + spid/tag/group | Cocos 场景/Node/Prefab/Label/Sprite | **不复刻 spid 体系**,只还原界面与协议数据展示 |
|
||||
| `gamemain.js` 事件总线 | Cocos 输入事件 + 自建 EventBus | 统一把交互/帧更新派发到 UI 与对局模块 |
|
||||
| `00_Surface/*` 平台层 | 一个"平台 SDK"模块(登录/大厅/房间/网络) | **按 01–04 章协议 1:1 实现**,是与服务器对接的关键 |
|
||||
| `Net._SendData` + 双层解包 | Cocos 的 WebSocket 封装 | 严格保持信封 `{app,route,rpc,data}`、双层包装、心跳识别(01 章)|
|
||||
| `Game_Modify` 钩子 | 对局模块对外接口 | 平台模块在相应时机回调对局模块 |
|
||||
| `Desk` / `C_Player` | 房间状态/玩家数据模型 | 按 04 章字段建模 |
|
||||
| `01_SubGame/*` | 具体游戏对局场景 | 自由用 Cocos 实现,只需吃平台给的数据、按对局协议收发 |
|
||||
|
||||
**核心结论**:与服务器"完美适配"只取决于**平台层协议(01–04 章)+ 对局协议(05 章,需补全)**的字节级一致;
|
||||
渲染引擎、UI 实现方式可以完全替换。把"平台 SDK"和"对局逻辑"在新前端里分清边界,就复刻了这套子游戏框架的精髓。
|
||||
@@ -0,0 +1,263 @@
|
||||
# 01 · 传输层与架构
|
||||
|
||||
> 本篇是「框架模板(00_Surface 平台层)↔ 服务器」协议规范的**传输层**部分:连接建立、握手、心跳、断线重连、服务器切换、收发包封装与过滤。
|
||||
> 双向完整(C→S 与 S→C)。不含子游戏对局包(见 05 章)。
|
||||
> 所有 `文件:行` 引用均按当前源码核对。源码 bug 用 🐛 标注;需后端抓包确认的用 ⚠️待服务器确认 标注。
|
||||
|
||||
---
|
||||
|
||||
## 1. 整体架构
|
||||
|
||||
```
|
||||
┌─────────────┐ 配置请求(HTTP) ┌──────────────────┐
|
||||
│ 新前端 │ ─────────────────► │ 配置服务器 │ 返回 update_json (身份层级配置)
|
||||
│ (Cocos) │ ◄───────────────── │ (gameserver 地址) │
|
||||
└─────────────┘ └──────────────────┘
|
||||
│ ws://ip:port (解析服务器配置后)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ 大厅服务器(agent) ←切换→ 房间服务器(room) │
|
||||
│ 登录/房间列表/排行/任务/支付 开局/聊天/解散/对局 │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- 同一时刻只保持**一条** WebSocket 连接(`Net.ws_tcp`)。
|
||||
- 大厅与房间是**不同的服务器地址**,通过 `connect_roomserver` / `connect_agentserver` 指令切换:
|
||||
服务器下发新地址 → 客户端把 `GameData.Server` 替换为新地址 → **关闭当前连接**(`Net.ws_tcp.close()`)→ `onclose` 触发后用新地址重连 → 重新登录/握手。
|
||||
- 路由空间 `platform / agent / room`(`02_Const.js:8-10`,字面值已确认与变量名一致)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 传输方式
|
||||
|
||||
源码支持两种传输,由 `ConstVal.netType` 决定(`02_Const.js:654`,默认值 `0`):
|
||||
|
||||
| netType | 方式 | 说明 |
|
||||
|---------|------|------|
|
||||
| `0` | **WebSocket**(默认,正式使用) | `Net.ws_tcp.send(JSON.stringify(msg))` |
|
||||
| `1` | HTTP Ajax(备用/降级) | `Func.AjaxHttp2(...)`,POST JSON |
|
||||
|
||||
> 大厅模式(`ConstVal.isGameHall==true`)启动时会被强制改成 `ConstVal.netType=1`(`12_Logic.js:383`)。子游戏(本框架模板的目标场景)走 `netType=0 / WebSocket`,这是线上实际通道,**新前端按 WebSocket 实现即可**。
|
||||
> 收包路径在两种传输下**不同**,详见 3.3 节对比。
|
||||
|
||||
### WebSocket 连接地址
|
||||
|
||||
WebSocket 在 `min_tcp(config)`(`00_minhttp.js:256`)内创建:
|
||||
```js
|
||||
var ws = new WebSocket("ws://" + config.ipport); // 00_minhttp.js:258,例: ws://127.0.0.1:5414
|
||||
```
|
||||
- `config.ipport` 来自 `GameData.Server`(`12_Logic.js:856 / 900 / 941` 等连接函数把 `server` 赋给 `config.ipport`)。
|
||||
- `GameData.Server` 的来源(netType==0 分支):启动实际调用 `get_config`(`12_Logic.js:521 / 1330`),以 POST 空 body 读取配置,URL 为 `gameserver + "?" + min_timestamp()`;`getConfig_Succ` 对响应文本 JSON.parse 后保存到内存 `GameData.serverConfig.data`。这里的 `data` 是客户端包装,远程 JSON 根对象本身不带这一层。
|
||||
- `get_paravalue` 按全局 → agent → game → channel → market 查找;每层第一个身份宽松相等的条目生效,只有 truthy 参数覆盖继承值,缺少层级即返回继承值,对象整体覆盖。
|
||||
- `getConfig_Succ` 选择 truthy `game_server_tcp`,否则 `player_server_tcp`;`serverType != 0` 时在配置解析之后覆盖为本地地址。debug/release 均读取配置,`isDebugger` 不控制读取开关。
|
||||
- 旧 `ServerUrl_Succ`(`12_Logic.js:533`)中的 `data.urlserver` 属于未启用的旧请求路径,不能作为当前启动配置契约。整份远程配置不写磁盘或 localStorage。
|
||||
- ⚠️ 注意:`Logic.setGameServer()`(`12_Logic.js:1270`)只设置 `Game_Config.Debugger.gameserver`(配置文件 URL),**不**设置 `GameData.Server`;两者是两步、不可混淆。
|
||||
- 切换房间/大厅服时,`GameData.Server` 被替换为 `_msg.data.roomserver` / `_msg.data.agentserver`(`09_Net.js:509 / 526`)。
|
||||
- `GameData.Server` 可为**单个字符串,也可为数组**(多候选服务器)。为数组时按 `GameData.serverIndex` 轮询,详见第 7 节。
|
||||
|
||||
---
|
||||
|
||||
## 3. 消息信封(Envelope)
|
||||
|
||||
### 3.1 客户端 → 服务器(发送)
|
||||
|
||||
统一出口 `Net._SendData(_app, _route, _rpc, _data)`(`09_Net.js:12`)。组装的是**单层**信封:
|
||||
|
||||
```json
|
||||
{
|
||||
"app": "youle",
|
||||
"route": "agent", // platform | agent | room
|
||||
"rpc": "player_login", // RPC 名称
|
||||
"data": { ... } // 业务数据
|
||||
}
|
||||
```
|
||||
|
||||
- `app` 字段固定取 `AppList.app`,值为 `"youle"`(`02_Const.js:5`,已确认恒为 `"youle"`)。
|
||||
- `route` 取 `RouteList.platform/agent/room`(`02_Const.js:8-10`)。
|
||||
- netType==0:`Net.ws_tcp.send(JSON.stringify(_msg))`(`09_Net.js:20`)。**单层**,直接发信封。
|
||||
- netType==1:走 `Func.AjaxHttp2(GameData.Server, _msg, ...)`(`09_Net.js:24`),POST JSON。
|
||||
|
||||
### 3.2 服务器 → 客户端(接收)—— WebSocket 路径:**单层**(「外层 data」是浏览器 MessageEvent,非协议层)
|
||||
|
||||
> **⚠️ 重要更正(2026-06-28 真机联调确认)**:早期本节误记为「双层 `{data:<inner>}`」。实测+源码核对确认:**服务器实发的协议帧是单层** `{route, rpc, data}`(实测还带 `app` 字段)。所谓"外层 `data`"是**浏览器 WebSocket `MessageEvent` 对象**,不是服务器的协议层。
|
||||
|
||||
依据 `00_minhttp.js:266` 的 `min_tcp()`:`ws.onmessage = config.onmessage;`——把**浏览器原生 `MessageEvent`** 直接交给 `12_Logic.js:119` 的 `this.onmessage(_msg)`。所以 `_msg` 是 `MessageEvent`,`_msg.data`(= `MessageEvent.data`)才是服务器实发帧(源码注释原文:「`msg.data` 才是服务器发过来的业务数据」`00_minhttp.js:265`)。`12_Logic.js` 里 `data=_msg.data`(137) 与 `_msg=_msg.data`(224) 读的是**同一个 `MessageEvent.data`**,只剥掉这一层浏览器事件壳,没有第二层。
|
||||
|
||||
```
|
||||
MessageEvent.data(= 服务器实发帧,单层)= {
|
||||
"app": "youle", // 实测服务器回包带 app(与客户端发包同形)
|
||||
"route": "...",
|
||||
"rpc": "...",
|
||||
"data": { ... } // 真正的业务数据
|
||||
}
|
||||
// 握手包:MessageEvent.data 为原始串 "@toconcon...";心跳包:{ "com": "@serverheartbeat" }
|
||||
```
|
||||
|
||||
**新前端落地(YouleNexus)**:传输层(`CocosWebSocketTransport` / 联调用 `WsTransport`)已取 `ev.data`(= MessageEvent.data = 服务器单层帧)交给 `decodeFrame`,故 **`decodeFrame` 不得再剥一层 `.data`**——直接把 frame 当单层 `{route,rpc,data}` 解。(曾因沿用"双层"误解多剥一层,导致 `kick_server` 等业务包被误丢弃,真机联调暴露并已修正。)
|
||||
|
||||
完整解包顺序(含三道前置过滤,务必照此实现,否则会误处理旧连接包/错误回包/握手心跳):
|
||||
|
||||
1. **旧连接去重**:`if(GameData.TcpID != this.id) return;`(`12_Logic.js:122`)——每次重连 `GameData.TcpID++`,每个 WebSocket 实例闭包持有自己的 `this.id`,只有最新连接的包才被处理,旧连接残留包直接丢弃。
|
||||
2. 若网络状态关闭 `if(!GameData.netWorkSate) return;`(`12_Logic.js:127`)。
|
||||
3. `_msg`(= MessageEvent;若传输直接给字符串则 `JSON.parse`,`12_Logic.js:133-136`)→ 取 `data = _msg.data`(= 服务器实发帧,`12_Logic.js:137`)。
|
||||
4. **特殊错误包**:若 `data === "webserve-服务器未工作"` → 进入重发登录分支后 `return`(详见 4 节)。
|
||||
5. 若 `data` 是字符串:
|
||||
- 若 `data.substr(0,9) === "@toconcon"` → **握手包,直接 `return` 忽略**(`12_Logic.js:176-179`)。
|
||||
- 否则 `data = JSON.parse(data)`(`12_Logic.js:180`)→ 得到单层 `{route,rpc,data}`。
|
||||
6. **重置收包超时定时器**(仅 `!ConstVal.isGameHall` 时创建,见第 4 节)(`12_Logic.js:182-217`)。
|
||||
7. 若 `data.com === "@serverheartbeat"` → **心跳包,直接 `return` 忽略,不回包**(`12_Logic.js:218-223`)。
|
||||
8. 令 `_msg = _msg.data`(仍是同一个 `MessageEvent.data`),必要时再 `JSON.parse`(`12_Logic.js:224-229`)→ 得到 `{route, rpc, data}`。
|
||||
9. **错误回包忽略**:`if(_msg.rpc == "submit_error") return;`(`12_Logic.js:235`)。
|
||||
10. **登录态门控**(`isSendLoginState`):若 `GameData.isSendLoginState==true`(已发登录、等待 `player_login` 响应期间),则除 `player_login`(清门控)与 `kick_server` 外,**其它收包一律 `return` 丢弃**(`12_Logic.js:238-257`)。
|
||||
11. 按 `_msg.route` 分发(见第 5 节,`12_Logic.js:258`)。
|
||||
|
||||
> 注:源码对 `MessageEvent.data` 做了 `typeof == "string"` 判断后再 `JSON.parse`,是为兼容服务器有时发对象、有时发字符串(**同一层**的两种编码,非两层)。新前端「是字符串就 parse 一次」即可。
|
||||
|
||||
### 3.3 服务器 → 客户端 —— HTTP(netType==1) 路径:单层、无信封过滤
|
||||
|
||||
netType==1 收包走 `Func.AjaxHttp2` 的成功回调(`09_Net.js:24-44`),与 WebSocket 路径**完全不同**:
|
||||
|
||||
- 回调直接拿到 `_msg`(字符串则 `JSON.parse`),**单层**:直接读 `_msg.route` / `_msg.rpc` 分发,**没有外层 `data` 信封、没有 `@toconcon` 握手、没有 `@serverheartbeat` 心跳、没有 30s 超时、没有 TcpID/isSendLoginState 三道过滤**。
|
||||
- 分发逻辑同 5 节:`route ∈ {platform,agent,room}` → `Netrpc`,否则 → `Game_Modify._ReceiveData(_msg)`。
|
||||
- 失败回调里若是登录请求(`input_msg=="playerLogin"`)→ `GameUI.OpenTips("网络状况不好")`(`09_Net.js:38-43`)。
|
||||
|
||||
> 新前端只需实现 WebSocket(netType==0) 路径;此节列出仅供对照,说明降级通道的差异。
|
||||
|
||||
---
|
||||
|
||||
## 4. 握手与心跳
|
||||
|
||||
| 包 | 方向 | 触发判定 | 频率 | 客户端动作 |
|
||||
|----|------|---------|------|-----------|
|
||||
| 握手包 | 服务器→客户端 | 外层 `data` 为字符串且 `substr(0,9)=="@toconcon"` | 连接成功后首包 | 忽略 `return` |
|
||||
| 心跳包 | 服务器→客户端 | 解包后 `data.com === "@serverheartbeat"` | ~20s 一次 ⚠️待服务器确认 | 忽略,**不回包** |
|
||||
| 收包超时 | 客户端本地 | —— | 每次收包重置定时器 | 仅 `!isGameHall` 启用;超时见下方降级逻辑 |
|
||||
|
||||
关键常量:`ConstVal.Max.heartbeat = 30000`(`02_Const.js:144`),即 30 秒收包超时阈值。
|
||||
|
||||
> ⚠️待服务器确认:心跳"约 20s 一次"仅源码注释佐证(`12_Logic.js:219` 注释「20秒一次」);客户端侧唯一硬常量是收包超时 30000ms。实际心跳间隔需抓包确认。
|
||||
|
||||
### 4.1 收包超时定时器(仅子游戏模式)
|
||||
|
||||
- 仅当 `!ConstVal.isGameHall` 时才 `setTimeout(..., ConstVal.Max.heartbeat)` 创建(`12_Logic.js:185-217`);**大厅模式不启用**此定时器。
|
||||
- 每次收到任意包都先 `clearTimeout` 再重建(滑动窗口)。
|
||||
- 30s 内未再收包 → `GameUI.OpenTips("网络较慢!")` + `GameData.heartBeatStage=true`,并按当前连接 `readyState` 分两条路径(仅非 Debugger 模式):
|
||||
- `readyState == CLOSED`:`disType=true; ConstVal? NetType=1; Logic.TryConnect();`(直接进重连)(`12_Logic.js:190-195`)。
|
||||
- `readyState == OPEN`:`NetType=1; GameUI.StartLoad(); isClose=true; Net.ws_tcp.close();` 并启 `setInterval(..., 5000)` 反复 `close()` 直到真正断开(`12_Logic.js:196-214`)。
|
||||
- 下次正常收包时若 `heartBeatStage==true`,会 `GameUI.CloseTips()` 并复位(`12_Logic.js:129-132`)。
|
||||
|
||||
> **结论**:心跳是服务器单向 push,新前端**无需**主动发送任何心跳包,只需:① 收到 `@serverheartbeat` 时不当作业务包丢弃;② 子游戏模式下维护一个「收到任意包就重置」的 30s 超时定时器用于断线判断与降级。
|
||||
|
||||
### 4.2 特殊错误包 `webserve-服务器未工作`
|
||||
|
||||
外层 `data === "webserve-服务器未工作"` 时(`12_Logic.js:138`):
|
||||
- **前置条件** `if(get_self(225,37,0,0,0) == 0)`(`12_Logic.js:139`,某 UI 状态判定)成立时:`setTimeout(..., 10000)` 即 10s 后用 `C_Player` 现有信息重新组装 `data` 并 `Net.Send_login(data)`(`12_Logic.js:140-159`)。
|
||||
- 然后 `return`,不继续后续解包(`12_Logic.js:161`)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 路由分发逻辑
|
||||
|
||||
解包得到内层 `{route, rpc, data}` 后(WS 路径 `12_Logic.js:258`;HTTP 路径 `09_Net.js:31`;`_SendData` 内 netType==1 回调亦同):
|
||||
|
||||
```js
|
||||
if (route === "platform" || route === "agent" || route === "room") {
|
||||
if (typeof Net[rpc] === "function") Netrpc; // 平台层:调用 Net.<rpc>(内层msg)
|
||||
} else {
|
||||
Game_Modify._ReceiveData(msg); // 其它路由:交给子游戏
|
||||
}
|
||||
```
|
||||
|
||||
- **平台层**:`route ∈ {platform, agent, room}` → 调用 `Netrpc`,`Net.<rpc>` 再转 `Desk.<rpc>` / `C_Player.<rpc>` 处理。
|
||||
- **游戏内**:其它 `route` → `Game_Modify._ReceiveData(内层msg)`,由子游戏自行按 `rpc` 分发(见 05 章)。
|
||||
|
||||
> 在新前端中,等价于实现一个 `dispatch(inner)`:先判断 route 是否平台层,是则查表调用对应处理器,否则进入游戏内协议处理器。
|
||||
|
||||
---
|
||||
|
||||
## 6. 登录前的完整握手流程(netType==0)
|
||||
|
||||
```
|
||||
1. 启动 AppStart → HTTP 请求配置服务器(gameserver=Game_Config.Debugger.gameserver)
|
||||
→ get_config POST 空 body → getConfig_Succ JSON.parse → 内存 GameData.serverConfig.data
|
||||
→ get_paravalue 按身份层级读取 → game_server_tcp 或 player_server_tcp → 显式本地覆盖
|
||||
2. (netType==0 时) AppStart 启 GameData.TcpTimer = setInterval(..., 3*GameData.timer) = 30s 首连看门狗
|
||||
(12_Logic.js:485-496):超时则 GameUI.OpenTips("网络状况不好...") 并 Net.ws_tcp.close()
|
||||
首连成功(onopen)或拿到配置后会 clearTimeout 它。
|
||||
3. Logic.firstConnect(GameData.Server) → new game_websocket(TcpID) → min_tcp() → new WebSocket("ws://"+server)
|
||||
4. onopen 触发 (12_Logic.js:3)。是否发 player_login 取决于状态(见 6.1)。
|
||||
5. 服务器先回握手包 @toconcon (忽略)。
|
||||
6. 服务器回 player_login 响应 (route=agent, rpc=player_login) → Net.player_login → Desk.login 处理。
|
||||
(登录响应字段见 02 章)
|
||||
7. 进入大厅或恢复房间/对局。
|
||||
```
|
||||
|
||||
### 6.1 onopen 发送 player_login 是**有条件**的(修正:非无条件)
|
||||
|
||||
`onopen`(`12_Logic.js:3-118`)按 `GameData.ConnectType` 与登录状态分支:
|
||||
|
||||
- 若 `!GameData.ConnectType`(普通连接,非服务器切换)且 `NetType==0`:
|
||||
- **重连场景** `GameData.isLogin == true`:用 `C_Player` 现有信息(agentid/openid/gameid/nickname/avatar/sex/province/unionid/city/version/channelid/marketid,含 deviceLogin 时附 telphone)组装 `data` 并 `Net.Send_login(data)`(`12_Logic.js:34-52`)。
|
||||
- **首登场景** `GameData.isLogin == false`:读 `Utl.getCookie(Utl.Config.wxinfo)`;**cookie 为 null 时不发** `player_login`(`12_Logic.js:53-79`);非 null 时 `SetWxInfo` 后组装并 `Net.Send_login(data)`。
|
||||
- 若 `GameData.ConnectType`(服务器切换重连):按 `GameData.ConnectRpc` 重发 `connect_roomserver`/`connect_agentserver`,或在 `disType` 下补发登录(`12_Logic.js:82-117`)。
|
||||
|
||||
> 修正要点:文档不可写「onopen 立即/无条件发 player_login」。首登必须先有 wxinfo cookie。
|
||||
|
||||
### 6.2 Send_login 的 4s 守护超时(子游戏模式)
|
||||
|
||||
`Net.Send_login(_data)`(`09_Net.js:122-238`)在 `!ConstVal.isGameHall` 且 `sendLoginTimer==null` 时,设 `setTimeout(..., 4000)`(`09_Net.js:155-172`):4s 内未收到 `player_login` 响应则
|
||||
`ConnectType=false; disType=true; NetType=1; isClose=true; Net.ws_tcp.close();` 并启 `setInterval(..., 5000)` 反复 `close()` 直到断开(再由 onclose 走重连/降级)。
|
||||
发出登录时设 `GameData.isSendLoginState=true`(`09_Net.js:152`),由 3.2 第 10 步门控收包。
|
||||
|
||||
---
|
||||
|
||||
## 7. 断线重连与服务器切换
|
||||
|
||||
### 7.1 onclose(`12_Logic.js:278`)
|
||||
|
||||
`onclose` 按 `GameData.ConnectType` 与 `GameData.firstConnect` 分流:
|
||||
|
||||
- `GameData.urlFail` 为真直接 `return`(配置都没拿到,不重连)(`12_Logic.js:281`)。
|
||||
- **非切换** `!ConstVal? !GameData.ConnectType`:
|
||||
- 若 `!GameData.firstConnect`(已过首连):`NetType=1; Logic.TryConnect();`,按 `disType` 显示断线 UI(`12_Logic.js:297-306`)。
|
||||
- 若 `GameData.firstConnect`(仍在首连阶段):`GameData.tryTimes++`;候选数 `ttimes`(数组时取 `Server.length`,否则 3);`tryTimes % ttimes == 0` 时提示「网络状况不好...」;`Logic.Connect(GameData.Server)`(`12_Logic.js:307-317`)。
|
||||
- **切换** `GameData.ConnectType`:`!disType` → `Logic.Connect(GameData.Server)`,否则 `GameUI.StartLoad()`(`12_Logic.js:319-326`)。
|
||||
|
||||
### 7.2 重连间隔与重连函数
|
||||
|
||||
- `GameData.timer = 10000`(`04_Data.js:42`)= 重连定时器间隔(`Logic.Connect`/`Logic.TryConnect` 内 `setTimeout(..., GameData.timer)`,`12_Logic.js:910 / 1021`)。
|
||||
- `Logic.firstConnect(server)`(`12_Logic.js:826`):首连,不延时。
|
||||
- `Logic.Connect(server)`(`12_Logic.js:869`):`setTimeout timer` 后连接。
|
||||
- `Logic.TryConnect()`(`12_Logic.js:915`):`netType==1` 时直接 `return`(不重连);否则立即连一次 + 再挂 `setTimeout timer` 一次。
|
||||
|
||||
### 7.3 多候选服务器轮询(`GameData.Server` 为数组)
|
||||
|
||||
两套独立计数路径:
|
||||
|
||||
- **首连阶段(onclose, firstConnect)**:`GameData.tryTimes++`;候选数取 `Server.length`;提示节流用 `tryTimes % ttimes`(`12_Logic.js:308-316`)。`firstConnect`/`Connect` 内若 `isArray(server)` 则 `serverIndex` 取 0 或 `(serverIndex+1)%length` 切换(`12_Logic.js:833-836 / 876-880`)。
|
||||
- **已登录后断线(TryConnect)**:`GameData.tryReconnectTimes++`;`tryReconnectTimes % 3 == 0` 时归零并 `serverIndex = (serverIndex+1)%Server.length` 切下一个(`12_Logic.js:952-962 / 1001-1011`)。即**每失败约 3 次轮换一个候选地址**。
|
||||
- 数组模式下每次连接都把 `GameData.sendLoginTimes = -1` 复位(`12_Logic.js:834 / 877 / 938 / 989`)。
|
||||
|
||||
### 7.4 服务器切换指令(S→C 推送)
|
||||
|
||||
> connect_roomserver · route=room · S→C 推送;服务器要求客户端切到房间服;推送字段(S→C) `roomserver`(string)—新房间服 ip:port;客户端动作:`GameData.ConnectType=true; GameData.ConnectRpc=connect_roomserver; GameData.ConnectPack=data; GameData.Server=data.roomserver; Net.ws_tcp.close()`(onclose 后用新地址重连,重连 onopen 会重发 `Send_connect_roomserver(ConnectPack)`);接收 `09_Net.js:503-512`;备注 —
|
||||
|
||||
> connect_agentserver · route=agent · S→C 推送;服务器要求客户端切回大厅服;推送字段(S→C) `agentserver`(string)—新大厅服 ip:port、`opt`(string)—切换原因;客户端动作:若 `opt == other_break_room || opt == free_room` 则先 `GameUI.StartLoad()`;随后 `ConnectType=true; ConnectRpc=connect_agentserver; ConnectPack=data; GameData.Server=data.agentserver; Net.ws_tcp.close()`;接收 `09_Net.js:518-529`;备注 ⚠️待服务器确认 `data.opt` 完整取值域(源码仅见其与 `other_break_room` / `free_room` 比较)。
|
||||
|
||||
> 这两个指令客户端也可主动发起:`Net.Send_connect_roomserver` / `Net.Send_connect_agentserver`(`09_Net.js:500 / 515`,route 分别为 room/agent,rpc 同名)。
|
||||
|
||||
---
|
||||
|
||||
## 8. 错误上报(可选实现,C→S)
|
||||
|
||||
> submit_error · route=agent · C→S 单向上报(无响应);上报客户端异常;请求字段(C→S) `packet`(string)—出错的包、`msg`(string)—错误堆栈、`playerid`/`agentid`/`gameid`;发送 `09_Net.js:55-80`(`Net.submit_error`,同 `msg` 去重,仅当 `errorMsg` 变化才发);备注:catch 中由 `12_Logic.js:265-274` 在 `isSubmitError` 开启时调用。
|
||||
|
||||
> submit_log · route=agent · C→S 单向上报;与 submit_error 同结构;发送 `09_Net.js:81-101`(`Net.submit_log`);备注 ⚠️待服务器确认:其内部 `rpc` 字段写死为 `"submit_error"`(`09_Net.js:85`),疑似复用——是否存在独立 `submit_log` 协议待后端确认。🐛 若服务端需区分两类上报,此处 rpc 名错误。
|
||||
|
||||
> 注意:客户端对收到的 `rpc=="submit_error"` 回包**直接 `return` 忽略**(`12_Logic.js:235`)。新前端对适配非必需,可选实现。
|
||||
|
||||
```json
|
||||
{ "app":"youle", "route":"agent", "rpc":"submit_error",
|
||||
"data": { "packet":"<出错的包>", "msg":"<错误堆栈>",
|
||||
"playerid":..., "agentid":..., "gameid":... } }
|
||||
```
|
||||
@@ -0,0 +1,411 @@
|
||||
# 02 · 平台层协议 — agent 路由(大厅服务器)
|
||||
|
||||
> 信封:`{ app:"youle", route:"agent", rpc:"<下列名称>", data:{...} }`
|
||||
> 本篇覆盖 **route=agent 的全部平台层数据包**(双向)。每个 rpc 给出 C→S 请求字段与 S→C 响应/推送字段。子游戏对局包(route=room)不在此篇。
|
||||
> 通用身份字段:`agentid`(代理ID)、`gameid`(游戏ID)、`playerid`(玩家ID)。多数请求由各调用点手工拼装,未必三者齐全,下文逐条标注实际字段。
|
||||
> 统一条目格式:
|
||||
> > `rpc名` · route=agent · <方向>
|
||||
> > 场景 / 请求字段(C→S) / 响应或推送字段(S→C) / 源码 发送·接收 / 备注(⚠️待后端确认,🐛源码 bug)
|
||||
|
||||
---
|
||||
|
||||
## 登录 / 账号
|
||||
|
||||
### `player_login` · route=agent · C→S 请求 + S→C 响应
|
||||
- 接收路由补充(2026-09-06 实包核对):C→S 仍为 `agent/player_login`;S→C 可以是 `agent`、`room` 或 `platform` 下的 `player_login`。原工程 `12_Logic.js:238-260` 按 rpc 解除登录等待,再把这三类平台路由交给 `Net[rpc]`。已实测短信验证码失败为 `room/player_login`,data 为 `{msg:"短信验证码不正确",state:-1,time:0}`;必须解除等待并交给登录失败处理,不能因响应 route 与发包 route 不同而忽略。
|
||||
- 场景:连接建立后 `onopen` 自动发送;断线重连、切服后重发(12_Logic.js:35、:61 构造,09_Net.js:122 `Send_login` 注入)。
|
||||
- 请求字段(C→S):
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| agentid | string/int | 代理ID |
|
||||
| gameid | string/int | 游戏ID |
|
||||
| openid | string | 微信 openid(游客可为空/特殊值) |
|
||||
| nickname | string | 昵称 |
|
||||
| avatar | string | 头像 URL |
|
||||
| sex | int | 性别 0未知/1男/2女 |
|
||||
| province | string | 省份 |
|
||||
| city | string | 城市 |
|
||||
| unionid | string/int | 微信开放平台 unionid |
|
||||
| version | string | 客户端版本号(GameData.versionCode) |
|
||||
| channelid | string/int | 渠道ID |
|
||||
| marketid | string/int | 市场ID |
|
||||
| ip | string | 客户端IP,`Send_login` 注入 `returnCitySN.ip`(09_Net.js:125,仅 returnCitySN 存在时) |
|
||||
| location | object | 定位对象,`Send_login` 注入 `C_Player.addr`(09_Net.js:132,可为 null) |
|
||||
| machineid | string | 机器标识 `Logic.getMachineId()`(09_Net.js:139) |
|
||||
| machineroom | string | 机房标识 `Utl.getRoomcode()`(09_Net.js:140) |
|
||||
| telphone | string | 绑定手机号,仅 `GameData.sysConfig.deviceLogin` 开启时随 `telphoneAuto:true` 一起携带(12_Logic.js:48-50) |
|
||||
| telphoneAuto | bool | 设备号自动登录标记,同上条件下=true |
|
||||
| playerid | int | 可选,本地缓存 playerid(`GameData.loginPlayerid` 开启时由 `Logic.readPlayerId()` 注入,09_Net.js:143-148,用于复用账号) |
|
||||
- 响应字段(S→C):`state`(int 0成功/非0失败) + 大量字段,分两组:
|
||||
- **A 组 账号资产**:`roomcard`、`bean`、`bank`(仓库星星)、`bankpower`、`bankpwd`、`charm`、`sign`、`tel`、`invitecode` 等(06_Player.js:88 `SetMyInfo` 读取,详见 [04-数据结构.md → 登录响应](04-数据结构.md#登录响应-deskloginplayer_login))。
|
||||
- **B 组 房间恢复**:在房时附带房间快照(roomcode/seat/players/deskinfo 等),由 `Desk.login` 处理,详见 doc04。
|
||||
- 源码:发送 `09_Net.js:236`(`Net.Send_login`→`Net._SendData`);接收 `09_Net.js:240`(`Net.player_login`→`Desk.login`)。
|
||||
- 备注:— (请求侧字段以本表为准;响应数据结构引用 doc04)
|
||||
|
||||
### `query_player2` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:仓库转账前查询目标玩家信息。
|
||||
- 请求字段(C→S):`agentid`、`playerid`(目标玩家ID)。
|
||||
- 响应字段(S→C):`avatar`(string)、`nickname`(string)、`playerid`(int);昵称/头像均空时提示"未找到对应玩家"。
|
||||
- 源码:发送 `09_Net.js:870`;接收 `09_Net.js:873`→`Desk.query_player2`(`07_Desk.js:1368`)。
|
||||
- 备注:—
|
||||
|
||||
### `binding_phone` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:绑定手机号。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`phonenum`、`smmcode`(短信验证码)。
|
||||
- 响应字段(S→C):`phonenum`(string,回写 `C_Player.tel`)。
|
||||
- 源码:发送 `09_Net.js:830`;接收 `09_Net.js:834`→`Desk.binding_phone`(`07_Desk.js:1337`)。
|
||||
- 备注:—
|
||||
|
||||
### `send_phone_checkcode` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:发送手机短信验证码。
|
||||
- 请求字段(C→S):`agentid`、`phonenum`(构造点未集中定位,至少含手机号)。
|
||||
- 响应字段(S→C):无业务字段(`Desk.send_phone_checkcode` 为空实现,07_Desk.js:1342)。
|
||||
- 源码:发送 `09_Net.js:838`;接收 `09_Net.js:842`。
|
||||
- 备注:⚠️ 请求字段以后端实现为准。
|
||||
|
||||
### `send_phone_code_wechat` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:微信渠道发送手机验证码(绑定手机流程的另一入口)。
|
||||
- 请求字段(C→S):`agentid`、`phonenum`(唯一调用点 11_GameUI.js:2620-2623 当前被注释,按注释代码为 agentid+phonenum)。
|
||||
- 响应字段(S→C):无业务字段(`Desk.send_phone_code_wechat` 为空实现,07_Desk.js:1345)。
|
||||
- 源码:RpcList 定义 `02_Const.js:81`;发送 `09_Net.js:846`(`Send_send_phone_code_wechat`);接收 `09_Net.js:850`→`Desk.send_phone_code_wechat`(`07_Desk.js:1345`)。
|
||||
- 备注:⚠️ 框架完整定义并接线,但**当前前端唯一调用点(11_GameUI.js:2623)被注释,实际不发送**;字段以后端实现为准。
|
||||
|
||||
### `setSign` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:设置个性签名。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`sign`。
|
||||
- 响应字段(S→C):`sign`(string,回显写入 `C_Player.sign`)。
|
||||
- 源码:发送 `09_Net.js:736`;接收 `09_Net.js:740`→`C_Player.setSign`(`06_Player.js:525`)。
|
||||
- 备注:—
|
||||
|
||||
---
|
||||
|
||||
## 房间创建 / 进入(大厅侧)
|
||||
|
||||
### `create_room` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:玩家创建房间。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`gameid`、`roomtype`(房间类型配置数组,见 doc04);`Send_create_room` 自动注入 `ip`=`C_Player.ip`、`location`=`C_Player.addr`(09_Net.js:106-107)。
|
||||
- 响应字段(S→C):`state`(0成功)、`roomcode`、`seat`、`roomtype`、`makewar`、`asetcount`、`shortcode`、`infinite`;失败 `showerror`/`error`。详见 [04 → 房间响应公共字段](04-数据结构.md#房间创建--进入响应公共字段)。
|
||||
- 源码:发送 `09_Net.js:104`;接收 `09_Net.js:111`→`Desk.create_room` + `Game_Modify.createRoom`。
|
||||
- 备注:—
|
||||
|
||||
### `self_join_room` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:输入房号 / 快速加入 / H5 唤起 / 进 VIP 配置房。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`gameid`、`roomcode`(房号);`Send_self_join_room` 自动注入 `location`=`C_Player.addr`、`ip`=`C_Player.ip`(09_Net.js:254-255);进 VIP 配置房入口额外带 `vipMatch:1`(12_Logic.js:2171);比赛进房入口可带 `match_id`。
|
||||
- 响应字段(S→C):`state`、`roomcode`、`seat`、`isowner`、`players[]`、`roomtype`、`makewar`、`asetcount`、`deskwar`、`deskinfo`(重连快照)。详见 [04 → 房间响应公共字段](04-数据结构.md#房间创建--进入响应公共字段)。
|
||||
- 源码:发送 `09_Net.js:251`;接收 `09_Net.js:259`→`Desk.self_join_room`。
|
||||
- 备注:—
|
||||
|
||||
### `quick_enter_share_room` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:快速进入分享/星星场房间。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`gameid`、`type`(房间类型)、`roomtype`(可选)。
|
||||
- 响应字段(S→C):走 self_join_room 进房流程(无独立 `Net.quick_enter_share_room` 接收函数,结果通过 self_join_room/show_message 等回包)。
|
||||
- 源码:发送 `09_Net.js:652`。
|
||||
- 备注:⚠️ 无对应接收处理函数,进房结果依赖其它推送。
|
||||
|
||||
### `advanced_roomlist` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:拉取 VIP/高级房间列表。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`gameid`。
|
||||
- 响应字段(S→C):房间列表对象,整包写入 `GameData.snrRoomList` 并渲染(07_Desk.js:1176)。
|
||||
- 源码:发送 `09_Net.js:660`;接收 `09_Net.js:665`→`Desk.advanced_roomlist`。
|
||||
- 备注:—
|
||||
|
||||
### `advanced_createroom` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:创建 VIP/高级房间。
|
||||
- 请求字段(C→S):`agentid`、`gameid`、`playerid`、`tea`(茶水费)、`infinite`(0/1无限局)、`roomtype`、`videoConfig`(可选)、`rebateLimit`(可选)、`rebateType`(可选)。
|
||||
- 响应字段(S→C):`tea`、`rebateLimit` 及房间配置(整包写入 `GameData.snrRoomList`,并回拉 advanced_roomlist,07_Desk.js:1180)。
|
||||
- 源码:发送 `09_Net.js:669`;接收 `09_Net.js:674`→`Desk.advanced_createroom`。
|
||||
- 备注:—
|
||||
|
||||
### `get_share_room` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:获取分享/星星场房间列表(仅非大厅环境发送)。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`gameid`。
|
||||
- 响应字段(S→C):房间数组(`Desk.get_share_room` / `Game_Modify.getShareRoom` 处理)。
|
||||
- 源码:发送 `09_Net.js:641`(`ConstVal.isGameHall` 为 true 时不发);接收 `09_Net.js:648`。
|
||||
- 备注:—
|
||||
|
||||
### `getInfoByShortCode` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:按短码批量查询房间信息(VIP 房列表)。
|
||||
- 请求字段(C→S):`agentid`、`gameid`、`shortcodeList`(短码列表)。
|
||||
- 响应字段(S→C):`roomInfo`(短号房间信息) → `GameUI.setVipRoomListData`(07_Desk.js:1284)。
|
||||
- 源码:发送 `09_Net.js:754`;接收 `09_Net.js:758`→`Desk.getInfoByShortCode`。
|
||||
- 备注:—
|
||||
|
||||
### `switchRoomList` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:开关房间在列表中的可见/可进入状态。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`gameid`、`isClose`(0开/1关)。
|
||||
- 响应字段(S→C):`state`(0成功)、`isClose`;失败 `error`(07_Desk.js:1267)。
|
||||
- 源码:发送 `09_Net.js:744`;接收 `09_Net.js:748`→`Desk.switchRoomList`。
|
||||
- 备注:—
|
||||
|
||||
---
|
||||
|
||||
## 战绩 / 排行 / 财富
|
||||
|
||||
### `get_player_grade1` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:拉取战绩(类型1)。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`gameid`、`type`(可选)、`direction`(可选,翻页)、`gradeidx`(可选,分页索引)。
|
||||
- 响应字段(S→C):战绩数据(`gameCombat.get_player_grade1` 渲染)。
|
||||
- 源码:发送 `09_Net.js:359`;接收 `09_Net.js:364`。
|
||||
- 备注:—
|
||||
|
||||
### `get_player_grade2` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:拉取战绩(类型2)。
|
||||
- 请求字段(C→S):透传调用方 `_data`,构造点未集中定位;至少含 `agentid`/`playerid`/`gameid`。
|
||||
- 响应字段(S→C):战绩数据(`gameCombat.get_player_grade2` 渲染)。
|
||||
- 源码:发送 `09_Net.js:372`;接收 `09_Net.js:377`。
|
||||
- 备注:⚠️ 请求字段构造点未定位,待核对。
|
||||
|
||||
### `get_treasurelist` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:财富榜。
|
||||
- 请求字段(C→S):`agentid`、`gameid`。
|
||||
- 响应字段(S→C):`list`(排行数组) → `Desk.get_treasurelist`。
|
||||
- 源码:发送 `09_Net.js:686`;接收 `09_Net.js:691`。
|
||||
- 备注:—
|
||||
|
||||
### `getShortCodeRankList` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:短号场排行榜。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`shortcode`。
|
||||
- 响应字段(S→C):成功为排行数据(整包写入 `GameData.vipRank.data`);失败 `error:true` + `message`(07_Desk.js:1311)。
|
||||
- 源码:发送 `09_Net.js:770`;接收 `09_Net.js:774`→`Desk.getShortCodeRankList`。
|
||||
- 备注:—
|
||||
|
||||
### `getVipRankList` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:VIP 排行榜。
|
||||
- 请求字段(C→S):`agentid`、`limit`(条数)。
|
||||
- 响应字段(S→C):`list`(VIP排行数组) → `GameData.rankList`(07_Desk.js:1328)。
|
||||
- 源码:发送 `09_Net.js:788`;接收 `09_Net.js:792`→`Desk.getVipRankList`。
|
||||
- 备注:—
|
||||
|
||||
---
|
||||
|
||||
## 任务系统
|
||||
|
||||
### `get_player_task` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:获取任务列表。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`gameid`。
|
||||
- 响应字段(S→C):`tasks`(任务数组) → `Desk.get_player_task`。
|
||||
- 数值兼容:实际回包的 `tasks[].award` 可为数字字符串;原 UI 对 `finish/total` 做算术、对 `state` 用宽松数值比较。新前端在任务列表入库时将这四个字段的数字/十进制数字字符串统一为 number;空值、非数字、非有限值仍报错,state 必须为整数。
|
||||
- 源码:发送 `09_Net.js:409`;接收 `09_Net.js:414`。
|
||||
- 备注:—
|
||||
|
||||
### `player_finish_task` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:上报任务完成(如分享成功触发,06_Player.js:414)。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`taskid`。
|
||||
- 响应字段(S→C):`state`(int)——`state==1` 且当前 `taskstate==0` 时把 `C_Player.taskstate` 置 1(06_Player.js:476)。
|
||||
- 源码:发送 `09_Net.js:421`;接收 `09_Net.js:425`→`C_Player.player_finish_task`(`06_Player.js:474`)。
|
||||
- 备注:原文档"响应含 `taskstate`"无源码依据,已删除——接收函数仅读 `_msg.data.state`。
|
||||
|
||||
### `get_task_award` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:领取任务奖励。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`taskid`。`11_GameUI.js` 的任务领取点击只构造这三个字段,`Net.Send_get_task_award` / `_SendData` 原样发送,不补 `gameid`(2026-09-06 源码复核修正)。
|
||||
- 响应字段(S→C):`taskid`(对应任务 state 置 2=已领取)、`taskstate`(写入 `C_Player.taskstate`)(06_Player.js:482)。
|
||||
- 源码:发送 `09_Net.js:429`;接收 `09_Net.js:435`→`C_Player.get_task_award`。
|
||||
- 备注:—
|
||||
|
||||
### `refresh_task_state` · route=agent · C→S 请求(发送侧 `Send_can_award`)
|
||||
- 场景:刷新任务可领取状态。
|
||||
- 请求字段(C→S):透传 `_data`(构造点未集中定位,至少含 `agentid`/`playerid`)。
|
||||
- 响应字段(S→C):服务器推送 `can_award`(见下条)。
|
||||
- 源码:发送 `09_Net.js:440`(`Net.Send_can_award`)。
|
||||
- 备注:⚠️ 发送函数名为 `Send_can_award`,但**实际发出的 rpc 是 `"refresh_task_state"`**(硬编码字面量,**不在 RpcList**)。文档以实际 rpc 名为准。
|
||||
|
||||
### `can_award` · route=agent · S→C 推送
|
||||
- 场景:服务器通知有任务可领取。
|
||||
- 请求字段:无(纯推送)。
|
||||
- 推送字段(S→C):任务可领取标记(载荷字段待后端确认)。
|
||||
- 源码:接收 `09_Net.js:444`(`Net.can_award`)→ 调 `C_Player.can_award(_msg)`(`09_Net.js:445`)。
|
||||
- 备注:🐛 `06_Player.js` **未定义** `Player.prototype.can_award`(全文无此方法)。推送一旦到达,`C_Player.can_award` 为 undefined,调用即抛 TypeError。当前为 bug,不可当正常协议使用。
|
||||
|
||||
---
|
||||
|
||||
## 支付 / 充值 / 资产
|
||||
|
||||
### `get_paylist` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:拉取支付项列表。
|
||||
- 请求字段(C→S):`agentid`。
|
||||
- 响应字段(S→C):`paylist`(支付项数组) → `GameData.payList`,并打开支付界面(09_Net.js:567)。
|
||||
- 源码:发送 `09_Net.js:562`;接收 `09_Net.js:567`。
|
||||
- 备注:—
|
||||
|
||||
### `pay_succ` · route=agent · C→S 请求(经 HTTP)
|
||||
- 场景:支付成功后通知服务器入账。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`channelid`、`productid`、`payid`、`amount`、`money`、`paytype`(05_Func.js:2240-2254 构造)。
|
||||
- 响应字段(S→C):无显式 WS 回包;资产变化通过 `update_bean`/`update_roomcard` 推送(充房卡场景前端还会本地 `UpdateRoomcard`,05_Func.js:2263)。
|
||||
- 源码:WS 发送函数 `Net.Send_pay_succ`(`09_Net.js:573`) **被注释**(05_Func.js:2249、:3199);实际改用 `Func.AjaxHttp` 以同样的 `{app,route:agent,rpc:pay_succ,data}` 信封走 **HTTP** 提交(05_Func.js:2250-2255、:3200-3205)。
|
||||
- 备注:⚠️ WebSocket 通道当前不发;该包以 HTTP POST 形式上行,rpc 名仍为 `pay_succ`,待后端确认接收端一致。
|
||||
|
||||
### `topup_card` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:充值卡兑换。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`cardno`(卡号)。
|
||||
- 响应字段(S→C):`Desk.topup_card` 为空实现(07_Desk.js:1365),资产变化经 update_bean/update_roomcard 推送。
|
||||
- 源码:发送 `09_Net.js:863`;接收 `09_Net.js:867`。
|
||||
- 备注:—
|
||||
|
||||
### `giveCoin` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:仓库面板向他人转账豆豆/金币。
|
||||
- 请求字段(C→S):`agentid`、`playerid`(转出)、`toPlayerid`(转入目标ID)、`gameid`、`count`(数量)、`password`(仓库密码)(11_GameUI.js:2132-2139)。
|
||||
- 响应字段(S→C):`state`(0成功)、`star2`(转出后**仓库星星**数 → `setWareHouseStarCOunt`,**非豆豆**);失败 `showerror`/`error`(07_Desk.js:1381)。
|
||||
- 源码:发送 `09_Net.js:876`;接收 `09_Net.js:879`→`Desk.giveCoin`。
|
||||
- 备注:响应 `star2` 为仓库星星数,注意区别于豆豆余额。
|
||||
|
||||
---
|
||||
|
||||
## 仓库 / 星星 / 魅力
|
||||
|
||||
### `set_bankpwd` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:设置仓库密码。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`unionid`、`password`。
|
||||
- 响应字段(S→C):`state`(0成功)、`password`;失败 `showerror`/`error`(07_Desk.js:1228)。
|
||||
- 源码:发送 `09_Net.js:697`;接收 `09_Net.js:701`→`Desk.set_bankpwd`。
|
||||
- 备注:—
|
||||
|
||||
### `change_star` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:仓库存/取(豆豆 ↔ 仓库星星)。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`mode`(0存入/1取出,源码 `safeInputType-1`)、`password`(仓库密码,字符串)、`count`(数量)(11_GameUI.js:2065-2069 调用点齐全携带此 5 字段)。
|
||||
- 响应字段(S→C):`state`(0成功)、`star1`(更新后豆豆余额 → `update_bean2`)、`star2`(更新后仓库星星数 → `setWareHouseStarCOunt`)、`msg`(可选提示)、`count`(可选,回填安全输入);失败 `showerror`/`error`(07_Desk.js:1238)。
|
||||
- 源码:发送 `09_Net.js:707`;接收 `09_Net.js:711`→`Desk.change_star`。
|
||||
- 备注:原审计疑虑"`mode`/`password` 未见"——经核对仓库存/取调用点(11_GameUI.js:2068-2069)**确含** `mode` 与 `password`,文档正确,疑虑解除。审计提到的"agentid/playerid/toPlayerid/gameid/count"实为相邻的 `giveCoin` 转账包(11_GameUI.js:2132-2139),并非本 rpc。
|
||||
|
||||
### `update_charm` · route=agent · S→C 推送
|
||||
- 场景:座位魅力值更新。
|
||||
- 请求字段:`Net.Send_update_charm`(`09_Net.js:727`) 已定义但**无任何调用点**;实际只作服务器→客户端推送。
|
||||
- 推送字段(S→C):`seatlist`:[ {`seat`(int), `charm`(number)} ](07_Desk.js:1260 遍历 setCharm)。
|
||||
- 源码:接收 `09_Net.js:731`→`Desk.update_charm`(`07_Desk.js:1260`)。
|
||||
- 备注:—
|
||||
|
||||
### `setAllCharm` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:批量设置总魅力。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`gameid`、`value`(11_GameUI.js:1269-1275)。
|
||||
- 响应字段(S→C):无显式业务字段,`Desk.setAllCharm` 仅本地存储并提示成功(07_Desk.js:1321)。
|
||||
- 源码:发送 `09_Net.js:779`;接收 `09_Net.js:783`→`Desk.setAllCharm`。
|
||||
- 备注:—
|
||||
|
||||
---
|
||||
|
||||
## 邀请码 / 绑定
|
||||
|
||||
### `binding_invitecode` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:绑定邀请码。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`invitecode`(11_GameUI.js:1367-1369)。
|
||||
- 响应字段(S→C):`state`(整数或整数型字符串,`0`/`"0"` 成功时写入 `invitecode`)、`invitecode`、`error`(提示文案)。原工程 `06_Player.js:270` 使用 `state == 0`;2026-09-06 联调确认服务器也返回字符串状态,前端在绑定响应入口归一化,不能用仅接受 number 的校验直接拒绝。
|
||||
- 源码:发送 `09_Net.js:587`;接收 `09_Net.js:592`→`C_Player.binding_invitecode`。
|
||||
- 备注:—
|
||||
|
||||
### `get_player_invitecode` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:获取自己的邀请码(打开绑定界面)。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`unionid`、`openid`(11_GameUI.js:1378-1382)。
|
||||
- 响应字段(S→C):`invitecode`(string) → `C_Player.setInvitecod` 并打开绑定界面(09_Net.js:607)。
|
||||
- 源码:发送 `09_Net.js:603`;接收 `09_Net.js:607`→`Net.get_player_invitecode`。
|
||||
- 备注:修正原文档——请求字段为 `agentid/playerid/unionid/openid`,**无 `gameid`**,新增 `unionid`/`openid`(11_GameUI.js:1378)。
|
||||
|
||||
---
|
||||
|
||||
## VIP 管理 / 黑白名单
|
||||
|
||||
### `optBanList` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:黑名单查看/添加/移除(多入口)。
|
||||
- 请求字段(C→S):随入口不同,公共字段 `agentid`、`playerid`、`type`:
|
||||
- `type=1` 查看黑名单列表(agentid/playerid/type,11_GameUI.js:2401-2405)
|
||||
- `type=3` 按 ID 添加(agentid/playerid/optId/type,11_GameUI.js:2376-2381)
|
||||
- `type=4` 按 ID 移除(agentid/playerid/optId/type,11_GameUI.js:2389-2394 及列表项删除 2453-2458)
|
||||
- `type=6` 一键全部添加(agentid/playerid/type,11_GameUI.js:1262-1267)
|
||||
- 另有带 `breakRoom`(0/1) 与 `gameid` 的 `type=3` 添加入口(agentid/gameid/playerid/optId/breakRoom/type,11_GameUI.js:2417-2428;breakRoom 由 `GameData.blackList.breakRoom` 决定,开关在 case 3260)
|
||||
- 服务器响应中亦见 `type=5`(另一种列表返回,07_Desk.js:1299)
|
||||
- 响应字段(S→C):`type`(1/3/4/5/6)、`banList`(黑名单数组)、`message`(可选提示)(07_Desk.js:1289)。
|
||||
- 源码:发送 `09_Net.js:762`;接收 `09_Net.js:766`→`Desk.optBanList`。
|
||||
- 备注:—
|
||||
|
||||
### `getPlayerWhiteList` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:获取白名单。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`gameid`。
|
||||
- 响应字段(S→C):`whiteList`(数组) → `GameData.whiteList.data`(07_Desk.js:1412)。
|
||||
- 源码:发送 `09_Net.js:882`;接收 `09_Net.js:886`→`Desk.getPlayerWhiteList`。
|
||||
- 备注:—
|
||||
|
||||
### `optWhiteList` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:白名单添加/修改/删除。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`gameid`、`shortcode`、`mode`、`userid`(目标ID):
|
||||
- `mode=1` 添加/修改,并带 `value`(设置的魅力值)
|
||||
- `mode=2` 删除(仅 `userid`)
|
||||
- 响应字段(S→C):`whiteList`(更新后数组)、`mode`(可选)、`message`(可选提示)(07_Desk.js:1398)。
|
||||
- 源码:发送 `09_Net.js:890`;接收 `09_Net.js:894`→`Desk.optWhiteList`。
|
||||
- 备注:—
|
||||
|
||||
### `setVipForbidSelect` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:VIP 房禁止玩家选桌开关。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`gameid`、`forbidSelect`(1开/0关,11_GameUI.js:2631-2639)。
|
||||
- 响应字段(S→C):`state`(0成功)、`forbidSelect`(1开/0关);失败 `error`(07_Desk.js:1348)。
|
||||
- 源码:发送 `09_Net.js:855`;接收 `09_Net.js:859`→`Desk.setVipForbidSelect`。
|
||||
- 备注:—
|
||||
|
||||
---
|
||||
|
||||
## 其它 agent 协议
|
||||
|
||||
### `submit_opinion` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:提交反馈/意见。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`gameid`、`content`。
|
||||
- 响应字段(S→C):`state`(0成功)(07_Desk.js:1100)。
|
||||
- 源码:发送 `09_Net.js:547`;接收 `09_Net.js:551`→`Desk.submit_opinion`。
|
||||
- 备注:—
|
||||
|
||||
### `submit_location` · route=agent · C→S 请求 + S→C 响应
|
||||
- 场景:提交定位信息(仅非大厅环境发送)。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`info`(定位对象)。
|
||||
- 响应字段(S→C):经 `Game.submit_location` 处理(09_Net.js:583)。
|
||||
- 源码:发送 `09_Net.js:577`(`ConstVal.isGameHall` 为 true 时不发);接收 `09_Net.js:583`。
|
||||
- 备注:—
|
||||
|
||||
### `submit_phoneinfo` · route=agent · C→S 请求(无响应处理)
|
||||
- 场景:提交手机/通讯录信息(仅非大厅环境发送)。
|
||||
- 请求字段(C→S):`agentid`、`playerid`、`info`{ `phoneInfo`(手机信息), `addrBook`(通讯录) }。
|
||||
- 响应字段(S→C):`Net.submit_phoneinfo` 接收函数为空实现(09_Net.js:722)。
|
||||
- 源码:发送 `09_Net.js:717`;接收 `09_Net.js:722`。
|
||||
- 备注:—
|
||||
|
||||
### `submit_error` / `submit_log` · route=agent · C→S 上行(硬编码包)
|
||||
- 场景:上报前端异常/日志。异常捕获时(12_Logic.js:270)、获取 HTML 失败(12_Logic.js:1990/2009/2014)、收到踢下线包(07_Desk.js:963)等处调用。
|
||||
- 请求字段(C→S):`packet`(出错数据包字符串)、`msg`(错误/堆栈信息)、`playerid`、`agentid`、`gameid`(09_Net.js:66-72)。
|
||||
- 响应字段(S→C):服务器若回 rpc `"submit_error"`,前端在分发处直接 `return` 忽略(12_Logic.js:235)。
|
||||
- 源码:发送 `Net.submit_error`(`09_Net.js:55`) / `Net.submit_log`(`09_Net.js:81`)。
|
||||
- 备注:均为**硬编码** `route:"agent", rpc:"submit_error"`,不经 `Send_*`/RpcList。注意 `Net.submit_log`(`09_Net.js:81`) 实际发出的 rpc 同样是 `"submit_error"`(与 submit_error 同包结构)。两者带去重:`submit_error` 对相同 `msg` 只发一次(09_Net.js:57-61),`submit_log` 不去重。
|
||||
|
||||
### `kick_server` · route=agent · C→S 请求 + S→C 推送
|
||||
- 场景:管理端踢出玩家 / 被踢下线弹窗。
|
||||
- 请求字段(C→S):`agentid` 等(构造点未集中定位)。
|
||||
- 推送字段(S→C):`msg`(踢出提示) → `GameUI.OpenKick`(07_Desk.js:1108)。
|
||||
- 源码:发送 `09_Net.js:555`;接收 `09_Net.js:558`→`Desk.kick_server`。
|
||||
- 备注:⚠️ 请求字段待补。
|
||||
|
||||
### `broadcast` · route=agent · C→S 请求 + S→C 推送
|
||||
- 场景:广播消息/滚动公告(主要为服务器推送)。
|
||||
- 请求字段(C→S):`agentid` 等(`Send_broadcast` 存在,09_Net.js:530)。
|
||||
- 推送字段(S→C):`msgtype`(0消息框/1滚动公告,可选,缺省 0)、`msgcontent`(内容)(07_Desk.js:1112)。
|
||||
- 源码:发送 `09_Net.js:530`;接收 `09_Net.js:534`→`Desk.broadcast`。
|
||||
- 备注:—
|
||||
|
||||
### `connect_agentserver` · route=agent · 双向(切服)
|
||||
- 场景:切换到大厅服务器。
|
||||
- 请求字段(C→S):`Send_connect_agentserver`(`09_Net.js:515`) 透传 `_data`(切服时使用)。
|
||||
- 推送字段(S→C):`agentserver`(新大厅服地址)、`opt`(切换原因,如 `other_break_room`/`free_room`)(09_Net.js:518)。
|
||||
- 源码:发送 `09_Net.js:515`;接收 `09_Net.js:518`→`Net.connect_agentserver`(关闭当前连接并重连新地址)。
|
||||
- 备注:—
|
||||
|
||||
### `playerBehavior` · route=agent(实际走 HTTP GET)
|
||||
- 场景:玩家行为埋点。
|
||||
- 请求字段:原 WS 路径(agentid/gameid/playerid/tag)**被注释**(09_Net.js:799-806),实际改走独立 HTTP GET 上报 `http://test3.1888day.com/api/gamedo/gamedo?agentid=&gameid=&playerid=&tag=`(09_Net.js:807-815)。
|
||||
- 响应字段(S→C):HTTP 回调 `playerBehavior_Succ`/`_Fail`(09_Net.js:818/822);WS 接收函数 `Net.playerBehavior`(`09_Net.js:826`) 当前无触发。
|
||||
- 源码:发送 `09_Net.js:797`(`Send_playerBehavior`)。
|
||||
- 备注:**非 WebSocket 协议**,RpcList 中虽有定义,实际不走 agent 路由。
|
||||
|
||||
---
|
||||
|
||||
## 仅接收的 agent 推送(无对应主动请求)
|
||||
|
||||
| rpc | 推送 data 字段 | 说明 | 接收源码 |
|
||||
|-----|---------------|------|---------|
|
||||
| `update_roomcard` | `roomcard`、`text`(可选) | 房卡变化(仅 `change` 未定义时更新,09_Net.js:383→06_Player.js:141) | 09_Net.js:383 |
|
||||
| `update_bean` | `bean`、`change`(可选)、`seat`(可选)、`type`(可选)、`text` | 豆豆变化(09_Net.js:598→Desk.update_bean,07_Desk.js:1201) | 09_Net.js:598 |
|
||||
| `can_award` | 任务可领取标记 | 🐛 接收即抛异常,见上文 任务系统 章 | 09_Net.js:444 |
|
||||
| `kick_offline` | `fromOther`(可选)、`gameid`(可选) | 被踢下线,弹 OpenKick;同时本地 `Net.submit_error` 上报"收到踢下线包"(07_Desk.js:945-963) | 09_Net.js:448 |
|
||||
| `show_message` | `msg`、`time` | 通用消息提示 → `GameUI.OpenTips`(07_Desk.js:1173) | 09_Net.js:656 |
|
||||
@@ -0,0 +1,306 @@
|
||||
# 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`)。
|
||||
@@ -0,0 +1,331 @@
|
||||
# 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\<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](01-传输层与架构.md))`data` 实测为:
|
||||
> ```json
|
||||
> {"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` 创建房间,仅供参考):
|
||||
```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` |
|
||||
@@ -0,0 +1,294 @@
|
||||
# 05 · 框架↔子游戏桥接 与 外部(H5/小程序)桥接
|
||||
|
||||
> 本篇只讲**框架侧机制**:route 分发判断、发对局包 API、对局包如何进入子游戏、deskinfo 重连、开战入口、外部 deeplink(H5/小程序)进房。
|
||||
> **不展开**具体子游戏的对局 rpc(发牌/出牌/下注/结算字段等由子游戏 `Game_Modify` 自定义实现,框架不感知)。
|
||||
|
||||
## 1. 平台层 vs 游戏内 的分界(route 分发)
|
||||
|
||||
WebSocket 主分发在 `12_Logic.js` 的 `onmessage` 内(约 `12_Logic.js:258-263`):
|
||||
|
||||
```js
|
||||
// 12_Logic.js onmessage 内(约 258-263)
|
||||
if (_msg.route == RouteList.platform || _msg.route == RouteList.agent || _msg.route == RouteList.room) {
|
||||
if (min_ExitsFunction(Net[_msg.rpc])) { // 存在性守卫:表里有该处理函数才调用
|
||||
Net_msg.rpc; // 平台层(02/03 章覆盖)
|
||||
}
|
||||
} else {
|
||||
Game_Modify._ReceiveData(_msg); // 游戏内对局协议 → 交子游戏
|
||||
}
|
||||
```
|
||||
|
||||
- `RouteList.platform = "platform"`、`RouteList.agent = "agent"`、`RouteList.room = "room"`(`02_Const.js:8-10`)。
|
||||
- `AppList.app = "youle"`(`02_Const.js:5`)。
|
||||
- **存在性守卫**:平台分支用 `min_ExitsFunction(Net[_msg.rpc])` 包裹,表里没有对应 `Net[rpc]` 处理函数时**静默丢弃**,不报错。新前端实现分发表时须复现这一点(未知 rpc 不应崩溃)。
|
||||
|
||||
> **关键**:游戏内对局包使用一个**非** `platform/agent/room` 的 `route`,落到 `else` 分支,
|
||||
> 由子游戏的 `Game_Modify._ReceiveData(_msg)` 接管,自行按 `_msg.rpc` 分发。框架对其字段一无所知。
|
||||
|
||||
### ⚠️ 另有一处同形分发,勿混淆
|
||||
|
||||
`09_Net.js:31-37` 里有一段**结构相同**的 route 分发,但它位于 `Net._SendData` 的 **HTTP/Ajax 成功回调内**,且**仅当 `ConstVal.netType != 0`(HTTP 模式)才会执行**:
|
||||
|
||||
```js
|
||||
// 09_Net.js Net._SendData 内(约 19-45)
|
||||
if (ConstVal.netType == 0) {
|
||||
Net.ws_tcp.send(JSON.stringify(_msg)); // 默认:WebSocket,走第 1 节的 onmessage 主分发
|
||||
} else {
|
||||
Func.AjaxHttp2(GameData.Server, _msg, function(_msg, state, input_msg){
|
||||
// —— HTTP 模式下的响应回调,内部才有那段 route 分发(约 31-37)——
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
默认 `netType == 0`(WebSocket),**不进**该分支。它是 HTTP 兜底模式的同步响应处理,**不是**与主分发并列的第二条主链路。**唯一权威的运行时分发是第 1 节的 `12_Logic.js:258-263`。**
|
||||
|
||||
## 2. 本工程状态:子游戏逻辑为空模板
|
||||
|
||||
`Game_Surface_3` 是**平台壳/模板工程**。`01_SubGame/02_SubGame_Input.js` 中 `Game_Modify.*` 全部是**空函数桩**:
|
||||
|
||||
```js
|
||||
// 01_SubGame/02_SubGame_Input.js(约 32-57)
|
||||
Game_Modify._ReceiveData = function(_msg){ } // 接收游戏内数据包(空)
|
||||
Game_Modify.StartWar = function(_msg){ } // 开战(空)
|
||||
Game_Modify.Reconnect = function(_deskinfo){ } // 重连恢复对局(空)
|
||||
Game_Modify.DeskInfo = function(_msg){ } // 未开战自己加入时的牌桌数据(空)
|
||||
```
|
||||
|
||||
**因此本工程内不存在某款具体游戏的对局字段定义。** 要拿到「发牌/出牌/结算」等精确字段,需从**目标子游戏工程**(其 `01_SubGame_modify.js` 有真实实现)提取,或对线上服务器**抓包**。本篇只描述上面这些钩子的**调用时机与实参**(框架契约)。
|
||||
|
||||
## 3. 游戏内发送通道(客户端 → 服务器)
|
||||
|
||||
游戏内操作同样走统一出口 `Net._SendData(_app, _route, _rpc, _data)`(`09_Net.js:12`):
|
||||
|
||||
```js
|
||||
Net._SendData("youle", "<game_route>", "<game_rpc>", {
|
||||
agentid: GameData.AgentId,
|
||||
gameid: GameData.GameId,
|
||||
playerid: C_Player.playerid,
|
||||
roomcode: Desk.roomcode,
|
||||
seat: C_Player.seat,
|
||||
// ... 该操作的业务字段(出牌/下注内容等,子游戏自定义)
|
||||
});
|
||||
```
|
||||
|
||||
- `_route` 用游戏约定值(非 agent/room/platform),服务器据此把包路由给对局逻辑。
|
||||
- 身份字段 `agentid/gameid/playerid/roomcode/seat` 是对局操作的通用前缀(约定俗成,非框架强制)。
|
||||
- 发送/接收的双层包装、心跳、握手规则与平台层**完全相同**(见 01 章)。
|
||||
- 框架不提供 `Net.Send_<game_rpc>` 之类的封装,子游戏直接调 `Net._SendData` 上行;**具体 rpc 名与字段不在框架职责内**。
|
||||
|
||||
## 4. 开战入口(框架侧)
|
||||
|
||||
框架共有三条进入 `Game_Modify.StartWar(_msg)` 的路径,**全部把整包 `_msg` 透传给子游戏**:
|
||||
|
||||
```
|
||||
入口 A · 房主主动开局
|
||||
房主点开始 → Net.Send_self_makewar(data) [route=room](09_Net.js:470-473)
|
||||
→ 服务器回 self_makewar → Net.self_makewar → Desk.self_makewar(_msg)
|
||||
→ Game_Modify.StartWar(_msg)(07_Desk.js:1018)
|
||||
|
||||
入口 B · 他人/自动开战广播
|
||||
服务器广播 other_makewar [route=room](09_Net.js:480-482)
|
||||
→ Desk.makewar(_msg) → Game_Modify.StartWar(_msg)(07_Desk.js:1057)
|
||||
|
||||
入口 C · 进房即已开战(断线/中途进房)
|
||||
Desk.self_join_room 中若 _msg.data.deskwar 为真:
|
||||
→ Desk.stage = 1 → Game_Modify.StartWar(_msg)(07_Desk.js:634-642)
|
||||
```
|
||||
|
||||
> 入口 C 是「进房自动开战」:玩家加入时这局已在打(`deskwar` 真),直接以整包 `_msg` 调 `StartWar`,无需再等 makewar 广播。
|
||||
|
||||
## 5. 进房分支与对局快照(self_join_room)
|
||||
|
||||
`Desk.self_join_room(_msg)`(`07_Desk.js:529` 起)是自己进房的总处理。`isbattle` 仅用于设置 `this.stage`(`07_Desk.js:324`),**不**决定重连。三种分支(`07_Desk.js:634-659`):
|
||||
|
||||
```js
|
||||
Game_Modify.myJoinRoom(_msg); // 总是先回调(07_Desk.js:632)
|
||||
|
||||
if (_msg.data.deskwar) { // ① 进房即已开战
|
||||
Desk.stage = 1;
|
||||
Game_Modify.StartWar(_msg); // 入口 C(07_Desk.js:642)
|
||||
} else {
|
||||
if (_msg.data.deskinfo) { // ② 未开战但有牌桌快照
|
||||
Desk.stage = 1;
|
||||
Game_Modify.DeskInfo(_msg.data.deskinfo); // 实参是 deskinfo,非整包(07_Desk.js:653)
|
||||
} else { // ③ 普通未开战
|
||||
Desk.stage = 0;
|
||||
GameUI.ShowStartScene();
|
||||
C_Player.ChangeExit(1);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `Game_Modify.DeskInfo` 收到的是 **`_msg.data.deskinfo`**(牌桌快照对象),**不是**整包 `_msg`。
|
||||
- `deskinfo` 内部结构由子游戏定义(应含当前轮次、各家手牌/明牌、出牌历史、分数等足以还原牌局的字段)。框架只负责把它原样递进去。
|
||||
|
||||
## 6. 重连恢复(deskinfo)
|
||||
|
||||
断线重连进房时的另一条恢复路径,在 `Desk.self_join_room` 进入主场景分支(`07_Desk.js:419-423`):
|
||||
|
||||
```js
|
||||
GameUI.mainSceneLoaded();
|
||||
if (_msg.data.deskinfo) { // 触发条件是 deskinfo 存在,不是 isbattle==1
|
||||
if (get_self(149,37,0,0,0) == 0 && !GameData.iscloseVideo) {
|
||||
Func.createRoom(); // 开视频相关
|
||||
}
|
||||
Game_Modify.Reconnect(_msg.data.deskinfo); // 实参是 deskinfo(07_Desk.js:423)
|
||||
}
|
||||
```
|
||||
|
||||
- **触发条件是 `_msg.data.deskinfo` 存在**,而非 `isbattle == 1`(旧文档此处有误)。
|
||||
- `Game_Modify.Reconnect` 的实参同样是 **`_msg.data.deskinfo`**,子游戏据此重建对局界面。
|
||||
|
||||
## 7. 结算与相关保留 rpc
|
||||
|
||||
- `RpcList.over_game = "over_game"`(`02_Const.js:29`):结算广播的保留 rpc 名。具体结算字段由子游戏在 `_ReceiveData` 中按自己的 route/rpc 处理,框架不解析。
|
||||
- `RpcList.agentserver_game = "agentserver_game"`(`02_Const.js:50`):对局数据在大厅(agent)服中转的保留 rpc 名。
|
||||
- 结算后资产更新走平台层既有推送(`update_bean` / `update_roomcard` 等,见 02/03 章),与对局协议分属两条链路。
|
||||
|
||||
## 8. 创建房间的双回调(create_room)
|
||||
|
||||
服务器一次 `create_room` 响应会**连续触发两个子游戏钩子**(`09_Net.js:111-119`):
|
||||
|
||||
```js
|
||||
Net.create_room = function(_msg){
|
||||
Desk.create_room(_msg);
|
||||
GameUI.EndLoad();
|
||||
Game_Modify.createRoom(_msg.data.roomtype, _msg.data.infinite); // 无条件调用
|
||||
if (Game_Modify.onCreateRoom) { // 带存在性守卫
|
||||
Game_Modify.onCreateRoom(_msg.data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `createRoom(roomtype, infinite)`:**无条件**调用,传两个标量。
|
||||
- `onCreateRoom(data)`:**带 `if` 守卫**(子游戏可不实现),传整包 `data`。
|
||||
|
||||
## 9. 子游戏向框架暴露的钩子(接口清单)
|
||||
|
||||
新前端虽不用 JS 类,但**必须实现等价逻辑**——这些是框架流程的回调点(`01_SubGame/02_SubGame_Input.js`)。注意实参形态:
|
||||
|
||||
| 钩子 | 触发时机 | 实参 |
|
||||
|------|---------|------|
|
||||
| `_ReceiveData(msg)` | 收到游戏内(非平台层 route)数据包 | 整包 `_msg` |
|
||||
| `StartWar(msg)` | 开战(入口 A/B/C,见第 4 节) | 整包 `_msg` |
|
||||
| `Reconnect(deskinfo)` | 进房发现有 deskinfo,需恢复对局 | **`_msg.data.deskinfo`** |
|
||||
| `DeskInfo(deskinfo)` | 未开战自己加入但有牌桌快照 | **`_msg.data.deskinfo`** |
|
||||
| `createRoom(roomtype, infinite)` | 创建房间成功(无条件) | 两个标量 |
|
||||
| `onCreateRoom(data)` | 创建房间成功(带守卫,可选实现) | 整包 `data` |
|
||||
| `myJoinRoom(msg)` / `playerJoinRoom(seat)` / `playerLeaveRoom(seat)` | 进/离房 | — |
|
||||
| `myExitRoom(seat)` / `breakRoom()` | 自己退房 / 已开局退房 | — |
|
||||
| `playerOffline(seat)` / `playerOnline(seat)` | 玩家上下线 | — |
|
||||
| `playerphonestate(seat,type)` | 玩家电话状态 | — |
|
||||
| `onReady(seat)` | 玩家准备 | — |
|
||||
| `changeSeat(seat1,seat2)` | 换座 | — |
|
||||
| `onSurrender(msg)` | 投降回包 | — |
|
||||
| `Free(msg)` | 解散确认 | — |
|
||||
| `updateScene()` / `closeGameScene()` | 刷新/关闭游戏界面 | — |
|
||||
| `onEnterMainScene(roomtype)` / `onExitMainScene()` | 进/出主场景 | — |
|
||||
| `stopAllSounds()` | 关闭所有游戏声音 | — |
|
||||
|
||||
部分钩子需**返回**房间展示信息(创建/列表界面用):
|
||||
|
||||
| 钩子 | 返回 |
|
||||
|------|------|
|
||||
| `getRoomInfo(roomtype,type,tea)` | 房间描述(每行≤18字符) |
|
||||
| `getFullRoomInfo(roomtype)` | 完整房间描述(每行≤26字符) |
|
||||
| `getRoomTopDescAry(roomtype)` | 房间标题描述字符串数组 |
|
||||
| `getStarLimit(roomtype)` | 星星场准入下限 |
|
||||
| `getMult(roomtype,type)` | 星星场倍数 |
|
||||
| `getLeaveLimit(roomtype)` | 离场限制 |
|
||||
| `getVideoByRoomType(roomtype)` | 是否开视频 0/1 |
|
||||
| `getRoomMode(roomtype)` | 是否金币场 0/1 |
|
||||
|
||||
## 10. 外部桥接:H5 / 小程序唤起进房(deeplink)
|
||||
|
||||
**两套数据来源不同,勿混为一谈。**
|
||||
|
||||
### 10.1 H5:URL 参数 `gameData`
|
||||
|
||||
⚠️ 实际函数段约 `12_Logic.js:1900-1940`。
|
||||
|
||||
```js
|
||||
// 写入侧(生成 deeplink):Logic.setGameData(12_Logic.js:1900-1905)
|
||||
return encodeURI(JSON.stringify({ rpc: _rpc, data: _data }));
|
||||
|
||||
// 读取侧:Logic.getGameDataFromH5(12_Logic.js:1907-1918)
|
||||
var _data = fGetQuery("gameData");
|
||||
if (_data) {
|
||||
_data = decodeURI(_data);
|
||||
GameData.fromH5GameData = JSON.parse(_data); // payload 落到 fromH5GameData
|
||||
}
|
||||
```
|
||||
|
||||
payload 格式:
|
||||
|
||||
```json
|
||||
{ "rpc": "joinRoom", "data": { "roomcode": "<房号>" } }
|
||||
```
|
||||
|
||||
`h5RpcList.joinRoom = "joinRoom"`(`02_Const.js:112`)是目前唯一支持的桥接 rpc。
|
||||
|
||||
### 10.2 小程序:本地存储 `openminigamedata`(不是 URL 参数)
|
||||
|
||||
⚠️ 实际函数段约 `12_Logic.js:2406-2431`。**旧文档的 `miniProData` URL 参数在源码中根本不存在。** 小程序数据来自本地存储:
|
||||
|
||||
```js
|
||||
// Logic.getGameDataFromMiniPro(12_Logic.js:2406-2417)
|
||||
var _data = Utl.ReadData("openminigamedata"); // 读本地存储键 "openminigamedata"
|
||||
if (_data) {
|
||||
_data = decodeURI(_data);
|
||||
GameData.fromMiniProData = JSON.parse(_data); // payload 落到 fromMiniProData
|
||||
}
|
||||
```
|
||||
|
||||
(`openminigamedata` 会在进房等时机被清空,见 `07_Desk.js:283/456/531`。)
|
||||
|
||||
### 10.3 解析后并不直接发包,而是弹确认框
|
||||
|
||||
旧文档「解析后等价触发 `self_join_room`」是错的,且**与源码相反**——解析到 `joinRoom` 后是**弹确认框**,真正发包在用户点确认之后:
|
||||
|
||||
```js
|
||||
// H5:Logic.joinRoomFromH5(12_Logic.js:1919-1940)
|
||||
case h5RpcList.joinRoom:
|
||||
GameData.checkType = 5;
|
||||
GameUI.openCheck("是否进入房间?");
|
||||
//Net.Send_self_join_room(data); // ← 此处发包被注释,不在这里发
|
||||
|
||||
// 小程序:Logic.joinRoomFromMiniPro(12_Logic.js:2418-2431)
|
||||
case h5RpcList.joinRoom:
|
||||
GameData.checkType = 8;
|
||||
GameUI.openCheck("是否进入房间?");
|
||||
```
|
||||
|
||||
确认框点「确定」后在 `11_GameUI.js` 的 `checkType` 分发里真正发包:
|
||||
|
||||
```js
|
||||
// 11_GameUI.js openCheck 确认处理(约 1219 起的 switch(GameData.checkType))
|
||||
case 5: // H5 跳转进房确认(11_GameUI.js:1253-1261)
|
||||
data.roomcode = GameData.fromH5GameData.data.roomcode;
|
||||
Net.Send_self_join_room(data); // ← H5 真正发包点
|
||||
break;
|
||||
```
|
||||
|
||||
🐛 **小程序桥接存在 checkType 撞号 bug**:`joinRoomFromMiniPro` 设 `GameData.checkType = 8` 后弹确认框,但同一确认处理 switch 的 `case 8` 是「删除白名单玩家」`Net.Send_optWhiteList`(`11_GameUI.js:1278-1288`),**并非进房**。即小程序 deeplink 走确认框「确定」时不会发 `self_join_room`,反而触发白名单删除逻辑。(H5 的 `case 5` 正确。)新前端实现小程序唤起时应改用独立的进房 checkType,勿沿用 8。
|
||||
|
||||
## 11. gamemain.js:网络回调统一留空
|
||||
|
||||
`gamemain.js` 把引擎回调转发到框架;但 `tcpconnected` / `tcpmessage` / `tcpdisconnected` / `tcperror` / `httpmessage` **均为空体**(`gamemain.js:125-151`):
|
||||
|
||||
```js
|
||||
gameabc_face.tcpmessage = function(tcpid, data){ ; }; // 空
|
||||
gameabc_face.tcpdisconnected = function(tcpid){ }; // 空
|
||||
// ……其余网络回调同样为空
|
||||
```
|
||||
|
||||
> 对局包**统一从 `12_Logic.js` 的 `onmessage` → `_ReceiveData` 进入**,不走 gamemain 这些钩子。新前端无需在引擎层接网络。
|
||||
|
||||
## 12. 给 CocosCreator 新前端的落地清单(桥接部分)
|
||||
|
||||
1. **route 分发**:接收按 `route` 分流——`platform/agent/room` 查 `Net[rpc]` 表(带存在性守卫),否则进 `Game_Modify._ReceiveData(msg)`;唯一权威分发点对应 `12_Logic.js:258-263`。
|
||||
2. **发对局包**:复用统一出口 `Net._SendData("youle", <game_route>, <game_rpc>, data)`;具体 rpc/字段从目标子游戏工程或抓包补全。
|
||||
3. **开战入口**:实现 A(self_makewar) / B(other_makewar) / C(进房 deskwar 真) 三条都汇入 `StartWar(整包)`。
|
||||
4. **进房快照**:区分 `deskwar`(开战) / `deskinfo`(未开战有快照→`DeskInfo(deskinfo)`) / 普通三分支;重连恢复看 `data.deskinfo` 存在 → `Reconnect(deskinfo)`,**勿**用 `isbattle` 判断。
|
||||
5. **创建房间**:一个响应连发 `createRoom(roomtype,infinite)`(无条件) 与 `onCreateRoom(data)`(可选)。
|
||||
6. **外部桥接**:H5 读 `fGetQuery("gameData")` → `fromH5GameData`;小程序读本地存储 `openminigamedata` → `fromMiniProData`;payload 均为 `{rpc:"joinRoom",data:{roomcode}}`;解析后**弹确认框**,确认后才发 `self_join_room`。实现时给小程序用独立 checkType,避开源码的 case 8 撞号 bug。
|
||||
@@ -0,0 +1,237 @@
|
||||
# 06 · 子游戏开发模式(基于框架)与 CocosCreator 方案
|
||||
|
||||
> 本章讲**框架模板提供给子游戏的钩子契约 + 状态归属 + 子游戏开发模式**,并给出 CocosCreator 重写的两种设计方案。
|
||||
> **不涉及任何一款具体子游戏的对局算法/字段/专属模块**——凡涉及处统一标注"由各子游戏自定义"。
|
||||
> 模板工程以 `Game_Surface_3` 为基准,源码事实均以该工程为准。
|
||||
|
||||
---
|
||||
|
||||
## 1. 一个子游戏工程 = 模板壳 + 三处定制
|
||||
|
||||
对比模板 `Game_Surface_3`,真实子游戏的改动集中在固定几处,**平台层(`00_Surface/*`)几乎原样保留**:
|
||||
|
||||
| 部分 | 模板 | 子游戏 | 是否改动 |
|
||||
|------|------|--------|----------|
|
||||
| `js/00_Surface/*`(平台层) | 完整 | **几乎不动**(与服务器对接的协议层,保持兼容) | ❌ 基本不改 |
|
||||
| `js/01_SubGame/00_SubGame_Config.js` | 默认配置 | 改:人数/聊天气泡位/分享/声音/系统开关等 | ✅ 配置 |
|
||||
| `js/01_SubGame/01_SubGame_modify.js` | 含创建房间界面 + 战绩页,对局留空 | **填**:创建房间界面、战绩定制、对局界面相关 | ✅ 重写 |
|
||||
| `js/01_SubGame/02_SubGame_Input.js` | 全是空桩 | **填**:所有 `Game_Modify.*` 钩子的真实实现(含 `_ReceiveData`) | ✅ 重写 |
|
||||
| `js/gamemain.js` | 纯转发引擎回调,`tcpmessage` 等为空体 | **扩展**:对局精灵初始化、引擎定时器/动画回调驱动对局 | ✅ 定制 |
|
||||
| `js/*.js`(子游戏专属模块) | 无 | **新增**:玩法相关模块(牌型/出牌/理牌/回放等,**由各子游戏自定义**) | ✅ 新增 |
|
||||
| `js/class/*`(OOP 类库,可选) | 无 | **可选新增**:牌型/牌局/算法类库(**由各子游戏自定义**) | ✅ 新增 |
|
||||
| `output/*.min.js` | 模板界面 | **替换**:本游戏的精灵布局数据(编辑器导出) | ✅ 美术 |
|
||||
|
||||
> 一句话:**改服务器对接以上的那一层(`01_SubGame` 三件套 + `gamemain` + 专属模块 + 美术数据)就得到一款新游戏,平台层和协议不动**。这正是"子游戏框架"的价值,也是"服务器零改动"的根因。
|
||||
|
||||
### 加载顺序
|
||||
|
||||
```
|
||||
引擎(gameabc) + Spine
|
||||
→ 00_Surface/00..12 平台层(与模板一致,含 Desk/Net/GameUI/Logic 等)
|
||||
→ 01_SubGame/00,01,02 子游戏三件套
|
||||
→ gamemain.js 子游戏定制的引擎回调
|
||||
→ 专属模块 玩法相关模块(由各子游戏自定义)
|
||||
→ class/* 类库(可选,由各子游戏自定义)
|
||||
→ output/*.min.js 美术布局数据
|
||||
```
|
||||
后加载的 `01_SubGame/02_SubGame_Input.js` 用**真实实现覆盖**了平台默认的 `Game_Modify` 空桩。
|
||||
|
||||
---
|
||||
|
||||
## 2. 子游戏 ↔ 框架的契约(最重要)
|
||||
|
||||
### 2.1 框架 → 子游戏:回调钩子(平台在关键时机调用)
|
||||
|
||||
子游戏在 `02_SubGame_Input.js` 实现这些 `Game_Modify.*` 钩子,模板里它们全是空桩(或仅 `console.log`);平台层在固定时机调用它们,子游戏据此驱动对局界面与状态。下表的"平台调用点"以模板工程 `Game_Surface_3` 的源码为准(`文件:行号`),子游戏内部的具体实现"**由各子游戏自定义**"。
|
||||
|
||||
> 方向均为 **平台 → 子游戏调用**(框架在内部回调子游戏实现的钩子)。
|
||||
|
||||
| 钩子(签名) | 触发时机 / 平台调用点(文件:行) | 说明 |
|
||||
|------|------|------|
|
||||
| `_ReceiveData(_msg)` | 收到对局自定义包时(`12_Logic.js:263`、`09_Net.js:36`) | 对局包入口。子游戏内 `switch(_msg.rpc)` 自分发并刷新对局,rpc 集合由各子游戏自定义 |
|
||||
| `StartWar(_msg)` | 开战(`07_Desk.js:642 / 1018 / 1057 / 1141`) | 开局:初始化牌桌、起手发牌等 |
|
||||
| `Reconnect(_deskinfo)` | 进房响应**含 `deskinfo`** 时(`07_Desk.js:423`) | 断线重连还原对局快照。**触发条件是响应含 `deskinfo`**(见 §2.4);模板为空实现,还原逻辑由各子游戏自定义 |
|
||||
| `DeskInfo(_msg)` | 未开战状态下自己加入、携带牌桌数据时(`07_Desk.js:653`) | 同步未开战阶段的牌桌信息 |
|
||||
| `createRoom(_roomtype,_infinite)` | 收到创建房间回包(`09_Net.js:115`) | 重置对局变量、铺座位等 |
|
||||
| `onCreateRoom(_data)` | 创建房间成功后(`09_Net.js:117`,可选钩子) | 创建房间成功后的子游戏处理 |
|
||||
| `myJoinRoom(_msg)` | 自己进入房间(`07_Desk.js:632`) | 自己入座后的初始化 |
|
||||
| `playerJoinRoom(seat)` | 其他玩家加入(`07_Desk.js:736`) | 同步该座位玩家显示 |
|
||||
| `playerLeaveRoom(seat)` | 其他玩家离开(`07_Desk.js:801`) | 清理该座位显示 |
|
||||
| `myExitRoom(seat)` | 自己退出房间(`07_Desk.js:766`,可选钩子) | 自己离场处理 |
|
||||
| `breakRoom()` | 已开局情况下自己退出(`07_Desk.js:756`) | 开局中退出的清理 |
|
||||
| `playerOffline(seat)` | 玩家离线(`07_Desk.js:891`) | 标记离线态 |
|
||||
| `playerOnline(seat)` | 玩家上线(`07_Desk.js:898`) | 标记在线态 |
|
||||
| `playerphonestate(seat,type)` | 玩家电话状态(`07_Desk.js:974`/`984`,`type` 1=挂断、0=通话/来电) | 电话状态显示 |
|
||||
| `onReady(seat)` | 玩家准备(`07_Desk.js:1136`) | 同步准备态 |
|
||||
| `changeSeat(seat1,seat2)` | 收到换座包(`07_Desk.js:191`,可选钩子) | 同步换座后界面 |
|
||||
| `onSurrender(_msg)` | 收到投降回包(`09_Net.js:621`) | 投降结果处理 |
|
||||
| `Free(_msg)` | 投票解散同意后确认时(`07_Desk.js:873`、`11_GameUI.js:5026`) | 解散结算分支 |
|
||||
| `updateScene()` | 按本地状态重绘界面(`05_Func.js:1642 / 2922`,可选钩子) | 断线恢复 / 切前台时重绘整个对局界面 |
|
||||
| `closeGameScene()` | 关闭游戏界面(`07_Desk.js:427`) | 退出对局界面 |
|
||||
| `onEnterMainScene(roomtype)` | 进入游戏主场景(`11_GameUI.js:4795`) | 进入主场景 |
|
||||
| `onExitMainScene()` | 退出游戏主场景(`11_GameUI.js:3901`) | 退出主场景 |
|
||||
| `onMainMenuScene()` | 显示大厅界面(`11_GameUI.js:3904`,可选钩子) | 回到大厅 |
|
||||
| `onCreateDesk(roomtype)` | 进入游戏界面创建牌桌之前(`12_Logic.js:2125`) | 创建牌桌前的子游戏准备 |
|
||||
| `onGameConfig(_gameConfig)` | 获取到游戏配置时(`12_Logic.js:1825`,仅 game_config 有数据时调用) | 接收服务器下发的游戏配置 |
|
||||
| `onEnterVideo()` | 进入牌局回放时(`08_Utl_Output.js:593`) | 回放入口 |
|
||||
| `calResult(inputArr)` | 倍率结算面板确认(`11_GameUI.js:6556`,参数为倍率数组) | 结算计算回调 |
|
||||
| `onOpenHelp(spid)` | 打开帮助页面(`11_GameUI.js:4720`) | 帮助页处理 |
|
||||
| `onCheckInput(_result)` | 数字输入框确认(`11_GameUI.js:7986`/`7988`) | 数字输入回调 |
|
||||
| `onLocationInfo(_locationInfo)` | 成功获取定位信息(`05_Func.js:2063 / 3087`,可选钩子) | 定位信息回调 |
|
||||
| `onCloseVip()` | 关闭 vip 选项时(`11_GameUI.js:7436`,可选钩子) | 关闭 vip 处理 |
|
||||
| `getShareRoom(_msg)` | 收到星星场(分享房)信息时(`07_Desk.js:1160`,可选钩子) | 星星场房间处理 |
|
||||
| `stopAllSounds()` | 需要静音对局声音时(`05_Func.js:1658 / 2934`、`07_Desk.js:718 / 725 / 772`) | 关闭子游戏声音 |
|
||||
| `shakeEvent()` | 摇一摇事件(`05_Func.js:1958 / 3049`) | 模板示例里据 `GameData.shakeID` 走 `Net.Send_self_makewar()` 开战 |
|
||||
|
||||
**返回类钩子**(平台据返回值渲染大厅 / 房间列表,返回内容**由各子游戏自定义**):
|
||||
|
||||
| 钩子(签名) | 平台调用点(文件:行) | 返回值含义 |
|
||||
|------|------|------|
|
||||
| `getRoomInfo(roomtype,type,tea)` | `11_GameUI.js:6942` | 房间描述文本(`type` 1=系统房间、2=非系统房间) |
|
||||
| `getFullRoomInfo(roomtype)` | `11_GameUI.js:7479 / 9284 / 9337` | 房间全部信息描述文本 |
|
||||
| `getRoomTopDescAry(roomtype)` | `11_GameUI.js:8951` | 房间顶部一组描述(字符数组) |
|
||||
| `getStarLimit(roomtype)` | `11_GameUI.js:6874 / 7167 …` | 星星场准入下限 |
|
||||
| `getMult(roomtype,type)` | `11_GameUI.js:6823 / 6921 / 7168` | 星星场倍数 |
|
||||
| `getLeaveLimit(roomtype)` | `11_GameUI.js:6948` | 离场限制 |
|
||||
| `getVideoByRoomType(roomtype)` | 据 roomtype 决定是否开视频(模板内当前为注释状态) | 0=不开、1=开 |
|
||||
| `getRoomMode(roomtype)` | `11_GameUI.js:1886 / 6721 / 7183 …` | 是否金币场(1/0) |
|
||||
|
||||
> `roomtype` 的具体取值、房间描述文案、各返回值的格式均"**由各子游戏自定义**",本框架文档不展开。
|
||||
|
||||
### 2.2 子游戏 → 框架:可调用的 API(契约清单)
|
||||
|
||||
这是 CocosCreator 重写时**必须在"平台 SDK"侧提供的接口**。子游戏通过这些接口读取框架态、发包、复用平台 UI:
|
||||
|
||||
| 类别 | API | 作用 |
|
||||
|------|-----|------|
|
||||
| **座位/身份** | `Utl.getMySeat()` | 我的绝对座位 |
|
||||
| | `Logic.ChangeToStatus(mySeat, targetSeat)` | 绝对座位 → 以我为视角的相对位(UI 摆位关键) |
|
||||
| | `C_Player.playerid` / `GameData.AgentId` / `GameData.GameId` | 身份 |
|
||||
| **房间状态** | `Desk.roomcode` / `Desk.roomtype` / `Desk.GetPlayerBySeat(seat)` / `Desk.PlayerList` | 房间/座位玩家数据 |
|
||||
| | `Utl.getIsInfinite()` / `Utl.getIsDebugger()` | 房间属性/调试 |
|
||||
| | `Utl.setDeskStage(...)` | 设置牌桌阶段 |
|
||||
| **座位展示** | `Utl.setGrade(seat, value)` | 设置某座积分显示 |
|
||||
| | `Utl.setPlayerPrepare(seat, ...)` / `Utl.getPlayerReadyState(seat)` | 准备态读写 |
|
||||
| **发包(平台 RPC)** | `Net.Send_*(data)` | 平台 RPC(开局/聊天/解散/创建房间…见 02/03 章) |
|
||||
| | `Net.Send_self_makewar(data)` | 主动开战(摇一摇等触发) |
|
||||
| **发包(对局自定义)** | `Net._SendData(_app, _route, _rpc, _data)` | **对局自定义包**(出牌等);`_app/_route/_rpc` 取值由各子游戏自定义 |
|
||||
| **平台 UI** | `GameUI.*`(弹窗/按钮/提示等) | 复用平台通用界面 |
|
||||
| **渲染引擎** | `set_self/get_self/set_group/play_ani/set_clip/ifast_*` | 直接操作精灵(见 00 章) |
|
||||
| **配置/全局** | `Game_Config.*` / `GameData.*` | 配置与全局态 |
|
||||
|
||||
> 平台 RPC 的发包入口与字段见 02/03 章;对局自定义包统一走 `Net._SendData(_app,_route,_rpc,_data)`,其 `_app/_route/_rpc` 与 `_data` 结构"**由各子游戏自定义**"。
|
||||
|
||||
### 2.3 状态归属:框架态 vs 子游戏态
|
||||
|
||||
| | 框架维护 | 子游戏维护 |
|
||||
|---|---------|-----------|
|
||||
| 对象 | `Desk`、`C_Player`、`GameData` | 自建命名空间(对局状态对象,**由各子游戏自定义**) |
|
||||
| 内容 | 房间/座位/玩家公共信息、连接、资产 | 纯对局数据(牌、轮次、结算明细等) |
|
||||
| 来源 | 平台协议(01–04 章) | 对局协议(`_ReceiveData` 收到的 `_msg.data`) + `deskinfo`(重连快照) |
|
||||
|
||||
- **框架态**:由 `00_Surface/*` 平台层统一维护,跟随平台协议(登录/大厅/房间/解散/资产)更新,子游戏**只读不写**。
|
||||
- **子游戏态**:子游戏自建命名空间存放纯对局数据,来源是对局协议包与重连快照 `deskinfo`,结构由各子游戏自定义。
|
||||
|
||||
### 2.4 deskinfo 与断线重连机制(与 05 章一致)
|
||||
|
||||
- **触发条件**:进房响应里**携带 `deskinfo` 字段**时,平台在 `07_Desk.js:423` 调用 `Game_Modify.Reconnect(_msg.data.deskinfo)`。判定依据是"响应含 `deskinfo`"(`07_Desk.js:419` 的 `if(_msg.data.deskinfo)`),**不是** `isbattle==1` 之类的标志位。
|
||||
- **deskinfo 含义**:`deskinfo` 是子游戏对局态的**完整快照**,由服务器在开战时记录、在玩家重连时回发;其内部结构属对局协议,"**由各子游戏自定义**",本框架文档不展开。
|
||||
- **模板实现**:模板里 `Game_Modify.Reconnect` 是**空桩**(`02_SubGame_Input.js`)。"用 `deskinfo` 还原对局状态"的具体逻辑**由各子游戏实现**,模板为空实现。
|
||||
- **新前端约束**:CocosCreator 重写时,对局态必须能从同一份 `deskinfo` 完整还原,才能与旧服务器的重连流程兼容。
|
||||
|
||||
---
|
||||
|
||||
## 3. 对局驱动机制(子游戏的"心跳")
|
||||
|
||||
子游戏不轮询,而是靠**引擎回调**推进,全部经 `gamemain.js`(`gameabc_face.*`)转发到子游戏模块:
|
||||
|
||||
- **`ontimer(...)`**:子游戏用精灵定时器(`set_self(spid, 57, 间隔ms)`)开启,在此驱动动画 / 回合节奏。具体定时器 spid 与节奏"**由各子游戏自定义**"。
|
||||
- **`ani_doend(id,sx,count,allend)`**:动画结束回调,用于串联动画链。
|
||||
- **`gamemydraw / gamemydrawbegin`**:精灵自绘与裁剪(手牌滚动、勾选标记等)。
|
||||
- **`mousedown / mouseup / mousemove`**:对局交互(选牌/滑动/出牌);模板 `gamemain.js` 已把这些引擎回调转发给 `GameUI`、`Game_Modify`、`gameCombat` 三方。
|
||||
- **`gamestart`**:`Logic.AppStart()` 后,子游戏在此把对局精灵组初始化(通常先隐藏)。
|
||||
|
||||
> 网络回调(`tcpmessage / tcpconnected / tcpdisconnected` 等)在 `gamemain.js` 里**保持为空体**——对局包统一从平台的 `_ReceiveData` 进入,不走引擎 TCP。模板 `gamemain.js` 的桥接职责就是"把引擎回调转发给平台/子游戏模块",本身不含业务逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 4. 子游戏开发的通用模式(小结)
|
||||
|
||||
| 维度 | 框架提供 | 子游戏负责 |
|
||||
|------|----------|------------|
|
||||
| 平台协议层 `00_Surface/*` | 完整、稳定,不动 | 只读消费 |
|
||||
| 三件套 `01_SubGame/*` | 空桩契约(钩子签名固定) | 填实现(接入点统一) |
|
||||
| `gamemain.js` | 引擎回调桥接(转发,`tcp*` 空体) | 扩展对局精灵初始化与驱动 |
|
||||
| 对局 rpc(`_ReceiveData`) | 提供入口 | `switch(_msg.rpc)` 自分发,rpc 集合自定义 |
|
||||
| 重连 | 提供 `deskinfo` 快照与 `Reconnect` 钩子 | 实现"快照 → 对局态"的还原 |
|
||||
| 结算 | 提供 `Free` / 倍率面板等通用 UI | 据玩法走结算分支 |
|
||||
| 专属模块 / 类库 / 美术 | 无 | 全部自定义 |
|
||||
|
||||
**结论**:换一款游戏 = 换 `01_SubGame` 三件套 + `gamemain` 定制 + 专属模块 + 美术数据;**框架与协议是稳定底座**。具体玩法(牌型、出牌规则、roomtype 取值、deskinfo 结构、专属模块拆分)均"**由各子游戏自定义**",不在本框架文档范围内。
|
||||
|
||||
---
|
||||
|
||||
## 5. CocosCreator 重写方案(设计建议)
|
||||
|
||||
目标不变:**服务器零改动**。因此无论哪种方案,"平台 SDK + 对局协议"的字节级一致是硬约束(01–05 章),可自由替换的是渲染与工程组织。本节为设计建议,须与前述源码事实(钩子契约、状态归属、deskinfo 重连机制)保持一致。
|
||||
|
||||
### 方案 A:忠实复刻(迁移成本低、风险小)
|
||||
|
||||
把原框架的分层直接映射到 Cocos:
|
||||
|
||||
```
|
||||
NetworkLayer WebSocket 封装:信封{app,route,rpc,data}+双层解包+心跳(01章)
|
||||
PlatformSDK 复刻 00_Surface:登录/大厅/房间/解散/聊天/资产(02–04章协议 1:1)
|
||||
├─ Net Send_*/收包分发,含 _SendData(app,route,rpc,data) 对局自定义包通道
|
||||
├─ Desk 房间/座位状态(04章)
|
||||
├─ Player C_Player(04章)
|
||||
└─ GameUI 大厅/房间通用界面(Cocos 场景/Prefab 重画,不复刻 spid)
|
||||
SubGameModule 对局模块,暴露与 Game_Modify 等价的钩子接口:
|
||||
_ReceiveData(msg) / StartWar / Reconnect(deskinfo) / DeskInfo /
|
||||
myJoinRoom / playerJoinRoom / onReady / Free / getRoomInfo... (§2.1 全套)
|
||||
EventBus 取代 gamemain.js 的统一分发(输入/帧/定时 → UI 与对局)
|
||||
```
|
||||
- 优点:与原游戏行为高度一致,便于逐个对照验证、对接旧 `deskinfo`。
|
||||
- 适合:先快速上线、再逐步现代化。
|
||||
|
||||
### 方案 B:现代化重构(推荐,更高效)
|
||||
|
||||
保留协议层不变,对局层用 Cocos 的能力重做:
|
||||
|
||||
1. **协议层 TypeScript 化**:把 RpcList / 字段定义成 TS `interface` 与枚举,收发用泛型包裹,编译期校验字段。
|
||||
```ts
|
||||
interface Envelope<T> { app: string; route: string; rpc: string; data: T }
|
||||
```
|
||||
2. **平台 SDK 做成独立模块/npm 包**:`NetClient`(连接/心跳/解包) + `PlatformApi`(login/room/...) + `RoomStore`(响应式房间态)。多款游戏复用,等价于原 `00_Surface`。
|
||||
3. **对局用 Cocos 组件化**:场景=Scene,玩家位/手牌/牌桌=Prefab+组件;动画用 Cocos Animation/tween 取代引擎定时器与 `play_ani`;骨骼继续用 Spine(原框架也用 Spine)。
|
||||
4. **对局接入用接口而非全局函数**:定义 `IGameModule { onReceive(rpc,data); onReconnect(deskinfo); render(state) }`,平台 SDK 通过它回调对局;对局通过注入的 `sdk` 调 `sendAction()/getMySeat()/seatToView()`。这与原框架 `Game_Modify.*` 钩子 + 子游戏调 `Net/Desk/Utl` 的边界一一对应。
|
||||
5. **状态驱动渲染**:对局态集中在一个 store(对应原框架的子游戏命名空间),`render(state)` 纯函数刷新。**重连**:响应含 `deskinfo` 时 `onReconnect(deskinfo)` 把快照灌入 store——与原框架"响应含 deskinfo 即触发 Reconnect"的判定一致(§2.4)。
|
||||
6. **座位视角换算保留**:实现 `seatToView(mySeat,target)` 等价 `Logic.ChangeToStatus`,对局只关心"相对位"。
|
||||
|
||||
建议工程结构:
|
||||
```
|
||||
assets/
|
||||
scripts/
|
||||
net/ NetClient, Envelope, heartbeat, reconnect
|
||||
platform/ PlatformApi(02/03章), RoomStore/PlayerStore(04章), 通用UI
|
||||
game/<玩法>/ GameModule(实现 IGameModule), state, view 组件, prefab
|
||||
core/ EventBus, seatUtil, types(协议 TS 定义)
|
||||
```
|
||||
|
||||
### 两方案对比
|
||||
|
||||
| | 方案 A 复刻 | 方案 B 现代化 |
|
||||
|---|-----------|--------------|
|
||||
| 协议兼容 | ✅ | ✅(不动协议) |
|
||||
| 上手速度 | 快 | 中 |
|
||||
| 多游戏复用 | 一般 | 强(SDK 独立) |
|
||||
| 可维护性/类型安全 | 一般 | 强 |
|
||||
| 适合 | 首款快速验证 | 长期多游戏平台 |
|
||||
|
||||
> **共同底线**:把"平台 SDK"和"对局模块"边界划清(即 §2.1 钩子契约 + §2.2 API 清单 + §2.4 重连机制),协议字段严格对齐 01–05 章。这样新前端对服务器而言与旧前端**不可区分**,即达成"完美适配、服务器零改动"。
|
||||
|
||||
---
|
||||
|
||||
## 6. 待补:具体对局字段
|
||||
|
||||
本章聚焦框架使用模式与钩子契约,**未列任何对局包字段**。某款子游戏的对局 rpc 详细字段(`StartWar/Free/deskinfo` 等结构)、roomtype 取值、专属模块划分等,均属该子游戏范畴;可在确定首款游戏后,对其 `_ReceiveData` 各 case 与发包点做一次专项提取,补入 05 章。
|
||||
@@ -0,0 +1,833 @@
|
||||
# 平台模板 RPC 全量清单
|
||||
|
||||
> 核对日期:2026-09-05;基线:codex/local-platform-login @ 556c47a。本文是后续联调的索引与覆盖快照,不替代 01–05 章的字段契约,也不表示清单内接口已经验收。
|
||||
|
||||
## 范围与计数
|
||||
|
||||
- 共 **86 个唯一名称**:旧 RpcList 的 **82 个**(重复赋值按名称去重),加表外 submit_error、show_message、refresh_task_state,以及 h5RpcList.joinRoom。
|
||||
- 这是 Game_Surface_3 平台模板的全量:包含大厅、房间、纯推送、历史/保留名、HTTP 特例和外部桥接。具体子游戏自定义 route/rpc 没有在模板中穷举定义,留到子游戏阶段;本轮不选择或接入真实子游戏。
|
||||
- 86 是索引名称数,不是 86 个可直接调用的 WebSocket 接口。submit_log 不另计;握手 @toconcon、心跳 @serverheartbeat 和 webserve-服务器未工作 是传输控制数据,不是业务 RPC。
|
||||
- platform 是合法路由名称,但本次模板发送扫描未发现独占 route=platform 的命名业务请求;不复制 agent/room 名称来凑第三份清单。纯推送的路由归档不是当前本地服务器抓包证明。
|
||||
|
||||
## 接入与验收状态口径
|
||||
|
||||
- **L**:本地真实 Cocos 启动、加载、游客登录已验收;仅 player_login。依据[验收记录](../architecture/current-status.md)。
|
||||
- **R**:新 PlatformRuntime 的严格平台分发表已接入(共 11 个,含 L),仅表示源码处理链存在;除 L 外,当前批次没有真实 RPC 成功验收证据。
|
||||
- **N**:不在新 PlatformRuntime 平台分发表中;不代表旧 H5 或旧 PlatformSession 完全没有实现。协议常量存在、旧 room-handlers 存在,均不能算新 Runtime 已接入。
|
||||
- **X**:保留、HTTP 特例或本地桥接,不按普通 WS 平台命令判定。具体子游戏生命周期支持也不等于其某个对局 rpc 已实现。
|
||||
- 当前 LocalPlatformLogin 联调入口只允许发 agent/player_login;R 中的房间/切服条目仍受测试边界阻断。本文不放宽测试入口。
|
||||
- 条目中“旧发送/接收函数”只表示静态定义,不能推断调用点活跃或服务器确实响应。请求/响应摘要取自对应协议章节;未明确之处保留待核实,不补字段。
|
||||
|
||||
## 发现的差异与注意事项
|
||||
|
||||
1. refresh_task_state:当前旧源码引用未定义常量;02 章称硬编码字符串,已不符。新 routes.ts 也未收录。
|
||||
2. submit_error、show_message:旧路径确有硬编码发送/命名接收器,但不在旧 RpcList,也不在新 routes.ts;不能只扫常量表。
|
||||
3. submit_log:实际 rpc 仍为 submit_error;change_room 的响应为 change_seat;quick_enter_share_room 没有同名接收器。
|
||||
4. README 仍写 WS 双层包装,而 01 章已更正为单层 {app,route,rpc,data};MessageEvent.data 是浏览器事件壳。以 01 章与已验收传输实现为准。
|
||||
5. 02 章登录 version:string 与本地已验收 number 值有差异;openid/unionid 不得依据历史“游客可空”注释省略。
|
||||
6. 新 routes.ts 的注释分组不是权威 rpc→route 映射,例如 create_room/self_join_room/get_player_grade1/get_player_grade2 在 room 注释区域,但旧发送实际为 agent;本清单按协议与发送源码归档。
|
||||
7. can_award 的旧接收器调用未定义 C_Player.can_award;五个社交保留名没有旧 Net 接收器;不能按完整可用协议验收。
|
||||
8. update_roomcard/update_bean:05_Func.js 的原生 yPaytype 与 WVJB yPaytype 回调直接上行,02 章“仅接收、无主动请求”不完整;已在条目中补齐,不能划为只读接口。
|
||||
9. 本轮仅整理清单,未修改上述历史文档、业务代码、服务端或任何 Cocos 序列化资源。
|
||||
|
||||
## 配置来源与批次边界
|
||||
|
||||
- agentid/gameid/channelid/marketid/version 继续由现有配置来源经 runtime-config/local-startup 解析;各 RPC 只携带它所要求的字段,不能为统一外观给所有请求塞上整套身份。规范字段名为 channelid,不能写 chanelid。
|
||||
- openid/unionid 与游客资料由现有 visitor-account 来源读取缓存或生成;登录后的 playerid 来自服务器与 Store。查询目标 playerid 与当前登录 playerid 的语义要逐项区分。
|
||||
- roomcode/seat/shortcode/roomtype/deskinfo 等属于后续房间或子游戏范围;短信、原生数据、支付渠道等有各自前置条件,不能生成假值补齐。
|
||||
- 后续优先梳理非房间平台 RPC 的只读查询,再逐项处理写操作和推送;房间相关及子游戏最后进行。列入本清单不等于授权执行。
|
||||
|
||||
## 按功能快速浏览
|
||||
|
||||
- **大厅 · 登录 / 账号(6)**:`player_login`、`query_player2`、`binding_phone`、`send_phone_checkcode`、`send_phone_code_wechat`、`setSign`。
|
||||
- **大厅 · 房间创建 / 进入(大厅侧)(8)**:`create_room`、`self_join_room`、`quick_enter_share_room`、`advanced_roomlist`、`advanced_createroom`、`get_share_room`、`getInfoByShortCode`、`switchRoomList`。
|
||||
- **大厅 · 战绩 / 排行 / 财富(5)**:`get_player_grade1`、`get_player_grade2`、`get_treasurelist`、`getShortCodeRankList`、`getVipRankList`。
|
||||
- **大厅 · 任务系统(5)**:`get_player_task`、`player_finish_task`、`get_task_award`、`refresh_task_state`、`can_award`。
|
||||
- **大厅 · 支付 / 充值 / 资产(4)**:`get_paylist`、`pay_succ`、`topup_card`、`giveCoin`。
|
||||
- **大厅 · 仓库 / 星星 / 魅力(4)**:`set_bankpwd`、`change_star`、`update_charm`、`setAllCharm`。
|
||||
- **大厅 · 邀请码 / 绑定(2)**:`binding_invitecode`、`get_player_invitecode`。
|
||||
- **大厅 · VIP 管理 / 黑白名单(4)**:`optBanList`、`getPlayerWhiteList`、`optWhiteList`、`setVipForbidSelect`。
|
||||
- **大厅 · 其它 agent 协议(8)**:`submit_opinion`、`submit_location`、`submit_phoneinfo`、`submit_error`、`kick_server`、`broadcast`、`connect_agentserver`、`playerBehavior`。
|
||||
- **房间 · 房间生命周期(8)**:`self_break_room`、`other_break_room`、`self_exit_room`、`other_exit_room`、`player_prepare`、`change_room`、`share_room`、`change_seat`。
|
||||
- **房间 · 进房推送(他人视角)(3)**:`other_join_room`、`other_offline`、`other_online`。
|
||||
- **房间 · 解散投票(开局后)(8)**:`self_apply_free_room`、`other_apply_free_room`、`self_agree_free_room`、`other_agree_free_room`、`self_refuse_free_room`、`other_refuse_free_room`、`free_room`、`beanroom_surrender`。
|
||||
- **房间 · 开局(2)**:`self_makewar`、`other_makewar`。
|
||||
- **房间 · 房间内社交(6)**:`send_text`、`send_voice`、`send_gift`、`send_phiz`、`call_phone`、`hangup_phone`。
|
||||
- **保留名称 · 房间社交(5)**:`receive_chat`、`play_voice`、`other_send_gift`、`other_callphone`、`other_hangup`。
|
||||
- **大厅 · 资产更新与系统推送(4)**:`update_bean`、`update_roomcard`、`kick_offline`、`show_message`。
|
||||
- **房间 · 服务器切换(room 侧)(1)**:`connect_roomserver`。
|
||||
- **子游戏 · 框架保留名称(2)**:`over_game`、`agentserver_game`。
|
||||
- **外部桥接 · 非网络 RPC(1)**:`joinRoom`。
|
||||
|
||||
## 全量条目
|
||||
|
||||
### 大厅 · 登录 / 账号(6)
|
||||
|
||||
#### 01. player_login
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**L / R**;新 Rpc 常量:有。
|
||||
- 用途:连接建立后 `onopen` 自动发送;断线重连、切服后重发(12_Logic.js:35、:61 构造,09_Net.js:122 `Send_login` 注入)。
|
||||
- 请求:构建身份 agentid/gameid/channelid/marketid/version;账号 openid/unionid/nickname/avatar/sex/province/city;设备与环境 machineid/machineroom/location/ip;按来源契约携带设备登录和缓存 playerid 等可选字段。完整结构见登录契约与 02/04 章。
|
||||
- 响应/推送:state(0 成功);账号/资产字段,以及可能存在的房间恢复快照。完整结构见 04 章与当前 login-contract;本批只验收不在房的成功登录。
|
||||
- 注意:真实本地启动→加载→游客登录已验收;version=1(number)已被本地服务器接受。02 章 version:string、游客 openid 可空的描述不能作为本地联调输入依据;本地服务要求 openid 与 unionid。房间恢复不在已验收范围。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_login、player_login。
|
||||
|
||||
#### 02. query_player2
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:仓库转账前查询目标玩家信息。
|
||||
- 请求:`agentid`、`playerid`(目标玩家ID)。
|
||||
- 响应/推送:`avatar`(string)、`nickname`(string)、`playerid`(int);昵称/头像均空时提示"未找到对应玩家"。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_query_player2、query_player2。
|
||||
|
||||
#### 03. binding_phone
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:绑定手机号。
|
||||
- 请求:`agentid`、`playerid`、`phonenum`、`smmcode`(短信验证码)。
|
||||
- 响应/推送:`phonenum`(string,回写 `C_Player.tel`)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_binding_phone、binding_phone。
|
||||
|
||||
#### 04. send_phone_checkcode
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:发送手机短信验证码。
|
||||
- 请求:`agentid`、`phonenum`(构造点未集中定位,至少含手机号)。
|
||||
- 响应/推送:无业务字段(`Desk.send_phone_checkcode` 为空实现,07_Desk.js:1342)。
|
||||
- 注意:⚠️ 请求字段以后端实现为准。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_send_phone_checkcode、send_phone_checkcode。
|
||||
|
||||
#### 05. send_phone_code_wechat
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:微信渠道发送手机验证码(绑定手机流程的另一入口)。
|
||||
- 请求:`agentid`、`phonenum`(唯一调用点 11_GameUI.js:2620-2623 当前被注释,按注释代码为 agentid+phonenum)。
|
||||
- 响应/推送:无业务字段(`Desk.send_phone_code_wechat` 为空实现,07_Desk.js:1345)。
|
||||
- 注意:⚠️ 框架完整定义并接线,但**当前前端唯一调用点(11_GameUI.js:2623)被注释,实际不发送**;字段以后端实现为准。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_send_phone_code_wechat、send_phone_code_wechat。
|
||||
|
||||
#### 06. setSign
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:设置个性签名。
|
||||
- 请求:`agentid`、`playerid`、`sign`。
|
||||
- 响应/推送:`sign`(string,回显写入 `C_Player.sign`)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_setSign、setSign。
|
||||
|
||||
### 大厅 · 房间创建 / 进入(大厅侧)(8)
|
||||
|
||||
#### 07. create_room
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:玩家创建房间。
|
||||
- 请求:`agentid`、`playerid`、`gameid`、`roomtype`(房间类型配置数组,见 doc04);`Send_create_room` 自动注入 `ip`=`C_Player.ip`、`location`=`C_Player.addr`(09_Net.js:106-107)。
|
||||
- 响应/推送:`state`(0成功)、`roomcode`、`seat`、`roomtype`、`makewar`、`asetcount`、`shortcode`、`infinite`;失败 `showerror`/`error`。详见 [04 → 房间响应公共字段](04-数据结构.md#房间创建--进入响应公共字段)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_create_room、create_room。
|
||||
|
||||
#### 08. self_join_room
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**R**;新 Rpc 常量:有。
|
||||
- 用途:输入房号 / 快速加入 / H5 唤起 / 进 VIP 配置房。
|
||||
- 请求:`agentid`、`playerid`、`gameid`、`roomcode`(房号);`Send_self_join_room` 自动注入 `location`=`C_Player.addr`、`ip`=`C_Player.ip`(09_Net.js:254-255);进 VIP 配置房入口额外带 `vipMatch:1`(12_Logic.js:2171);比赛进房入口可带 `match_id`。
|
||||
- 响应/推送:`state`、`roomcode`、`seat`、`isowner`、`players[]`、`roomtype`、`makewar`、`asetcount`、`deskwar`、`deskinfo`(重连快照)。详见 [04 → 房间响应公共字段](04-数据结构.md#房间创建--进入响应公共字段)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_self_join_room、self_join_room。
|
||||
|
||||
#### 09. quick_enter_share_room
|
||||
|
||||
- 路由/通道:agent;方向:C→S;结果经其它 rpc 返回;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:快速进入分享/星星场房间。
|
||||
- 请求:`agentid`、`playerid`、`gameid`、`type`(房间类型)、`roomtype`(可选)。
|
||||
- 响应/推送:走 self_join_room 进房流程(无独立 `Net.quick_enter_share_room` 接收函数,结果通过 self_join_room/show_message 等回包)。
|
||||
- 注意:无 Net.quick_enter_share_room;关联 self_join_room/show_message,不能按同名请求响应配对。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_quick_enter_share_room。
|
||||
|
||||
#### 10. advanced_roomlist
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:拉取 VIP/高级房间列表。
|
||||
- 请求:`agentid`、`playerid`、`gameid`。
|
||||
- 响应/推送:房间列表对象,整包写入 `GameData.snrRoomList` 并渲染(07_Desk.js:1176)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_advanced_roomlist、advanced_roomlist。
|
||||
|
||||
#### 11. advanced_createroom
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:创建 VIP/高级房间。
|
||||
- 请求:`agentid`、`gameid`、`playerid`、`tea`(茶水费)、`infinite`(0/1无限局)、`roomtype`、`videoConfig`(可选)、`rebateLimit`(可选)、`rebateType`(可选)。
|
||||
- 响应/推送:`tea`、`rebateLimit` 及房间配置(整包写入 `GameData.snrRoomList`,并回拉 advanced_roomlist,07_Desk.js:1180)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_advanced_createroom、advanced_createroom。
|
||||
|
||||
#### 12. get_share_room
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:获取分享/星星场房间列表(仅非大厅环境发送)。
|
||||
- 请求:`agentid`、`playerid`、`gameid`。
|
||||
- 响应/推送:房间数组(`Desk.get_share_room` / `Game_Modify.getShareRoom` 处理)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_get_share_room、get_share_room。
|
||||
|
||||
#### 13. getInfoByShortCode
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:按短码批量查询房间信息(VIP 房列表)。
|
||||
- 请求:`agentid`、`gameid`、`shortcodeList`(短码列表)。
|
||||
- 响应/推送:`roomInfo`(短号房间信息) → `GameUI.setVipRoomListData`(07_Desk.js:1284)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_getInfoByShortCode、getInfoByShortCode。
|
||||
|
||||
#### 14. switchRoomList
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:开关房间在列表中的可见/可进入状态。
|
||||
- 请求:`agentid`、`playerid`、`gameid`、`isClose`(0开/1关)。
|
||||
- 响应/推送:`state`(0成功)、`isClose`;失败 `error`(07_Desk.js:1267)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_switchRoomList、switchRoomList。
|
||||
|
||||
### 大厅 · 战绩 / 排行 / 财富(5)
|
||||
|
||||
#### 15. get_player_grade1
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:拉取战绩(类型1)。
|
||||
- 请求:`agentid`、`playerid`、`gameid`、`type`(可选)、`direction`(可选,翻页)、`gradeidx`(可选,分页索引)。
|
||||
- 响应/推送:战绩数据(`gameCombat.get_player_grade1` 渲染)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_get_player_grade1、get_player_grade1。
|
||||
|
||||
#### 16. get_player_grade2
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:拉取战绩(类型2)。
|
||||
- 请求:透传调用方 `_data`,构造点未集中定位;至少含 `agentid`/`playerid`/`gameid`。
|
||||
- 响应/推送:战绩数据(`gameCombat.get_player_grade2` 渲染)。
|
||||
- 注意:⚠️ 请求字段构造点未定位,待核对。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_get_player_grade2、get_player_grade2。
|
||||
|
||||
#### 17. get_treasurelist
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:财富榜。
|
||||
- 请求:`agentid`、`gameid`。
|
||||
- 响应/推送:`list`(排行数组) → `Desk.get_treasurelist`。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_get_treasurelist、get_treasurelist。
|
||||
|
||||
#### 18. getShortCodeRankList
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:短号场排行榜。
|
||||
- 请求:`agentid`、`playerid`、`shortcode`。
|
||||
- 响应/推送:成功为排行数据(整包写入 `GameData.vipRank.data`);失败 `error:true` + `message`(07_Desk.js:1311)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_getShortCodeRankList、getShortCodeRankList。
|
||||
|
||||
#### 19. getVipRankList
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:VIP 排行榜。
|
||||
- 请求:`agentid`、`limit`(条数)。
|
||||
- 响应/推送:`list`(VIP排行数组) → `GameData.rankList`(07_Desk.js:1328)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_getVipRankList、getVipRankList。
|
||||
|
||||
### 大厅 · 任务系统(5)
|
||||
|
||||
#### 20. get_player_task
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:获取任务列表。
|
||||
- 请求:`agentid`、`playerid`、`gameid`。
|
||||
- 响应/推送:`tasks`(任务数组) → `Desk.get_player_task`。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_get_player_task、get_player_task。
|
||||
|
||||
#### 21. player_finish_task
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:上报任务完成(如分享成功触发,06_Player.js:414)。
|
||||
- 请求:`agentid`、`playerid`、`taskid`。
|
||||
- 响应/推送:`state`(int)——`state==1` 且当前 `taskstate==0` 时把 `C_Player.taskstate` 置 1(06_Player.js:476)。
|
||||
- 注意:原文档"响应含 `taskstate`"无源码依据,已删除——接收函数仅读 `_msg.data.state`。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_player_finish_task、player_finish_task。
|
||||
|
||||
#### 22. get_task_award
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:领取任务奖励。
|
||||
- 请求:`agentid`、`playerid`、`taskid`(任务领取点击与 Net 发送层均不补 gameid,见 02 文档源码复核说明)。
|
||||
- 响应/推送:`taskid`(对应任务 state 置 2=已领取)、`taskstate`(写入 `C_Player.taskstate`)(06_Player.js:482)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_get_task_award、get_task_award。
|
||||
|
||||
#### 23. refresh_task_state
|
||||
|
||||
- 路由/通道:agent;方向:C→S 意图;当前旧代码缺常量,不能认定可发送;状态:**N**;新 Rpc 常量:无。
|
||||
- 用途:刷新任务可领取状态。
|
||||
- 请求:透传 `_data`(构造点未集中定位,至少含 `agentid`/`playerid`)。
|
||||
- 响应/推送:服务器推送 `can_award`(见下条)。
|
||||
- 注意:本次核实:09_Net.js:440 用 RpcList.refresh_task_state,但 02_Const.js 无此定义;全模板搜索未找到赋值。未被外部注入时该值为 undefined,JSON.stringify 会省略 rpc。02 章“硬编码字符串”的记载与当前源码不符,待后续专项核对;本轮不修代码。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_can_award。
|
||||
|
||||
#### 24. can_award
|
||||
|
||||
- 路由/通道:agent;方向:S→C 推送;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:服务器通知有任务可领取。
|
||||
- 请求:无(纯推送)。
|
||||
- 响应/推送:任务可领取标记(载荷字段待后端确认)。
|
||||
- 注意:🐛 `06_Player.js` **未定义** `Player.prototype.can_award`(全文无此方法)。推送一旦到达,`C_Player.can_award` 为 undefined,调用即抛 TypeError。当前为 bug,不可当正常协议使用。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_can_award、can_award。
|
||||
|
||||
### 大厅 · 支付 / 充值 / 资产(4)
|
||||
|
||||
#### 25. get_paylist
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:拉取支付项列表。
|
||||
- 请求:`agentid`。
|
||||
- 响应/推送:`paylist`(支付项数组) → `GameData.payList`,并打开支付界面(09_Net.js:567)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_get_paylist、get_paylist。
|
||||
|
||||
#### 26. pay_succ
|
||||
|
||||
- 路由/通道:HTTP(保留 agent 信封);方向:C→HTTP;未见同名 Net 接收函数;状态:**X**;新 Rpc 常量:有。
|
||||
- 用途:支付成功后通知服务器入账。
|
||||
- 请求:`agentid`、`playerid`、`channelid`、`productid`、`payid`、`amount`、`money`、`paytype`(05_Func.js:2240-2254 构造)。
|
||||
- 响应/推送:无显式 WS 回包;资产变化通过 `update_bean`/`update_roomcard` 推送(充房卡场景前端还会本地 `UpdateRoomcard`,05_Func.js:2263)。
|
||||
- 注意:原工程支付流程使用 HTTP;存在 Send_pay_succ 定义,但调用点被注释。不得仅凭常量/发送函数判为活跃 WS 请求。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_pay_succ。
|
||||
|
||||
#### 27. topup_card
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:充值卡兑换。
|
||||
- 请求:`agentid`、`playerid`、`cardno`(卡号)。
|
||||
- 响应/推送:`Desk.topup_card` 为空实现(07_Desk.js:1365),资产变化经 update_bean/update_roomcard 推送。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_topup_card、topup_card。
|
||||
|
||||
#### 28. giveCoin
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:仓库面板向他人转账豆豆/金币。
|
||||
- 请求:`agentid`、`playerid`(转出)、`toPlayerid`(转入目标ID)、`gameid`、`count`(数量)、`password`(仓库密码)(11_GameUI.js:2132-2139)。
|
||||
- 响应/推送:`state`(0成功)、`star2`(转出后**仓库星星**数 → `setWareHouseStarCOunt`,**非豆豆**);失败 `showerror`/`error`(07_Desk.js:1381)。
|
||||
- 注意:响应 `star2` 为仓库星星数,注意区别于豆豆余额。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_giveCoin、giveCoin。
|
||||
|
||||
### 大厅 · 仓库 / 星星 / 魅力(4)
|
||||
|
||||
#### 29. set_bankpwd
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:设置仓库密码。
|
||||
- 请求:`agentid`、`playerid`、`unionid`、`password`。
|
||||
- 响应/推送:`state`(0成功)、`password`;失败 `showerror`/`error`(07_Desk.js:1228)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_set_bankpwd、set_bankpwd。
|
||||
|
||||
#### 30. change_star
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:仓库存/取(豆豆 ↔ 仓库星星)。
|
||||
- 请求:`agentid`、`playerid`、`mode`(0存入/1取出,源码 `safeInputType-1`)、`password`(仓库密码,字符串)、`count`(数量)(11_GameUI.js:2065-2069 调用点齐全携带此 5 字段)。
|
||||
- 响应/推送:`state`(0成功)、`star1`(更新后豆豆余额 → `update_bean2`)、`star2`(更新后仓库星星数 → `setWareHouseStarCOunt`)、`msg`(可选提示)、`count`(可选,回填安全输入);失败 `showerror`/`error`(07_Desk.js:1238)。
|
||||
- 注意:原审计疑虑"`mode`/`password` 未见"——经核对仓库存/取调用点(11_GameUI.js:2068-2069)**确含** `mode` 与 `password`,文档正确,疑虑解除。审计提到的"agentid/playerid/toPlayerid/gameid/count"实为相邻的 `giveCoin` 转账包(11_GameUI.js:2132-2139),并非本 rpc。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_change_star、change_star。
|
||||
|
||||
#### 31. update_charm
|
||||
|
||||
- 路由/通道:agent;方向:S→C 推送;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:座位魅力值更新。
|
||||
- 请求:`Net.Send_update_charm`(`09_Net.js:727`) 已定义但**无任何调用点**;实际只作服务器→客户端推送。
|
||||
- 响应/推送:`seatlist`:[ {`seat`(int), `charm`(number)} ](07_Desk.js:1260 遍历 setCharm)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_update_charm、update_charm。
|
||||
|
||||
#### 32. setAllCharm
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:批量设置总魅力。
|
||||
- 请求:`agentid`、`playerid`、`gameid`、`value`(11_GameUI.js:1269-1275)。
|
||||
- 响应/推送:无显式业务字段,`Desk.setAllCharm` 仅本地存储并提示成功(07_Desk.js:1321)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_setAllCharm、setAllCharm。
|
||||
|
||||
### 大厅 · 邀请码 / 绑定(2)
|
||||
|
||||
#### 33. binding_invitecode
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:绑定邀请码。
|
||||
- 请求:`agentid`、`playerid`、`invitecode`(11_GameUI.js:1367-1369)。
|
||||
- 响应/推送:`state`(0成功时写入 `invitecode`)、`invitecode`、`error`(提示文案)(06_Player.js:251)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_binding_invitecode、binding_invitecode。
|
||||
|
||||
#### 34. get_player_invitecode
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:获取自己的邀请码(打开绑定界面)。
|
||||
- 请求:`agentid`、`playerid`、`unionid`、`openid`(11_GameUI.js:1378-1382)。
|
||||
- 响应/推送:`invitecode`(string) → `C_Player.setInvitecod` 并打开绑定界面(09_Net.js:607)。
|
||||
- 注意:修正原文档——请求字段为 `agentid/playerid/unionid/openid`,**无 `gameid`**,新增 `unionid`/`openid`(11_GameUI.js:1378)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_get_player_invitecode、get_player_invitecode。
|
||||
|
||||
### 大厅 · VIP 管理 / 黑白名单(4)
|
||||
|
||||
#### 35. optBanList
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:黑名单查看/添加/移除(多入口)。
|
||||
- 请求:随入口不同,公共字段 `agentid`、`playerid`、`type`:
|
||||
- 响应/推送:`type`(1/3/4/5/6)、`banList`(黑名单数组)、`message`(可选提示)(07_Desk.js:1289)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_optBanList、optBanList。
|
||||
|
||||
#### 36. getPlayerWhiteList
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:获取白名单。
|
||||
- 请求:`agentid`、`playerid`、`gameid`。
|
||||
- 响应/推送:`whiteList`(数组) → `GameData.whiteList.data`(07_Desk.js:1412)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_getPlayerWhiteList、getPlayerWhiteList。
|
||||
|
||||
#### 37. optWhiteList
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:白名单添加/修改/删除。
|
||||
- 请求:`agentid`、`playerid`、`gameid`、`shortcode`、`mode`、`userid`(目标ID):
|
||||
- 响应/推送:`whiteList`(更新后数组)、`mode`(可选)、`message`(可选提示)(07_Desk.js:1398)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_optWhiteList、optWhiteList。
|
||||
|
||||
#### 38. setVipForbidSelect
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:VIP 房禁止玩家选桌开关。
|
||||
- 请求:`agentid`、`playerid`、`gameid`、`forbidSelect`(1开/0关,11_GameUI.js:2631-2639)。
|
||||
- 响应/推送:`state`(0成功)、`forbidSelect`(1开/0关);失败 `error`(07_Desk.js:1348)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_setVipForbidSelect、setVipForbidSelect。
|
||||
|
||||
### 大厅 · 其它 agent 协议(8)
|
||||
|
||||
#### 39. submit_opinion
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:提交反馈/意见。
|
||||
- 请求:`agentid`、`playerid`、`gameid`、`content`。
|
||||
- 响应/推送:`state`(0成功)(07_Desk.js:1100)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_submit_opinion、submit_opinion。
|
||||
|
||||
#### 40. submit_location
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:提交定位信息(仅非大厅环境发送)。
|
||||
- 请求:`agentid`、`playerid`、`info`(定位对象)。
|
||||
- 响应/推送:经 `Game.submit_location` 处理(09_Net.js:583)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_submit_location、submit_location。
|
||||
|
||||
#### 41. submit_phoneinfo
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求(无响应处理);状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:提交手机/通讯录信息(仅非大厅环境发送)。
|
||||
- 请求:`agentid`、`playerid`、`info`{ `phoneInfo`(手机信息), `addrBook`(通讯录) }。
|
||||
- 响应/推送:`Net.submit_phoneinfo` 接收函数为空实现(09_Net.js:722)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_submit_phoneinfo、submit_phoneinfo。
|
||||
|
||||
#### 42. submit_error
|
||||
|
||||
- 路由/通道:agent;方向:C→S;同名回包被旧接收器忽略;状态:**N**;新 Rpc 常量:无。
|
||||
- 用途:异常/日志上报(Net.submit_error 与 Net.submit_log 共用同一个实际 rpc)。
|
||||
- 请求:`packet`(出错数据包字符串)、`msg`(错误/堆栈信息)、`playerid`、`agentid`、`gameid`(09_Net.js:66-72)。
|
||||
- 响应/推送:服务器若回 rpc `"submit_error"`,前端在分发处直接 `return` 忽略(12_Logic.js:235)。
|
||||
- 注意:submit_log 是本地函数别名,不是第二个线上 rpc;不能重复计数。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:submit_error、submit_log。
|
||||
|
||||
#### 43. kick_server
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 推送;状态:**R**;新 Rpc 常量:有。
|
||||
- 用途:管理端踢出玩家 / 被踢下线弹窗。
|
||||
- 请求:`agentid` 等(构造点未集中定位)。
|
||||
- 响应/推送:`msg`(踢出提示) → `GameUI.OpenKick`(07_Desk.js:1108)。
|
||||
- 注意:⚠️ 请求字段待补。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_kick_server、kick_server。
|
||||
|
||||
#### 44. broadcast
|
||||
|
||||
- 路由/通道:agent;方向:C→S 请求 + S→C 推送;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:广播消息/滚动公告(主要为服务器推送)。
|
||||
- 请求:`agentid` 等(`Send_broadcast` 存在,09_Net.js:530)。
|
||||
- 响应/推送:`msgtype`(0消息框/1滚动公告,可选,缺省 0)、`msgcontent`(内容)(07_Desk.js:1112)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_broadcast、broadcast。
|
||||
|
||||
#### 45. connect_agentserver
|
||||
|
||||
- 路由/通道:agent;方向:双向(切服);状态:**R**;新 Rpc 常量:有。
|
||||
- 用途:切换到大厅服务器。
|
||||
- 请求:`Send_connect_agentserver`(`09_Net.js:515`) 透传 `_data`(切服时使用)。
|
||||
- 响应/推送:`agentserver`(新大厅服地址)、`opt`(切换原因,如 `other_break_room`/`free_room`)(09_Net.js:518)。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_connect_agentserver、connect_agentserver。
|
||||
|
||||
#### 46. playerBehavior
|
||||
|
||||
- 路由/通道:HTTP GET(历史 agent 名称);方向:C→HTTP;状态:**X**;新 Rpc 常量:有。
|
||||
- 用途:玩家行为埋点。
|
||||
- 请求:原 WS 路径(agentid/gameid/playerid/tag)**被注释**(09_Net.js:799-806),实际改走独立 HTTP GET 上报 `http://test3.1888day.com/api/gamedo/gamedo?agentid=&gameid=&playerid=&tag=`(09_Net.js:807-815)。
|
||||
- 响应/推送:HTTP 回调 `playerBehavior_Succ`/`_Fail`(09_Net.js:818/822);WS 接收函数 `Net.playerBehavior`(`09_Net.js:826`) 当前无触发。
|
||||
- 注意:当前调用走独立 HTTP 埋点地址,WS 段被注释;不归入本地 WebSocket 验收。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:Send_playerBehavior、playerBehavior。
|
||||
|
||||
### 房间 · 房间生命周期(8)
|
||||
|
||||
#### 47. self_break_room
|
||||
|
||||
- 路由/通道:room;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:房主在**未开局**前主动解散房间。
|
||||
- 请求:仅通用四字段。
|
||||
- 响应/推送:`roomcode`(可选,用于从本地"我的房间"列表移除)。处理:清空牌桌、回大厅。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:Send_self_break_room、self_break_room。
|
||||
|
||||
#### 48. other_break_room
|
||||
|
||||
- 路由/通道:room;方向:S→C 推送;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:他人(房主)解散房间,推送给房内其余玩家。
|
||||
- 请求:无(纯推送)。
|
||||
- 响应/推送:无业务字段;触发 `Func.exitRoom()`、清桌、回大厅、提示"房主已解散房间!"。
|
||||
- 注意:常伴随 **connect_agentserver** 切服包(`data.opt == other_break_room`,见下文)。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:other_break_room。
|
||||
|
||||
#### 49. self_exit_room
|
||||
|
||||
- 路由/通道:room;方向:C→S 请求 + S→C 响应;状态:**R**;新 Rpc 常量:有。
|
||||
- 用途:自己在**未开局**前退出房间。
|
||||
- 请求:仅通用四字段(发送处见 `08_Utl_Output.js:993`)。
|
||||
- 响应/推送:`isowner`(可选,==1 且非无限局时把房间加回本地列表), `seat`(可选,传给 `Game_Modify.myExitRoom`), `roomcode`(可选)。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:Send_self_exit_room、self_exit_room。
|
||||
|
||||
#### 50. other_exit_room
|
||||
|
||||
- 路由/通道:room;方向:S→C 推送;状态:**R**;新 Rpc 常量:有。
|
||||
- 用途:其他玩家(未开局)退出房间。
|
||||
- 请求:无(纯推送)。
|
||||
- 响应/推送:`seat`(离开者座位)。处理:清该座位、`playercnt--`;若 `seat==0` 且非无限局,提示房主已离开。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:other_exit_room。
|
||||
|
||||
#### 51. player_prepare
|
||||
|
||||
- 路由/通道:room;方向:C→S 请求 + S→C 推送;状态:**R**;新 Rpc 常量:有。
|
||||
- 用途:玩家点击准备(`needprepare==1` 的房间)。
|
||||
- 请求:仅通用四字段。
|
||||
- 响应/推送:`seat`(准备者座位), `deskwar`(可选;为真表示满足开战条件 → `HideStartScene` + `Game_Modify.StartWar`)。推送给房内所有人。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:Send_player_prepare、player_prepare。
|
||||
|
||||
#### 52. change_room
|
||||
|
||||
- 路由/通道:room;方向:C→S;响应 rpc=change_seat;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:玩家请求换座 / 换桌。
|
||||
- 请求:仅通用四字段(**不带目标座位**,由服务器决定换到哪个空位)。发送处 `11_GameUI.js:1945`、`08_Utl_Output.js:1006`。
|
||||
- 响应/推送:rpc 名改为 **`change_seat`**,`data`:`seat1`, `seat2`(两个互换的座位号)。处理:交换两座 Desk 信息,若自己在其中则 `C_Player.SetSeat` 更新。
|
||||
- 注意:上行 rpc=`change_room`,下行 rpc=`change_seat`,二者成对。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:Send_change_room。
|
||||
|
||||
#### 53. share_room
|
||||
|
||||
- 路由/通道:room;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:把房间分享到世界房列表。
|
||||
- 请求:通用四字段;可选 `roomlist`、`roomtype`、`shareType`(高级房分享时携带,见 `11_GameUI.js:1734`/`11_GameUI.js:1891`)。
|
||||
- 响应/推送:无业务字段;提示"已成功分享至平台!"。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:Send_share_room、share_room。
|
||||
|
||||
#### 54. change_seat
|
||||
|
||||
- 路由/通道:room;方向:S→C(change_room 的响应);状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:服务器指定两座互换。
|
||||
- 请求:无同名上行;上行是 change_room。
|
||||
- 响应/推送:seat1、seat2。
|
||||
- 注意:独立列名,不能把 change_room 与 change_seat 合成一条而漏计。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:change_seat。
|
||||
|
||||
### 房间 · 进房推送(他人视角)(3)
|
||||
|
||||
#### 55. other_join_room
|
||||
|
||||
- 路由/通道:room;方向:S→C 推送;状态:**R**;新 Rpc 常量:有。
|
||||
- 用途:其他玩家加入当前房间。
|
||||
- 请求:无(纯推送;自己进房用 `self_join_room`,走 agent 路由,见 02 章)。
|
||||
- 响应/推送:`seat`(新玩家座位) + 该玩家完整对象(直接传给 `Player.SetDeskInfo`,见 [04 · Player 座位对象](04-数据结构.md#player-座位对象)):
|
||||
- 注意:⚠️ `charm`/`sign` 是否每次必含、其余字段是否完整以 `Player.SetDeskInfo` 实际读取为准,详见 [04 章](04-数据结构.md#player-座位对象)。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:other_join_room。
|
||||
|
||||
#### 56. other_offline
|
||||
|
||||
- 路由/通道:room;方向:S→C 推送;状态:**R**;新 Rpc 常量:有。
|
||||
- 用途:其他玩家离线。
|
||||
- 请求:无(纯推送)。
|
||||
- 响应/推送:`seat`。处理:该座 `onstate=1`,刷新 UI。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:other_offline。
|
||||
|
||||
#### 57. other_online
|
||||
|
||||
- 路由/通道:room;方向:S→C 推送;状态:**R**;新 Rpc 常量:有。
|
||||
- 用途:其他玩家重新上线。
|
||||
- 请求:无(纯推送)。
|
||||
- 响应/推送:`seat`, `ip`。处理:该座 `onstate=0`、更新 ip。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:other_online。
|
||||
|
||||
### 房间 · 解散投票(开局后)(8)
|
||||
|
||||
#### 58. self_apply_free_room
|
||||
|
||||
- 路由/通道:room;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:自己申请解散房间。
|
||||
- 请求:仅通用四字段。
|
||||
- 响应/推送:`agreefree` { `state`:[各座同意状态数组], `countdown`:倒计时 }。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:Send_self_apply_free_room、self_apply_free_room。
|
||||
|
||||
#### 59. other_apply_free_room
|
||||
|
||||
- 路由/通道:room;方向:S→C 推送;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:他人申请解散。
|
||||
- 请求:无(纯推送)。
|
||||
- 响应/推送:`seat`(申请者), `agreefree` { `state`[], `countdown` }。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:other_apply_free_room。
|
||||
|
||||
#### 60. self_agree_free_room
|
||||
|
||||
- 路由/通道:room;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:自己同意解散。
|
||||
- 请求:仅通用四字段。
|
||||
- 响应/推送:无业务字段;本地把自己加入同意列表、刷新投票 UI。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:Send_self_agree_free_room、self_agree_free_room。
|
||||
|
||||
#### 61. other_agree_free_room
|
||||
|
||||
- 路由/通道:room;方向:S→C 推送;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:他人同意解散。
|
||||
- 请求:无(纯推送)。
|
||||
- 响应/推送:`seat`(同意者)。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:other_agree_free_room。
|
||||
|
||||
#### 62. self_refuse_free_room
|
||||
|
||||
- 路由/通道:room;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:自己拒绝解散。
|
||||
- 请求:仅通用四字段。
|
||||
- 响应/推送:无业务字段;清空同意列表、投票结果置不通过、弹"已拒绝"结果。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:Send_self_refuse_free_room、self_refuse_free_room。
|
||||
|
||||
#### 63. other_refuse_free_room
|
||||
|
||||
- 路由/通道:room;方向:S→C 推送;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:他人拒绝解散(任一人拒绝即否决本轮)。
|
||||
- 请求:无(纯推送)。
|
||||
- 响应/推送:`seat`(拒绝者)。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:other_refuse_free_room。
|
||||
|
||||
#### 64. free_room
|
||||
|
||||
- 路由/通道:room;方向:S→C 推送;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:解散投票最终结果。
|
||||
- 请求:无(纯推送)。
|
||||
- 响应/推送:freeNow;deskfree、roomcard、tips、time、seats 按具体分支读取,字段语义见 03 章。
|
||||
- 注意:可伴随 **connect_agentserver** 切服包(`data.opt == free_room`,见下文)。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:free_room。
|
||||
|
||||
#### 65. beanroom_surrender
|
||||
|
||||
- 路由/通道:room;方向:C→S 请求 + S→C 响应;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:豆豆房(金币房)投降。
|
||||
- 请求:通用四字段 + `count`(投降数量 = `GameData.surrendCount`,发送处 `11_GameUI.js:1245`)。
|
||||
- 响应/推送:`state`(0=成功 → `Game_Modify.onSurrender(_msg)`); 失败时 `showerror`(==1 则弹) / `error`(错误文本)。
|
||||
- 注意:⚠️ 未见成对的 `other_xxx` 推送,是否向房内其他玩家广播他人投降待后端确认。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:Send_beanroom_surrender、beanroom_surrender。
|
||||
|
||||
### 房间 · 开局(2)
|
||||
|
||||
#### 66. self_makewar
|
||||
|
||||
- 路由/通道:room;方向:C→S 请求 → S→C 响应(self_makewar);状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:房主主动开局。
|
||||
- 请求:仅通用四字段。
|
||||
- 响应/推送:rpc=`self_makewar`,无业务字段;触发 `Desk.self_makewar` → `Game_Modify.StartWar(_msg)`。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:Send_self_makewar、self_makewar。
|
||||
|
||||
#### 67. other_makewar
|
||||
|
||||
- 路由/通道:room;方向:S→C 推送;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:开局广播(房主开局或满员自动开局)给房内其他玩家。
|
||||
- 请求:无(纯推送)。
|
||||
- 响应/推送:开局信息对象 → `Desk.makewar` → `Game_Modify.StartWar(msg)`。
|
||||
- 注意:实际发牌等对局数据在此之后通过**子游戏内协议**下发(见 05 章)。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:other_makewar。
|
||||
|
||||
### 房间 · 房间内社交(6)
|
||||
|
||||
#### 68. send_text
|
||||
|
||||
- 路由/通道:room;方向:C→S 请求 + S→C 推送 · 文字聊天;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:文字聊天 / 全服公告 / 预定义常用语。
|
||||
- 请求:通用四字段 + `text`(内容;点常用语时为 `Game_Config.Info.TextContent[spid-206]` 文本) + `type`(0=普通 / 1=全服公告;按是否勾选公告设 0/1) + `info`(可选;**有 info 时 `type` 改为 2**)。发送处 `11_GameUI.js:1170`(输入框)、`11_GameUI.js:2717`(常用语)。
|
||||
- 响应/推送:`type`(0=普通 / 1=全服公告 / 2=机器人 / 3=预定义文字), `text`(内容;**type==3 时为 `Game_Config.Info.TextContent` 的 1 基索引**,客户端按 `(idx-1)%len` 取文本), `seat`(发送者座位,type≠1 时使用), `info`(type==2 时附加)。
|
||||
- 注意:百人场(`vipInfinite`)只处理 type==1 公告分支。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:Send_send_text、send_text。
|
||||
|
||||
#### 69. send_voice
|
||||
|
||||
- 路由/通道:room;方向:C→S 请求 + S→C 推送 · 语音;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:发送语音消息。
|
||||
- 请求:通用四字段 + `voiceurl`(已上传音频地址) + `time`(时长) + `info`(可选) + `type`(可选,有 info 时为 2)。发送处 `05_Func.js:1714`、`05_Func.js:2984`。
|
||||
- 响应/推送:`type`(0=普通 / 2=机器人), `seat`(发送座位), `voiceurl`, `time`, `info`(type==2 时)。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:Send_send_voice、send_voice。
|
||||
|
||||
#### 70. send_gift
|
||||
|
||||
- 路由/通道:room;方向:C→S 请求 + S→C 推送 · 互动/送礼;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:向指定座位送互动礼物。
|
||||
- 请求:通用四字段 + `giftid`(=`spid_up - 255`,按钮精灵号算出,发送处 `11_GameUI.js:2725`) + `receiveseat`(=`GameData.InteractPlayer`) + `info`(可选) + `type`(有 info 时为 2)。
|
||||
- 响应/推送:`type`(0=普通 / 2=机器人), `giftid`(客户端做 `(giftid-1)%4+1` 归一为 1~4 动画), `sendseat`(发送座位), `receiveseat`(接收座位), `info`(type==2 时)。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:Send_send_gift、send_gift。
|
||||
|
||||
#### 71. send_phiz
|
||||
|
||||
- 路由/通道:room;方向:C→S 请求 + S→C 推送 · 表情;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:发送表情动画。
|
||||
- 请求:通用四字段 + `text`(=`up_id`,表情按钮序号 1~`ConstVal.Emotion.count`,发送处 `11_GameUI.js:815`) + `info`(可选) + `type`(有 info 时为 2)。
|
||||
- 响应/推送:`type`(0=普通 / 2=机器人), `text`(表情 ID,客户端按 `(text-1)%ConstVal.Emotion.src_list.length+1` 归一), `seat`(发送座位), `info`(type==2 时)。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:Send_send_phiz、send_phiz。
|
||||
|
||||
#### 72. call_phone
|
||||
|
||||
- 路由/通道:room;方向:C→S 请求 + S→C 推送 · 拨打电话;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:拨打/接听电话,置玩家 `onstate=2`(通话中)。
|
||||
- 请求:仅通用四字段(发送处 `06_Player.js:380` / `:388` / `:396`,对应接起/电话进来/去电三种触发)。
|
||||
- 响应/推送:rpc=`call_phone`,`seat`(拨打者座位 → 该玩家 `onstate=2`)。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:Send_call_phone、call_phone。
|
||||
|
||||
#### 73. hangup_phone
|
||||
|
||||
- 路由/通道:room;方向:C→S 请求 + S→C 推送 · 挂断电话;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:挂断电话,置玩家 `onstate=0`。
|
||||
- 请求:仅通用四字段(发送处 `06_Player.js:372`)。
|
||||
- 响应/推送:rpc=`hangup_phone`,`seat`(挂断者座位 → 该玩家 `onstate=0`)。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:Send_hangup_phone、hangup_phone。
|
||||
|
||||
### 保留名称 · 房间社交(5)
|
||||
|
||||
#### 74. receive_chat
|
||||
|
||||
- 路由/通道:room(文档归类,线上未确认);方向:未确认(文档推测为下行);状态:**X**;新 Rpc 常量:有。
|
||||
- 用途:聊天推送的另一可能 rpc 名(与 `send_text` 成对)。
|
||||
- 请求:未发现同名发送链。
|
||||
- 响应/推送:未定义,不能套用其它 rpc 字段。
|
||||
- 注意:常量存在,旧 Net 无同名接收器。与实际已实现的 send_voice/send_text/call_phone/hangup_phone/send_gift 区分;不据此断言服务器绝不会发送。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:无同名 Net 定义(保留/桥接按所引章节核对)。
|
||||
|
||||
#### 75. play_voice
|
||||
|
||||
- 路由/通道:room(文档归类,线上未确认);方向:未确认(文档推测为下行);状态:**X**;新 Rpc 常量:有。
|
||||
- 用途:与 `send_voice` 成对,疑为语音**播放**推送。
|
||||
- 请求:未发现同名发送链。
|
||||
- 响应/推送:未定义,不能套用其它 rpc 字段。
|
||||
- 注意:常量存在,旧 Net 无同名接收器。与实际已实现的 send_voice/send_text/call_phone/hangup_phone/send_gift 区分;不据此断言服务器绝不会发送。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:无同名 Net 定义(保留/桥接按所引章节核对)。
|
||||
|
||||
#### 76. other_send_gift
|
||||
|
||||
- 路由/通道:room(文档归类,线上未确认);方向:未确认(文档推测为下行);状态:**X**;新 Rpc 常量:有。
|
||||
- 用途:与 `send_gift` 成对,疑为他人送礼推送。
|
||||
- 请求:未发现同名发送链。
|
||||
- 响应/推送:未定义,不能套用其它 rpc 字段。
|
||||
- 注意:常量存在,旧 Net 无同名接收器。与实际已实现的 send_voice/send_text/call_phone/hangup_phone/send_gift 区分;不据此断言服务器绝不会发送。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:无同名 Net 定义(保留/桥接按所引章节核对)。
|
||||
|
||||
#### 77. other_callphone
|
||||
|
||||
- 路由/通道:room(文档归类,线上未确认);方向:未确认(文档推测为下行);状态:**X**;新 Rpc 常量:有。
|
||||
- 用途:与 `call_phone` 成对,疑为他人拨打电话推送。
|
||||
- 请求:未发现同名发送链。
|
||||
- 响应/推送:未定义,不能套用其它 rpc 字段。
|
||||
- 注意:常量存在,旧 Net 无同名接收器。与实际已实现的 send_voice/send_text/call_phone/hangup_phone/send_gift 区分;不据此断言服务器绝不会发送。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:无同名 Net 定义(保留/桥接按所引章节核对)。
|
||||
|
||||
#### 78. other_hangup
|
||||
|
||||
- 路由/通道:room(文档归类,线上未确认);方向:未确认(文档推测为下行);状态:**X**;新 Rpc 常量:有。
|
||||
- 用途:与 `hangup_phone` 成对,疑为他人挂断电话推送。
|
||||
- 请求:未发现同名发送链。
|
||||
- 响应/推送:未定义,不能套用其它 rpc 字段。
|
||||
- 注意:常量存在,旧 Net 无同名接收器。与实际已实现的 send_voice/send_text/call_phone/hangup_phone/send_gift 区分;不据此断言服务器绝不会发送。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:无同名 Net 定义(保留/桥接按所引章节核对)。
|
||||
|
||||
### 大厅 · 资产更新与系统推送(4)
|
||||
|
||||
#### 79. update_bean
|
||||
|
||||
- 路由/通道:上行 agent;下行按 agent 归档(实际推送路由待逐项抓包);方向:C→S + S→C;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:豆豆变化(本人或房内座位)
|
||||
- 请求:原生 yPaytype / WVJB yPaytype 回调直接发送 agentid、playerid、change:num、type:9;这是资产写入路径,不属于只读查询。
|
||||
- 响应/推送:bean、change/seat/type(按场景可选)、text;不同分支见原处理器。
|
||||
- 注意:02 章“无对应主动请求”与当前 05_Func.js 不符。存在发送代码不代表支付来源可信性、服务端行为或新框架已经验收。
|
||||
- 直接上行来源:原生 yPaytype、WVJB yPaytype。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:update_bean。
|
||||
|
||||
#### 80. update_roomcard
|
||||
|
||||
- 路由/通道:上行 agent;下行按 agent 归档(实际推送路由待逐项抓包);方向:C→S + S→C;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:房卡变化
|
||||
- 请求:原生 yPaytype / WVJB yPaytype 回调直接发送 agentid、playerid、change:num、type:9;这是资产写入路径,不属于只读查询。
|
||||
- 响应/推送:roomcard、text(可选);change 分支条件见原处理器。
|
||||
- 注意:02 章“无对应主动请求”与当前 05_Func.js 不符。存在发送代码不代表支付来源可信性、服务端行为或新框架已经验收。
|
||||
- 直接上行来源:原生 yPaytype、WVJB yPaytype。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:update_roomcard。
|
||||
|
||||
#### 81. kick_offline
|
||||
|
||||
- 路由/通道:agent(协议归档;实际推送路由待逐项抓包);方向:S→C;状态:**N**;新 Rpc 常量:有。
|
||||
- 用途:被踢下线
|
||||
- 请求:无对应主动请求。
|
||||
- 响应/推送:fromOther、gameid(可选);旧流程同时上报 submit_error。
|
||||
- 注意:仅有旧处理器不能证明新 Runtime 接入或真实服务已覆盖。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:kick_offline。
|
||||
|
||||
#### 82. show_message
|
||||
|
||||
- 路由/通道:agent(协议归档;实际推送路由待逐项抓包);方向:S→C;状态:**N**;新 Rpc 常量:无。
|
||||
- 用途:通用提示
|
||||
- 请求:无对应主动请求。
|
||||
- 响应/推送:msg、time。
|
||||
- 注意:仅有旧处理器不能证明新 Runtime 接入或真实服务已覆盖。
|
||||
- 依据:[02-协议-agent路由.md](02-协议-agent路由.md);旧函数:show_message。
|
||||
|
||||
### 房间 · 服务器切换(room 侧)(1)
|
||||
|
||||
#### 83. connect_roomserver
|
||||
|
||||
- 路由/通道:room;方向:C→S 请求 + S→C 推送 · 切到房间服;状态:**R**;新 Rpc 常量:有。
|
||||
- 用途:从大厅服切换到房间服务器。
|
||||
- 请求:由 `Send_connect_roomserver` 发起(走 room 路由)。
|
||||
- 响应/推送:`data.roomserver`(新房间服地址)。处理:置 `GameData.ConnectType=true`、`GameData.ConnectRpc=connect_roomserver`、`GameData.Server=data.roomserver`,关闭当前连接并用新地址重连。
|
||||
- 依据:[03-协议-room路由.md](03-协议-room路由.md);旧函数:Send_connect_roomserver、connect_roomserver。
|
||||
|
||||
### 子游戏 · 框架保留名称(2)
|
||||
|
||||
#### 84. over_game
|
||||
|
||||
- 路由/通道:由具体子游戏契约确定;方向:文档描述 S→C;实际由子游戏确认;状态:**X**;新 Rpc 常量:有。
|
||||
- 用途:结算广播保留名;具体载荷由子游戏定义。
|
||||
- 请求:模板未定义。
|
||||
- 响应/推送:模板未定义。
|
||||
- 注意:Game_Surface_3 的 Game_Modify 为模板,不能据名字推导具体子游戏协议。
|
||||
- 依据:[05-游戏内协议与桥接.md](05-游戏内协议与桥接.md);旧函数:无同名 Net 定义(保留/桥接按所引章节核对)。
|
||||
|
||||
#### 85. agentserver_game
|
||||
|
||||
- 路由/通道:由具体子游戏契约确定;方向:未定义;状态:**X**;新 Rpc 常量:有。
|
||||
- 用途:对局数据经大厅服中转的保留名。
|
||||
- 请求:模板未定义。
|
||||
- 响应/推送:模板未定义。
|
||||
- 注意:Game_Surface_3 的 Game_Modify 为模板,不能据名字推导具体子游戏协议。
|
||||
- 依据:[05-游戏内协议与桥接.md](05-游戏内协议与桥接.md);旧函数:无同名 Net 定义(保留/桥接按所引章节核对)。
|
||||
|
||||
### 外部桥接 · 非网络 RPC(1)
|
||||
|
||||
#### 86. joinRoom
|
||||
|
||||
- 路由/通道:H5/小程序本地桥接;方向:外部入口→前端确认流程;状态:**X**;新 Rpc 常量:有。
|
||||
- 用途:唤起进房确认;不是直接向服务器发送 joinRoom。
|
||||
- 请求:{rpc:"joinRoom",data:{roomcode}};H5 来自 URL gameData,小程序来自缓存 openminigamedata。
|
||||
- 响应/推送:确认后走 self_join_room;不产生 joinRoom 网络回包。
|
||||
- 注意:h5RpcList 唯一名称;旧小程序 checkType=8 与白名单删除撞号,不能沿用。WVJB handler 名不计为服务器 RPC。
|
||||
- 依据:[05-游戏内协议与桥接.md](05-游戏内协议与桥接.md);旧函数:无同名 Net 定义(保留/桥接按所引章节核对)。
|
||||
|
||||
## 覆盖核对方法
|
||||
|
||||
- 逐一取 02_Const.js 的 RpcList 字符串赋值去重;核对 09_Net.js 中 RpcList 引用、Net 命名函数以及硬编码 rpc;扫描全模板的直接 _SendData 调用与 rpc 赋值(包含 05_Func.js 支付桥接),补入 h5RpcList。
|
||||
- 与 02/03 章标题、02 章末尾推送表以及 05 章保留/桥接名称取并集;按实际名称一条一项,跨章节引用不重复计数。
|
||||
- 结果:82 个旧常量名称全部命中;补充 4 个名称后 86 条,无遗漏或重复;11 个现代严格分发名称全部命中。
|
||||
- 新框架状态依据 platform/runtime.ts、protocol/platform-handlers.ts、protocol/first-slice-routes.ts;旧 protocol/routes.ts 大常量表仅用于“名称存在”对照。
|
||||
- 本轮没有发送网络请求、触发 Cocos 编辑器操作或运行资源生成器;这份静态索引不新增真实验收结论。
|
||||
@@ -0,0 +1,20 @@
|
||||
# 平台网络协议
|
||||
|
||||
返回[框架目录](../README.md)。这些协议内容已整合在本目录,无需跳转外部文档。
|
||||
|
||||
1. [框架架构](00-框架架构设计.md)
|
||||
2. [传输层与架构](01-传输层与架构.md)
|
||||
3. [agent 路由](02-协议-agent路由.md)
|
||||
4. [room 路由](03-协议-room路由.md)
|
||||
5. [数据结构](04-数据结构.md)
|
||||
6. [游戏内协议与桥接](05-游戏内协议与桥接.md)
|
||||
7. [历史子游戏开发模式与 Cocos 方案](06-子游戏开发模式与Cocos方案.md)
|
||||
8. [RPC 清单](07-RPC全量清单.md)
|
||||
|
||||
## 阅读约束
|
||||
|
||||
协议保留既有服务器信封与字段;不得为了前端便利改变服务器。历史文档中的原工程类名用于追溯,不要求调用旧实现。
|
||||
|
||||
早期 agent 表将 version 标为 string,但实包登录字段必须是数值 versionCode,详见数据结构中的登录校验记录。早期 create_room 的数组 roomtype 描述针对模板;通用平台保存与转发目标游戏实际的配置形态,二七王按[二七王协议](../../subgames/erqiwang/protocol/packet_protocol.md)使用字符串。
|
||||
|
||||
设计文档中的未来接入方式不能替代[当前实施状态](../architecture/current-status.md)。
|
||||
@@ -0,0 +1,71 @@
|
||||
# 子游戏 SDK 契约
|
||||
|
||||
返回[框架目录](../README.md)。相关:[创建房间](../integration/create-room.md)、[房间设计](../architecture/room-platform.md)。
|
||||
|
||||
本页整理当前接口职责,类型名对应工程中的同名契约。具体游戏通过契约接入,不导入平台内部状态实现。
|
||||
|
||||
## GameDefinition:子游戏入口定义
|
||||
|
||||
必须提供 key、route、config.versionResource、config.assetRoot、createGame(gameId)、chat、roomMenu(mainSceneButton/vipInfinite)、createPageClass、roomViewClass,以及 resources 中的 createRoom/room/roomScene 路径描述。
|
||||
|
||||
key 与场景 gameKey 一致。route 遵循协议,gameId 来自 XML。versionResource 精确为 `<gameKey>/version`,assetRoot 是本游戏的 `games/<目录>`。createRoom/room 必须位于本游戏根目录内,加载器从目标 Bundle 自动加载;roomScene 是公共承载场景引用。定义由本游戏 `GameEntry_<gameKey>` 类的 static definition 暴露,公共框架不维护游戏列表。
|
||||
|
||||
## GameBinding 与 GameEntry
|
||||
|
||||
GameBinding 包含 entry、mount(view)、restoredSnapshot、roomProjection。entry 提供 key/gameId/route、resolveSeatCount(roomtype)、createModule()。
|
||||
|
||||
resolveSeatCount 解释服务器实际返回的本游戏配置;不要让平台猜人数。createModule 创建独立房间模块,禁止把上一房间可变状态复用于下一房间。
|
||||
|
||||
## GameModule 生命周期
|
||||
|
||||
```ts
|
||||
interface GameModule {
|
||||
attach(host: GameHost): void;
|
||||
handlePlatformEvent(event: PlatformToGameEvent): void;
|
||||
handleGameMessage(message: GameServerMessage): void;
|
||||
restore(deskinfo: unknown): void;
|
||||
dispose(): void;
|
||||
}
|
||||
```
|
||||
|
||||
这是核心生命周期摘录。可选 roomProjection 用于向公共房间展示提供游戏投影;没有投影时明确为空。restore 必须按本游戏快照协议恢复,不能将仅缓存对象当作恢复完成。
|
||||
|
||||
## RoomCapabilities
|
||||
|
||||
通过 requireRoomCapabilities(host) 取得能力;生产宿主缺少能力时显式报错,不退回另一套隐藏实现。
|
||||
|
||||
- room.seat:座位映射。
|
||||
- room.getSnapshot / subscribe:快照与订阅,subscribe 返回退订函数。
|
||||
- room.prepare / requestExit / applyDissolution:公共房间操作。
|
||||
- messages.send(rpc, data):本游戏消息发送,data 遵循 JSON 与真实协议。
|
||||
- ui.beginLoading():返回具有 close() 的所有权句柄。
|
||||
- ui.showBusinessTip(message,time):玩家业务提示,返回 close() 句柄;不能传入异常或调试字符串。
|
||||
- ui.requestNumber / requestDigitText / requestMultiplier:异步输入,区分 confirmed 与 cancelled。
|
||||
- scope.active:会话是否有效。
|
||||
- scope.defer(cleanup):会话结束清理;返回函数用于取消该清理注册,不会立即执行 cleanup。
|
||||
|
||||
输入取消原因包含 user、scope-ended、replaced。取消不是故障,游戏必须处理取消分支。释放后回调不得继续操作 UI 或发送旧房间消息。
|
||||
|
||||
## 创建页面
|
||||
|
||||
```ts
|
||||
interface CreateRoomPageContext {
|
||||
readonly previousRoomtype: unknown;
|
||||
submit(roomtype: Roomtype): void;
|
||||
cancel(): void;
|
||||
reportError(message: string): void;
|
||||
}
|
||||
interface CreateRoomPagePort {
|
||||
open(context: CreateRoomPageContext): void;
|
||||
setBusy(busy: boolean): void;
|
||||
close(): void;
|
||||
}
|
||||
```
|
||||
|
||||
Cocos 页面继承 CreateRoomPage 并挂在 prefab 根节点。previousRoomtype 为 null 时,子游戏来源可定义首次默认值;已有无效数据不能静默重置。reportError 当前用于诊断,不显示玩家提示。详细实现步骤见[创建房间接入](../integration/create-room.md)。
|
||||
|
||||
## 聊天、视图与释放
|
||||
|
||||
每款游戏提供自己的 RoomChatConfig,常用语和历史通过动态列表展示,不根据另一款游戏的固定数量写框架分支。
|
||||
|
||||
RoomPresentation.render 接收平台快照、公共操作与可选游戏投影;close 释放展示状态。房间视图匹配注册的 roomViewClass。组件、定时器、事件和订阅都要在页面关闭、房间结束或节点销毁后解除。
|
||||
@@ -0,0 +1,240 @@
|
||||
# 原接口迁移清单与子游戏接入契约
|
||||
|
||||
版本:1.1 · 2026-09-08 · 状态:已落地 API 与兼容边界见第 7 节及[实施记录](../architecture/current-status.md)。前文保留原接口迁移目标。
|
||||
|
||||
主文档:[房间平台架构设计](../architecture/current-status.md)。本清单基于原工程 Input/Output 文件及其调用点,区分“保留业务能力”与“保留旧调用方式”。默认不保留全局 Game_Modify、Utl、Desk、C_Player 接口。
|
||||
|
||||
## 1. 迁移规则
|
||||
|
||||
- 保留:协议语义、原始载荷、来源定义的顺序、玩家可观察的正确结果。
|
||||
- 改造:直接读写全局状态、精灵编号、跨层调用、手工拼公共身份、全局输入回调。
|
||||
- 分离:在线与回放、局内分数与账户资产、请求与确认、状态重绘与一次性业务副作用。
|
||||
- 不照搬:示例默认值、空钩子掩盖未接入、吞异常、以显示状态判断业务阶段。
|
||||
- 必需项缺失在装配时失败;可选项必须声明“未提供”的行为,不能通过任意 fallback 冒充支持。
|
||||
|
||||
## 2. Input:平台通知游戏
|
||||
|
||||
源码:02_SubGame_Input.js。
|
||||
|
||||
### 2.1 房间进入与游戏协议
|
||||
|
||||
`StartWar(_msg)` → started 输入。保留触发 sourceRpc 和原整包;包括 self_makewar、makewar、加入/准备附带 deskwar,不仅监听单个 rpc。
|
||||
|
||||
`_ReceiveData(_msg)` → game-message 输入。游戏自己的协议适配器校验 rpc/payload,平台只校验路由归属与传输外层。
|
||||
|
||||
`createRoom(roomtype,infinite)`、`onCreateRoom(data)` → created 成功输入的游戏适配。旧包装层可能在失败后仍调用钩子,必须记录并改为明确的结果/成功边界,禁止把失败视为建房成功。
|
||||
|
||||
`myJoinRoom(_msg)` → joined 输入及对应 waiting/snapshot/started 分支。不能因为有快照而省略主动加入语义。
|
||||
|
||||
`Reconnect(deskinfo)` → login-restored 输入;`ReconnectNoMakewar` 虽未在此文件声明,但在 Desk.login 调用,应纳入 login-waiting 契约。
|
||||
|
||||
`DeskInfo(deskinfo)` → joined-snapshot。与登录重连区分,游戏可以映射到共享内部还原函数,但平台不得强迫两者等同。
|
||||
|
||||
`onCreateDesk(roomtype)` → 游戏初始化/规则预检。座位规则可以先运行,任何需要 View 的工作移到挂载就绪后。
|
||||
|
||||
### 2.2 玩家与公共状态
|
||||
|
||||
`playerJoinRoom`、`playerLeaveRoom`、`playerOffline`、`playerOnline`、`onReady`、`changeSeat` → 类型明确的公共成员输入,始终使用服务器座位。
|
||||
|
||||
`myExitRoom`、`breakRoom` → 保留服务端确认的自身离开原因,在旧上下文仍可读取的收尾阶段通知,然后统一释放。游戏不能在通知中再次发送同一退出命令。
|
||||
|
||||
`playerphonestate` → 设备/玩家通话状态能力输入。旧参数 0/1 的实际语义以 Desk.call_phone/hangup_phone 为准,不照抄注释中的歧义文本。
|
||||
|
||||
### 2.3 视图生命周期
|
||||
|
||||
`onEnterMainScene` → View 挂载就绪后的明确通知;不通过读取精灵可见性判断。
|
||||
|
||||
`onExitMainScene`、`closeGameScene`、`stopAllSounds` → 由 scope 组织的停止/卸载/音频释放。音频使用房间或游戏专属 owner,不误停应用其他声音。
|
||||
|
||||
`updateScene` → 从现有游戏状态重建展示。不会重新解释创建响应、再次发命令或再次产生分享/活动结果。
|
||||
|
||||
`onMainMenuScene` → 应用/大厅导航生命周期,非每房间 GameModel 必需能力。
|
||||
|
||||
### 2.4 结算与活动
|
||||
|
||||
`Free` → dissolution-settlement,区分普通投票确认与 freeNow。deskfree 内容由游戏解析。
|
||||
|
||||
`onSurrender` → 对应来源的业务结果输入,游戏解释;公共投降入口只负责协议封装和展示流程。
|
||||
|
||||
`onEnterVideo` → 可选回放能力,绑定独立 ReplayContext。
|
||||
|
||||
`onCloseVip`、`getShareRoom` → 大厅/房间列表功能的可选扩展,不强迫纯房间游戏实现。
|
||||
|
||||
### 2.5 同步规则与描述
|
||||
|
||||
`getRoomInfo`、`getFullRoomInfo`、`getRoomTopDescAry` → GameRoomDescription。返回标题、说明项等结构化内容,UI 决定换行和字体,不让游戏按“18/26 个字符”进行硬布局。
|
||||
|
||||
`getRoomMode`、`getStarLimit`、`getLeaveLimit`、`getMult`、`getVideoByRoomType` → 游戏规则/策略查询;不修改 roomtype、不发包、不写 UI。仅保留项目实际支持的功能,未提供时有明确的能力边界。
|
||||
|
||||
创建前的规则预览与入房后的权威公共回包不能混用:例如 roommode 已由服务端明确给出时,不用本地规则覆盖。
|
||||
|
||||
`onGameConfig` → 游戏配置生命周期。配置来源唯一,更新时有版本/替换语义,游戏不得自行从另一个 URL 再读一套。
|
||||
|
||||
### 2.6 用户输入与设备
|
||||
|
||||
`calResult`、`onCheckInput` → 对应具体输入请求的结果,不再使用全局回调入口。
|
||||
|
||||
`shakeEvent` → 平台设备意图适配。平台提供的“摇动开始”与开始按钮调用同一个 start 命令;游戏自定义摇动行为可以声明独立能力,不能复制公共身份拼包代码。
|
||||
|
||||
`onLocationInfo` → Location 能力的结果/订阅。浏览器替身数据的缺省由浏览器适配器明确定义,游戏不猜地址或坐标。
|
||||
|
||||
`onOpenHelp(spid)` → 帮助内容/页面能力,不暴露原精灵编号。
|
||||
|
||||
### 2.7 gameHallImport
|
||||
|
||||
appStart、jumpMenuScene、gameStart、setGameList、clearGameinfo、getWebdata、isInstalled、up_imgurl、getphoto 属于宿主/大厅适配。应与房间游戏 SDK 分开,不要求每个子游戏定义一套全局对象。
|
||||
|
||||
isInstalled 等示例固定返回值不能成为新实现的真实能力证明。
|
||||
|
||||
## 3. Output:游戏使用平台能力
|
||||
|
||||
源码:08_Utl_Output.js。按职责归属迁移,不按原函数顺序重新堆成大工具类。
|
||||
|
||||
### 3.1 身份、玩家和房间查询
|
||||
|
||||
涉及:getMyInfo、getGameID、getAgentID、getPlayeridBySeat、getNicknameBySeat、getSexBySeat、getMyPlayerid、getMySex、getRoomcode、getMySeat、getMyOpenid、getPlayerInfoBySeat、getPlayerList、getPlayerCnt、getPlayerReadyState、getBeanBySeat、getShortCode、getIsInfinite、getInfMode。
|
||||
|
||||
归属:RoomReadPort 的只读快照、必要身份投影和具名选择器。occupiedCount 与 seatCount 分开,空位有明确表示;缺失玩家不能返回 -1 后让 UI 猜测。
|
||||
|
||||
getMyOpenid 不应作为普通玩法的默认必需字段;确有原业务用途时,通过最小身份能力提供,不把完整登录响应暴露给所有游戏。
|
||||
|
||||
changeToStatus → 统一 seat mapper。服务端座位和显示座位可使用不同类型别名,减少混用,转换只在边界进行。
|
||||
|
||||
getOnState/isMainScene → 分别读取状态投影和生命周期,不能读取 UI 数组或 Node.active 来决定业务。
|
||||
|
||||
### 3.2 网络与房间命令
|
||||
|
||||
sendData → 当前游戏 route 绑定的 GameMessagePort。app/route 不由子游戏每次传入,rpc/data 仍由游戏定义。
|
||||
|
||||
sendExitRoom/sendChangeRoom/sendText/enterShareRoom/getShareRoom → 公共具名命令。平台负责身份和房间参数,调用方只提供真正变化的业务参数。
|
||||
|
||||
Exit → 不暴露为可任意清空平台状态的方法。区分请求退出、已确认的本地会话结束、结算返回大厅,由协调器内部执行。
|
||||
|
||||
openMatchUrl、getMathInfo/getIsMathInfo、getAdvanced/getPlayerAdvanced、openSnrOption、getRebateRange → 对应比赛、VIP/房间策略能力。不要混入通用游戏传输端口;不支持的模式明确标记。
|
||||
|
||||
### 3.3 状态修改与展示
|
||||
|
||||
setGrade → 游戏状态中的局内分数,经 GameRoomProjection/HUD 展示模型更新,布局由 View 负责。
|
||||
|
||||
changeBean → 先追踪调用源。若是玩法结算的相对得分,应写游戏投影;若是权威资产更新,走对应协议来源。禁止 initialBean + delta 直接覆盖平台账户余额。
|
||||
|
||||
setPlayerPrepare/setDeskStage/changePlayerState → 拆分公共协议事实、游戏阶段/参与状态、公共动作限制。必要映射显式声明,禁止通用 setter 任意修改平台状态。
|
||||
|
||||
closeMainSceneButton/closeInvitation/closeCommunication/updatePlayerInfoUI → 公共展示模型的状态或具名 UI 操作。业务长期显隐通过模型表达,短暂关闭模态框通过所有者句柄执行,不直接查找平台节点。
|
||||
|
||||
getExitVisible/getChangeVisible → RoomPolicy 派生值。命令执行时仍重新验证当前 scope 与策略,不能只靠按钮隐藏防止错误操作。
|
||||
|
||||
### 3.4 UI、资源和声音
|
||||
|
||||
openTips/openTips2/closeTips → 有所有者的提示能力,返回可关闭句柄;一个游戏不能关闭其他来源的提示。
|
||||
|
||||
Layer612_Tips 的内容仅面向玩家业务。reportError/catch/Error.message 不得直接转接提示端口;配置、协议、资源及代码异常写诊断日志,业务提示通过单独的 notify/showBusinessTip 入口。不能把技术异常转移到 Kick 等其他玩家面板。
|
||||
|
||||
startLoad/endLoad → 任务绑定的 loading 句柄或计数租约;并发两个任务时,一个结束不能关闭另一个的加载提示。
|
||||
|
||||
openInputPanel/closeInputPanel/openTextInput → 有请求 ID(仅本地)、明确结果和取消语义的输入能力。
|
||||
|
||||
getMultipleResult → 纯计算与倍率偏好读取分开;展示倍率选择交给 UI 端口,禁止计算函数暗改 GameData.OrgArr 或存储。
|
||||
|
||||
playMusic/playSound/stopMusic/stopSound → AudioPort。声音标识来自游戏资源定义,播放策略遵循平台设置,句柄受 scope 管理。
|
||||
|
||||
getHeadimgSrc/openInfo/openInfo1 → 头像资源租约和玩家资料能力;不返回 116+seat 这类精灵 ID。
|
||||
|
||||
setFontColor/convertNumberToImg/getNameImgFrame_1/getNameImgFrame_2 → 展示适配层。Cocos 文本使用 Label/BitmapFont,具体业务不依赖字符替换 b/c/d 或图集帧号。
|
||||
|
||||
getRoomCardName/getstarName → 平台展示配置投影,保留单一来源,不在游戏里重复定义。
|
||||
|
||||
### 3.5 持久化与工具
|
||||
|
||||
SetStorage、Config.pre_* → 存储命名空间与键定义集中管理;账户/游戏作用域明确,不能以当前全局身份拼出不确定的键。
|
||||
|
||||
SaveData/ReadData/checkKey/RemoveItemByKey/ClearStorage、setCookie/getCookie/delCookie → 存储适配器。游戏只获得自身命名空间,不能清空应用全部登录/平台数据。
|
||||
|
||||
saveRoomtype/getRoomtype/delRoomtype → 游戏配置历史能力;保存与读取不转换 roomtype,版本迁移由游戏负责。
|
||||
|
||||
saveGradeInfo/readGradeInfo → 战绩/回放数据仓储,不与房间临时模型混放;来源、保留上限与账号隔离必须明确。
|
||||
|
||||
clone/removeItemFromArray → 普通内部工具或删除;不作为平台业务 SDK。新架构使用明确的数据所有权与不可变更新,避免原 clone 破坏数组类型。
|
||||
|
||||
### 3.6 结算、分享和回放
|
||||
|
||||
onGameFinished → 拆为已解析的结算事实、游戏结果展示和显式分享请求;渲染函数重复调用不会重复分享。
|
||||
|
||||
typeForActivity → 可选活动能力的一次性业务通知,绑定结算来源;不能在重绘中调用。
|
||||
|
||||
gameOver/mainScene → 由明确结束原因、结算状态与导航结果驱动。结束一局不等于结束房间。
|
||||
|
||||
openVideo/closeVideo → ReplaySession,独立状态、时钟、玩家视角,不修改在线房间数据。
|
||||
|
||||
### 3.7 设备与宿主环境
|
||||
|
||||
getLocation/getPhoneInfo/gameCopytext/getAppService/closeWindow → 设备/宿主能力;浏览器和原生各自实现相同契约。
|
||||
|
||||
getGameConfig/getVersionState/getH5Version/getIsDebugger/getShowShare → 配置与能力投影。新游戏优先判断声明的能力,而非散布平台版本号分支;调试配置不进入玩法规则。
|
||||
|
||||
getPayCodeBySeat、支付/VIP/分享等与房间非核心功能相连的读取 → 在迁移对应模式时核对实际字段来源,不能为满足接口数量提前返回假值。
|
||||
|
||||
## 4. 新接口的使用约定
|
||||
|
||||
### 4.1 查询
|
||||
|
||||
一次业务处理读取同一 revision 的快照。对象只读,任何修改都不通过 getter 返回对象完成。需要最新状态时再次读取;异步回调读取前确认 scope 仍有效。
|
||||
|
||||
### 4.2 命令
|
||||
|
||||
具名命令检查:scope 有效、登录有效、房间身份匹配、当前策略允许、是否已有同类 pending。发送成功不代表业务确认。
|
||||
|
||||
服务器没有关联 ID 时不得虚构关联。对于同房间互斥操作采用本地串行 pending,收到对应合法回复或超时后解除。重连恢复以服务器状态为准,不自动重发非幂等命令。
|
||||
|
||||
### 4.3 UI 请求
|
||||
|
||||
```ts
|
||||
type InputResult<T> =
|
||||
| { kind: 'confirmed'; value: T }
|
||||
| { kind: 'cancelled'; reason: 'user' | 'scope-ended' | 'replaced' };
|
||||
```
|
||||
|
||||
输入请求在离房时必须结束。数值模式返回 number,允许前导零的文本模式返回 string,不能在平台随意转换。类型签名可按不同请求模式精确约束。
|
||||
|
||||
### 4.4 投影
|
||||
|
||||
游戏模型是事实来源,投影只负责提取公共 HUD 所需字段。投影更新不能反向产生同一输入事件,避免状态→事件→状态循环。
|
||||
|
||||
平台准备/资产信息与投影若冲突,优先按各字段定义的唯一来源处理,并报告不一致,不采用“最后写入者胜出”。
|
||||
|
||||
### 4.5 一次性事件
|
||||
|
||||
结算活动、音效、分享等与状态渲染分开。重连直接重建状态,不默认重放历史音效、公告或分享。哪些效果需要重放由具体玩法契约明确规定。
|
||||
|
||||
## 5. 接入交付清单
|
||||
|
||||
- 一个权威 GameDefinition,明确必需和可选能力。
|
||||
- roomtype 编码/解析与规则测试,不要求平台理解格式。
|
||||
- 一个游戏模型以及进入来源/游戏 rpc 到模型的映射。
|
||||
- 创建页、房间 View 和资源定义;恰好一个页面契约实现。
|
||||
- SDK 回归夹具:创建、加入、重连有/无快照、开战、结算、取消、退出。
|
||||
- Cocos 实际创建按钮提交与 WebSocket 发包验证。
|
||||
- 已实现/未实现能力清单,不以空处理器通过检查。
|
||||
|
||||
只增加游戏目录和应用注册项即可接入。若仍必须修改平台菜单脚本、公共协议路由或提供很多固定返回值,说明边界设计尚未达标。
|
||||
|
||||
## 6. 过渡期
|
||||
|
||||
旧接口兼容层仅用于逐条迁移现有游戏,不作为新 SDK 的正式表面。每项兼容入口必须有目标能力、测试和移除条件。
|
||||
|
||||
不要求一次迁移所有历史宿主/VIP/回放功能,但必须明确未支持范围。任何尚未找到调用源或协议依据的接口不得凭名称实现。
|
||||
|
||||
## 7. 本轮实际落地位置
|
||||
|
||||
- StartWar:`PlatformToGameEvent.room.started`,保留完整 message/sourceRpc;线上 other_makewar 对应原 makewar 函数。原始游戏包由 `GameModule.handleGameMessage` 接收。
|
||||
- createRoom/onCreateRoom、myJoinRoom、Reconnect/DeskInfo:`room.entered.entry` 的来源分支与兼容 `restore`。开战优先于加入快照恢复。游戏自身牌桌显示仍未迁移。
|
||||
- 房间查询、座位映射:`requireRoomCapabilities(host).room.getSnapshot / subscribe / seat`。只读数据来自平台 Store。
|
||||
- 准备、退出、申请解散:同一 room 端口的 `prepare / requestExit / applyDissolution`,公共身份与投影权限由 PlatformCommands 处理。自定义游戏包使用 `messages.send`。
|
||||
- 数字、数字文本、倍率:`ui.requestNumber / requestDigitText / requestMultiplier`,返回 InputResult;本地取消、替换和 scope 结束均有结果。原回调式 Host 命令暂留兼容。
|
||||
- loading、玩家业务提示:`ui.beginLoading / showBusinessTip` 返回 `{close()}`,通过真实 FeedbackPanels 仲裁所有者。技术错误不得传入这些接口。
|
||||
- 资源生命周期:`scope.active / defer`;底层 RoomScope 复用既有 Host 租约失效机制。取消先撤销能力,再逐项释放资源。
|
||||
- 局内公共事实:`createGameRoomProjection` 的 owner 提交完整版本,GameModule 暴露 source;公共 render 第三个参数和命令权限读取同一来源。现有模板/二七王返回 null,未生成伪 phase 或局内分数。
|
||||
- 头像、聊天:RoomUi 使用共享租约;RoomChatPanel 使用本地 ID 增量行和同帧合并。协议不增加消息 ID。
|
||||
- 音频所有者接口:未提供,原生契约没有播放停止能力。原有 RoomVoiceAdapter 的语音功能保留;支付、分享、回放和其他未迁移 Output 接口继续按其原边界单独接入。
|
||||
|
||||
新游戏只需要自己的代码/资源、组合根注册,以及编辑器中对应的 SubgameAssets 资源配置。运行期不会预执行工厂来推断模块或资源;真实 open 时只创建一个模块并校验。
|
||||
Reference in New Issue
Block a user