# PVP 跨服战斗消息 `cmd/data` 转发改造说明 ## 背景 巅峰赛跨服战斗原本已经有 Redis pub/sub 通道,但战斗中的客户端操作转发仍然偏间接: 1. 本服 controller 先把客户端请求翻译成 `FightI` 调用。 2. `RemoteFightProxy` 再把 `FightI` 调用翻译成 `battle_command`。 3. 宿主服收到 `battle_command` 后,再翻译回 `FightI` 调用。 这样一来,跨服战斗链路和本地战斗链路之间多了一层“动作语义映射”,调试时不容易直接看到前端到底发了哪个 cmd、带了什么 data。 本次改造只处理“战斗中的消息转发”,不改匹配/加入队列逻辑。 ## 保持不变的部分 - 巅峰赛加入和取消匹配仍然走原有 RPC: - `MatchJoinOrUpdate` - `MatchCancel` - Ban/Pick、MatchFound、SessionClose 等 PVP 服务内消息仍兼容旧外层格式。 - `battle_command` 旧语义消息仍保留,作为兼容兜底,不强制一次性切掉。 对应原因: - 匹配加入需要同步返回结果,RPC 更直接。 - 现网如果已有旧 envelope 或旧 battle command 发送方,不能直接断。 ## 改造目标 把跨服战斗中的“客户端操作转发”改成直接发送: ```json { "cmd": "battle_client_command", "data": { "sessionId": "xsvr-...", "userId": 10001, "cmd": 2405, "data": { "SkillId": 1234 } } } ``` 核心含义: - 外层 `cmd`:PVP 内部 Redis 消息类型。 - 内层 `data.cmd`:真实客户端战斗协议 cmd。 - 内层 `data.data`:该 cmd 对应的 JSON 业务字段。 这样宿主服拿到消息后,可以直接按真实战斗 cmd 处理,而不是先理解一层额外的“跨服动作协议”。 ## 本次代码落点 ### 1. 新增 `cmd/data` envelope 能力 文件: - `logic/service/fight/pvpwire/types.go` 改动: - `Envelope` 新增: - `Cmd string` - `Data json.RawMessage` - 保留旧字段: - `Type string` - `Body []byte` - 新增辅助方法: - `NewEnvelope` - `MessageCmd` - `MessageData` 作用: - 新消息可以直接用 `cmd/data`。 - 旧消息仍可通过 `type/body` 解析。 ### 2. controller 层遇到远端跨服战斗时,直接转发原始战斗 cmd 文件: - `logic/controller/fight_unified.go` - `logic/controller/fight_base.go` 改动: - 在 `fight_unified.go` 增加 `relayRemoteFightCommand`。 - 当 `c.FightC` 是 `*pvp.RemoteFightProxy` 时,不再走本地 `dispatchFightActionEnvelope` 分发,而是直接转发当前请求的: - `data.Head.CMD` - 当前请求结构体 `data` 已接入的战斗入口包括: - `2404` 准备 - `7556` 旧组队准备 - `2405` 单战位技能 - `7505` 多战位技能 - `7558` 旧组队技能 - `2406` 使用道具 - `7562` 旧组队道具 - `2407` 切宠 - `7563` 旧组队切宠 - `2410` 逃跑 - `7565` 旧组队逃跑 - `2441` 加载进度 - `50002` 战斗聊天 ### 3. RemoteFightProxy 新增“直接转发客户端命令”能力 文件: - `logic/service/fight/pvp/proxy.go` 改动: - 新增 `RelayClientCommand(cmd uint32, data any) bool` - 新增 `marshalClientCommandData(data any) ([]byte, error)` 说明: - `marshalClientCommandData` 会把请求结构体转成 JSON。 - 会删除 `Head/head` 字段,避免把协议头、二进制字段一起塞进跨服消息。 - 这样发出去的 `data.data` 只保留业务字段。 ### 4. PVP 宿主服新增 `battle_client_command` 处理 文件: - `logic/service/fight/pvp/service.go` - `logic/service/fight/pvpwire/types.go` 改动: - 新增消息类型: - `MessageTypeBattleClientCommand` - 新增载荷: - `BattleClientCommandPayload` - `handleRedisMessage` 增加 `battle_client_command` 分支。 - 新增 `handleBattleClientCommand`。 处理方式: - 宿主服根据内层真实 `cmd` 解出 `data`。 - 再直接调用当前 `FightI` / `FightC`。 当前支持的映射: - `2404` / `7556` -> `ReadyFight` - `2405` / `7505` / `7558` -> `UseSkillAt` - `2406` / `7562` -> `UseItemAt` - `2407` / `7563` -> `ChangePetAt` - `2410` / `7565` -> `Over(PlayerEscape)` - `2441` -> `LoadPercent` - `50002` -> `Chat` ### 5. 战斗发包转发也统一改成 `cmd/data` 文件: - `logic/service/player/rpc.go` 改动: - `PacketRelayPayload` 外层 envelope 从 `type/body` 改为 `cmd/data`。 原因: - 它本质也是跨服战斗中的消息转发。 - 和新的 `battle_client_command` 保持一致,便于抓包和日志排查。 ## 当前实际链路 ### 客户端操作转发 1. 客户端发战斗包到本服 controller。 2. controller 判断当前 `FightC` 是否为 `RemoteFightProxy`。 3. 如果是: - 直接发布 `battle_client_command` - 内层携带真实 `cmd` 和 JSON `data` 4. 宿主服 `pvp.service` 收到后按 `cmd` 分发回战斗逻辑。 ### 战斗结果/下行发包转发 1. 宿主服在 `RPC_player.SendPackCmd` 中组包。 2. 发布 `packet_relay` 消息,外层使用 `cmd/data`。 3. 客服端所在服收到后解包并发回真实客户端。 ## 兼容策略 为了避免一次性改动过大,这次保留了两层兼容: ### 1. 旧 envelope 兼容 `Envelope` 同时支持: - 新格式:`cmd/data` - 旧格式:`type/body` `handleRedisMessage` 统一通过: - `MessageCmd()` - `MessageData()` 读取消息。 ### 2. 旧 `battle_command` 兼容 `RemoteFightProxy` 旧的: - `UseSkill` - `UseSkillAt` - `UseItem` - `ChangePet` - `Chat` 这些方法仍然保留,并继续发送原来的 `battle_command`。 新接入的 controller 优先走 `battle_client_command`,旧路径仍可兜底。 ## 这次改造的收益 ### 好处 - 跨服战斗消息更直观,日志里能直接看到真实客户端 cmd。 - controller 到宿主服之间不需要再先翻译成一套额外动作协议。 - 宿主服调试时更容易复现前端输入。 - 新老消息可并存,改造风险相对可控。 ### 仍然保留的复杂度 - 宿主服仍然需要针对不同战斗 cmd 做一次 decode 和分发。 - 这是必要复杂度,因为真实战斗入口本来就有多种协议包。 ## 验证 本次已执行: - `cd logic && go test ./service/fight/pvp ./service/fight/pvpwire` - `cd logic && go test -c ./controller` 结果: - `pvp`、`pvpwire` 包通过。 - `controller` 可以完成编译检查。 补充说明: - `go test ./controller ./service/player ...` 在当前环境会因为依赖初始化读取 `/proc/sys/kernel/osrelease` 失败而 panic,这属于运行环境问题,不是这次改动引入的编译错误。 ## 后续建议 如果后面继续收敛这块逻辑,可以按下面顺序做: 1. 把 `battle_command` 的旧语义层逐步下线,只保留 `battle_client_command`。 2. 把 `handleBattleClientCommand` 里的 cmd 映射抽成独立表,避免 `switch` 继续膨胀。 3. 如果后续战斗协议继续统一,可考虑把 controller 入站结构和跨服转发 decode 共享同一套注册表。