Files
bl/docs/pvp-cross-server-battle-cmd-data-design-2026-04-27.md
xinian 45f1485a11
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
feat: 支持跨服战斗原始cmd/data转发
2026-04-27 06:12:13 +08:00

6.8 KiB
Raw Blame History

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 发送方,不能直接断。

改造目标

把跨服战斗中的“客户端操作转发”改成直接发送:

{
  "cmd": "battle_client_command",
  "data": {
    "sessionId": "xsvr-...",
    "userId": 10001,
    "cmd": 2405,
    "data": {
      "SkillId": 1234
    }
  }
}

核心含义:

  • 外层 cmdPVP 内部 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

结果:

  • pvppvpwire 包通过。
  • controller 可以完成编译检查。

补充说明:

  • go test ./controller ./service/player ... 在当前环境会因为依赖初始化读取 /proc/sys/kernel/osrelease 失败而 panic这属于运行环境问题不是这次改动引入的编译错误。

后续建议

如果后面继续收敛这块逻辑,可以按下面顺序做:

  1. battle_command 的旧语义层逐步下线,只保留 battle_client_command
  2. handleBattleClientCommand 里的 cmd 映射抽成独立表,避免 switch 继续膨胀。
  3. 如果后续战斗协议继续统一,可考虑把 controller 入站结构和跨服转发 decode 共享同一套注册表。