RhythMC-Reborn 数据包格式文档
目录
数据包概述
数据包分类
设计原则
| 原则 | 描述 |
|---|---|
| 类型安全 | 使用 sealed interface 限制实现类型 |
| 不可变 | 使用 record 确保数据不可变 |
| 自描述 | 包含 type 字段用于类型识别 |
| JSON 友好 | 字段命名使用下划线风格 |
HTTP 数据包
HttpPayload 接口
public sealed interface HttpPayload
permits GameplayRecord, StatusRecord {
String type();
}
GameplayRecord (游戏记录)
用途: 记录玩家单局游戏的详细数据
结构图:
JSON 示例:
{
"type": "gameplay",
"levelID": 1,
"acc": 98,
"isRival": true,
"isWin": true,
"rivalUID": 67890,
"rivalAcc": 95,
"tapStats": {
"perfect": 150,
"great": 12,
"miss": 3
},
"holdStats": {
"perfect": 80,
"great": 5,
"miss": 0
},
"dodgeStats": {
"perfect": 40,
"great": 2,
"miss": 1
},
"lookStats": {
"perfect": 30,
"great": 1,
"miss": 0
},
"maxCombo": 245,
"pass": true,
"fullCombo": false,
"allPerfect": false,
"isNewRecord": true,
"player_arena": "arena_01",
"time": 1700000000000
}
字段说明表:
| 字段 | 类型 | 范围 | 描述 |
|---|---|---|---|
| type | String | "gameplay" | 类型标识 |
| levelID | int | > 0 | 关卡唯一标识 |
| acc | int | 0-100 | 准确率百分比 |
| isRival | boolean | - | 是否为对战模式 |
| isWin | boolean | - | 对战模式是否胜利 |
| rivalUID | int | > 0 | 对手玩家UID |
| rivalAcc | int | 0-100 | 对手准确率 |
| tapStats | StatsRecord | - | TAP 音符判定统计 |
| holdStats | StatsRecord | - | HOLD 音符判定统计 |
| dodgeStats | StatsRecord | - | DODGE 音符判定统计 |
| lookStats | StatsRecord | - | LOOK 音符判定统计 |
| maxCombo | int | >= 0 | 本局最大连击数 |
| pass | boolean | - | 是否通关 |
| fullCombo | boolean | - | 是否全连(无MISS) |
| allPerfect | boolean | - | 是否全PERFECT |
| isNewRecord | boolean | - | 是否刷新个人纪录 |
| player_arena | String | - | 游戏场地标识 |
| time | long | - | Unix时间戳(毫秒) |
StatusRecord (状态记录)
用途: 记录服务器当前在线玩家数
结构图:
JSON 示例:
{
"type": "status",
"playerCount": 42
}
BaseHttpRecord (HTTP记录容器)
用途: 包装 HTTP 数据包,添加玩家和服务器信息
结构图:
JSON 示例:
{
"player": {
"playerUID": 12345,
"accessToken": "player-access-token",
"timestamp": 1700000000000
},
"server": {
"serverID": 100,
"serverName": "CN-East-01",
"country": "CN"
},
"payload": {
"type": "gameplay",
"levelID": 1,
"acc": 98
}
}
WebSocket 数据包
WsPayload 接口
public sealed interface WsPayload
permits RivalDisconnect, SyncRival, Ping, Pong {
String type();
}
类型层次
Ping (心跳请求)
方向: Server → Client
用途: 检测客户端连接状态
{
"type": "ping"
}
Pong (心跳响应)
方向: Client → Server
用途: 响应服务器心跳请求
{
"type": "pong"
}
SyncRival (对手数据同步)
方向: 双向
用途: 实时同步对手游戏数据
结构图:
JSON 示例:
{
"type": "sync_rival",
"playerId": 12345,
"data": {
"score": 98500,
"combo": 127,
"accuracy": 98.5,
"health": 85
}
}
RivalDisconnect (对手断开)
方向: Server → Client
用途: 通知客户端对手已断开连接
结构图:
JSON 示例:
{
"type": "rival_disconnect",
"playerId": 12345
}
基础数据结构
完整类图
BaseInfo (玩家基础信息)
public record BaseInfo(
int playerUID, // 玩家唯一ID
String accessToken, // 玩家短期 access token
long timestamp // 时间戳
) {
public static BaseInfo build(RhyPlayer player) {
return new BaseInfo(
player.getUid(),
player.getSessionToken(),
System.currentTimeMillis()
);
}
}
JSON 示例:
{
"playerUID": 12345,
"accessToken": "player-access-token",
"timestamp": 1700000000000
}
ServerInfo (服务器信息)
public record ServerInfo(
int serverID, // 服务器ID
String serverName, // 服务器名称
String country // 所在国家/地区
) {
public static ServerInfo build(int serverID, String serverName, String country) {
return new ServerInfo(serverID, serverName, country);
}
}
JSON 示例:
{
"serverID": 100,
"serverName": "CN-East-01",
"country": "CN"
}
StatsRecord (判定统计)
用途: 统计各类型音符的判定结果
public record StatsRecord(
int perfect, // PERFECT 判定数
int great, // GREAT 判定数
int miss // MISS 判定数
) {}
JSON 示例:
{
"perfect": 150,
"great": 12,
"miss": 3
}
DownloadInfo (下载信息)
用途: 资源下载任务信息
public record DownloadInfo(
String path, // 文件相对路径
String url, // 下载URL
long bytes, // 文件大小(字节)
String sha1 // SHA1 校验值
) {}
JSON 示例:
{
"path": "songs/song1.rsm",
"url": "https://cdn.example.com/songs/song1.rsm",
"bytes": 1048576,
"sha1": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0"
}
序列化规范
JSON 序列化配置
使用 Alibaba FastJSON2 进行序列化:
// 序列化为 JSON 字符串
String json = JSON.toJSONString(object);
// 从 JSON 字符串解析
T object = JSON.parseObject(json, T.class);
类型识别机制
类型路由代码
// WebSocket 消息反序列化
public static WsPayload fromWsJson(String json) {
JSONObject obj = JSON.parseObject(json);
String type = obj.getString("type");
return switch (type) {
case "ping" -> JSON.parseObject(json, Ping.class);
case "pong" -> JSON.parseObject(json, Pong.class);
case "sync_rival" -> JSON.parseObject(json, SyncRival.class);
case "rival_disconnect" -> JSON.parseObject(json, RivalDisconnect.class);
default -> throw new IllegalArgumentException("Unknown type: " + type);
};
}
// HTTP 数据包反序列化
public static HttpPayload fromHttpPayloadJson(String json) {
JSONObject obj = JSON.parseObject(json);
String type = obj.getString("type");
return switch (type) {
case "gameplay" -> JSON.parseObject(json, GameplayRecord.class);
case "status" -> JSON.parseObject(json, StatusRecord.class);
default -> throw new IllegalArgumentException("Unknown type: " + type);
};
}
泛型反序列化
// 使用 TypeReference 处理泛型
public static BaseHttpRecord<?> fromBaseHttpRecordJson(String json) {
JSONObject obj = JSON.parseObject(json);
JSONObject payload = obj.getJSONObject("payload");
String type = payload.getString("type");
return switch (type) {
case "gameplay" -> JSON.parseObject(json,
new TypeReference<BaseHttpRecord<GameplayRecord>>() {});
case "status" -> JSON.parseObject(json,
new TypeReference<BaseHttpRecord<StatusRecord>>() {});
default -> throw new IllegalArgumentException("Unknown type: " + type);
};
}
数据包类型速查表
HTTP 数据包
| 类型标识 | 类名 | 方向 | 用途 |
|---|---|---|---|
| gameplay | GameplayRecord | C → S | 上传游戏记录 |
| status | StatusRecord | C → S | 上传服务器状态 |
WebSocket 数据包
| 类型标识 | 类名 | 方向 | 用途 |
|---|---|---|---|
| ping | Ping | S → C | 心跳请求 |
| pong | Pong | C → S | 心跳响应 |
| sync_rival | SyncRival | 双向 | 对手数据同步 |
| rival_disconnect | RivalDisconnect | S → C | 对手断开通知 |
数据结构
| 名称 | 用途 |
|---|---|
| BaseInfo | 玩家基础信息 |
| ServerInfo | 服务器信息 |
| StatsRecord | 判定统计 |
| DownloadInfo | 下载信息 |
| BaseHttpRecord | HTTP 数据容器 |