update:更新比赛服逻辑
This commit is contained in:
+301
-30
@@ -1,34 +1,199 @@
|
||||
import matchRoot from "./match.min.js";
|
||||
import protobuf from "protobufjs/minimal.js";
|
||||
|
||||
const { Reader, Writer } = protobuf;
|
||||
|
||||
// 比赛服 protobuf 协议适配层:
|
||||
// 这里只负责 raw protobuf frame 的解码和客户端消息编码,不处理 UI 状态。
|
||||
const ServerMessage = matchRoot.lookupType("rpc.ServerMessage");
|
||||
const ClientMessage = matchRoot.lookupType("rpc.ClientMessage");
|
||||
// 小程序环境不支持 protobufjs 反射模式里的动态 Function codegen,
|
||||
// 所以这里使用 minimal Reader/Writer 做静态字段解码和客户端消息编码。
|
||||
export const ServerMessageType = {
|
||||
SERVER_MSG_UNKNOWN: 0,
|
||||
SERVER_MSG_MATCH_READY: 1,
|
||||
SERVER_MSG_MATCH_START: 2,
|
||||
SERVER_MSG_NOW_YOU: 3,
|
||||
SERVER_MSG_SHOT: 4,
|
||||
SERVER_MSG_NEW_ROUND: 5,
|
||||
SERVER_MSG_MATCH_END: 6,
|
||||
SERVER_MSG_TIMEOUT: 7,
|
||||
SERVER_MSG_CHECK: 8,
|
||||
SERVER_MSG_RAND: 9,
|
||||
SERVER_MSG_NOT_ENOUGH_DISTANCE: 10,
|
||||
SERVER_MSG_PLAYER_LEFT: 11,
|
||||
SERVER_MSG_HEARTBEAT: 12,
|
||||
SERVER_MSG_PRACTICE_END: 13,
|
||||
};
|
||||
|
||||
// 从反射 Root 中读取枚举,避免直接依赖生成文件内部结构。
|
||||
function getEnumValues(name) {
|
||||
return matchRoot.lookupEnum(name).values || {};
|
||||
}
|
||||
export const ClientMessageType = {
|
||||
CLIENT_MSG_UNKNOWN: 0,
|
||||
CLIENT_MSG_HEARTBEAT_ACK: 1,
|
||||
CLIENT_MSG_SHOOT_DATA: 2,
|
||||
CLIENT_MSG_ACK: 3,
|
||||
CLIENT_MSG_LEAVE: 4,
|
||||
};
|
||||
|
||||
// protobufjs 的 enum 默认是 name -> value,这里反转成 value -> name 用于日志打印。
|
||||
function invertEnum(values) {
|
||||
return Object.keys(values).reduce((result, name) => {
|
||||
result[values[name]] = name;
|
||||
const ServerMessageTypeNameByValue = Object.keys(ServerMessageType).reduce(
|
||||
(result, name) => {
|
||||
result[ServerMessageType[name]] = name;
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
export const ServerMessageType = getEnumValues("rpc.ServerMessageType");
|
||||
export const ClientMessageType = getEnumValues("rpc.ClientMessageType");
|
||||
|
||||
const ServerMessageTypeNameByValue = invertEnum(ServerMessageType);
|
||||
|
||||
// 统一把 int64 转成字符串,避免小程序环境下大整数精度丢失。
|
||||
const TO_OBJECT_OPTIONS = {
|
||||
longs: String,
|
||||
enums: Number,
|
||||
bytes: Array,
|
||||
oneofs: true,
|
||||
// 当前只需要解码比赛服下发的字段并打印,字段表按后端 proto 定义维护。
|
||||
const SCHEMAS = {
|
||||
MatchShoot: {
|
||||
1: { name: "player_id", kind: "int64" },
|
||||
2: { name: "status", kind: "int32" },
|
||||
3: { name: "x", kind: "float" },
|
||||
4: { name: "y", kind: "float" },
|
||||
5: { name: "ring", kind: "int32" },
|
||||
6: { name: "ring_x", kind: "bool" },
|
||||
7: { name: "angle", kind: "float" },
|
||||
8: { name: "distance", kind: "float" },
|
||||
},
|
||||
MatchShootList: {
|
||||
1: { name: "items", kind: "message", type: "MatchShoot", repeated: true },
|
||||
},
|
||||
RoundScore: {
|
||||
1: { name: "total_ring", kind: "int32" },
|
||||
2: { name: "score", kind: "int32" },
|
||||
3: { name: "if_win", kind: "bool" },
|
||||
},
|
||||
MatchRound: {
|
||||
1: {
|
||||
name: "shoots",
|
||||
kind: "map",
|
||||
keyKind: "int64",
|
||||
valueKind: "message",
|
||||
valueType: "MatchShootList",
|
||||
},
|
||||
2: {
|
||||
name: "scores",
|
||||
kind: "map",
|
||||
keyKind: "int32",
|
||||
valueKind: "message",
|
||||
valueType: "RoundScore",
|
||||
},
|
||||
3: { name: "round", kind: "int32" },
|
||||
4: { name: "if_gold", kind: "bool" },
|
||||
5: { name: "status", kind: "int32" },
|
||||
6: { name: "gold_round", kind: "int32" },
|
||||
},
|
||||
PlayerMatchResult: {
|
||||
1: { name: "total_ring", kind: "int32" },
|
||||
2: { name: "user_id", kind: "int64" },
|
||||
3: { name: "ten_ring_count", kind: "int32" },
|
||||
4: { name: "average_ring", kind: "float" },
|
||||
},
|
||||
PlayerFull: {
|
||||
1: { name: "id", kind: "int64" },
|
||||
2: { name: "name", kind: "string" },
|
||||
3: { name: "avatar", kind: "string" },
|
||||
4: { name: "exp", kind: "int32" },
|
||||
5: { name: "score", kind: "int32" },
|
||||
6: { name: "level", kind: "int32" },
|
||||
7: { name: "before_level", kind: "int32" },
|
||||
8: { name: "before_exp", kind: "int32" },
|
||||
9: { name: "current_exp", kind: "int32" },
|
||||
10: { name: "upgrade_exp", kind: "int32" },
|
||||
11: { name: "active", kind: "int32" },
|
||||
12: { name: "device_id", kind: "string" },
|
||||
13: { name: "s_vip", kind: "bool" },
|
||||
14: { name: "vip", kind: "bool" },
|
||||
15: { name: "player_match_result", kind: "message", type: "PlayerMatchResult" },
|
||||
},
|
||||
TeamInfo: {
|
||||
1: { name: "players", kind: "message", type: "PlayerFull", repeated: true },
|
||||
2: { name: "id", kind: "int32" },
|
||||
3: { name: "name", kind: "string" },
|
||||
4: { name: "score", kind: "int32" },
|
||||
},
|
||||
CurrentShoot: {
|
||||
1: { name: "round", kind: "int32" },
|
||||
2: { name: "round_id", kind: "int64" },
|
||||
3: { name: "index", kind: "int32" },
|
||||
4: { name: "start_time", kind: "int64" },
|
||||
5: { name: "player_id", kind: "int64" },
|
||||
6: { name: "gold_round", kind: "bool" },
|
||||
7: { name: "start_time_text", kind: "string" },
|
||||
8: { name: "my_index", kind: "int32" },
|
||||
9: {
|
||||
name: "index_map",
|
||||
kind: "map",
|
||||
keyKind: "int64",
|
||||
valueKind: "int32",
|
||||
},
|
||||
},
|
||||
ShootData: {
|
||||
1: { name: "x", kind: "float" },
|
||||
2: { name: "y", kind: "float" },
|
||||
3: { name: "r", kind: "float" },
|
||||
4: { name: "dst", kind: "float" },
|
||||
5: { name: "m", kind: "string" },
|
||||
6: { name: "adc", kind: "float" },
|
||||
7: { name: "device_id", kind: "string" },
|
||||
8: { name: "shoot_id", kind: "string" },
|
||||
},
|
||||
PracticeInfo: {
|
||||
1: { name: "id", kind: "int64" },
|
||||
2: { name: "user_id", kind: "int64" },
|
||||
3: { name: "status", kind: "int32" },
|
||||
4: { name: "status_text", kind: "string" },
|
||||
5: { name: "start_time", kind: "int64" },
|
||||
6: { name: "target_type", kind: "int32" },
|
||||
7: { name: "vip", kind: "bool" },
|
||||
8: { name: "s_vip", kind: "bool" },
|
||||
9: { name: "device_id", kind: "string" },
|
||||
10: { name: "shoot_data", kind: "message", type: "MatchShoot" },
|
||||
11: { name: "details", kind: "message", type: "MatchShoot", repeated: true },
|
||||
},
|
||||
MatchInfo: {
|
||||
1: { name: "match_id", kind: "int64" },
|
||||
2: { name: "create_time", kind: "int64" },
|
||||
3: { name: "start_time", kind: "int64" },
|
||||
4: { name: "server_time", kind: "int64" },
|
||||
5: { name: "shoot_time", kind: "int32" },
|
||||
6: { name: "shoot_number", kind: "int32" },
|
||||
7: { name: "ready_time", kind: "int32" },
|
||||
8: { name: "way", kind: "int32" },
|
||||
9: { name: "mode", kind: "int32" },
|
||||
10: { name: "status", kind: "int32" },
|
||||
11: { name: "status_text", kind: "string" },
|
||||
12: { name: "rounds", kind: "message", type: "MatchRound", repeated: true },
|
||||
13: {
|
||||
name: "teams",
|
||||
kind: "map",
|
||||
keyKind: "int32",
|
||||
valueKind: "message",
|
||||
valueType: "TeamInfo",
|
||||
},
|
||||
14: { name: "current", kind: "message", type: "CurrentShoot" },
|
||||
15: { name: "next", kind: "message", type: "CurrentShoot" },
|
||||
16: { name: "shoot_data", kind: "message", type: "MatchShoot" },
|
||||
17: { name: "win_team", kind: "int32" },
|
||||
18: { name: "mvp", kind: "message", type: "PlayerFull" },
|
||||
19: { name: "room_id", kind: "string" },
|
||||
20: {
|
||||
name: "result_list",
|
||||
kind: "message",
|
||||
type: "PlayerMatchResult",
|
||||
repeated: true,
|
||||
},
|
||||
21: { name: "timeout_time", kind: "int64" },
|
||||
22: { name: "target_type", kind: "int32" },
|
||||
23: { name: "event_type", kind: "int32" },
|
||||
24: { name: "timeout", kind: "int32" },
|
||||
25: { name: "server_addr", kind: "string" },
|
||||
},
|
||||
ServerMessage: {
|
||||
1: { name: "type", kind: "int32" },
|
||||
2: { name: "match_id", kind: "int64" },
|
||||
3: { name: "timestamp", kind: "int64" },
|
||||
4: { name: "match_info", kind: "message", type: "MatchInfo", oneof: "payload" },
|
||||
5: { name: "shoot_data", kind: "message", type: "ShootData", oneof: "payload" },
|
||||
6: { name: "practice_info", kind: "message", type: "PracticeInfo", oneof: "payload" },
|
||||
7: { name: "sequence", kind: "int64" },
|
||||
},
|
||||
};
|
||||
|
||||
// 小程序 websocket 收到的 data 可能是 ArrayBuffer、TypedArray 或字符串,这里统一成 Uint8Array。
|
||||
@@ -55,16 +220,122 @@ function toArrayBuffer(bytes) {
|
||||
return output.buffer;
|
||||
}
|
||||
|
||||
function readScalar(reader, kind) {
|
||||
switch (kind) {
|
||||
case "int32":
|
||||
return reader.int32();
|
||||
case "int64":
|
||||
// 统一把 int64 转成字符串,避免小程序环境下大整数精度丢失。
|
||||
return reader.int64().toString();
|
||||
case "float":
|
||||
return reader.float();
|
||||
case "bool":
|
||||
return reader.bool();
|
||||
case "string":
|
||||
return reader.string();
|
||||
case "bytes":
|
||||
return Array.from(reader.bytes());
|
||||
default:
|
||||
throw new Error(`Unsupported protobuf scalar kind: ${kind}`);
|
||||
}
|
||||
}
|
||||
|
||||
function readMapEntry(reader, field) {
|
||||
const end = reader.uint32() + reader.pos;
|
||||
let key = "";
|
||||
let value;
|
||||
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1:
|
||||
key = readScalar(reader, field.keyKind);
|
||||
break;
|
||||
case 2:
|
||||
value =
|
||||
field.valueKind === "message"
|
||||
? decodeMessage(field.valueType, reader, reader.uint32())
|
||||
: readScalar(reader, field.valueKind);
|
||||
break;
|
||||
default:
|
||||
reader.skipType(tag & 7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { key: String(key), value };
|
||||
}
|
||||
|
||||
function readValue(reader, field) {
|
||||
if (field.kind === "message") {
|
||||
return decodeMessage(field.type, reader, reader.uint32());
|
||||
}
|
||||
return readScalar(reader, field.kind);
|
||||
}
|
||||
|
||||
function decodeMessage(schemaName, readerOrData, length) {
|
||||
const schema = SCHEMAS[schemaName];
|
||||
const reader =
|
||||
readerOrData instanceof Reader ? readerOrData : Reader.create(readerOrData);
|
||||
const end = length === undefined ? reader.len : reader.pos + length;
|
||||
const message = {};
|
||||
|
||||
while (reader.pos < end) {
|
||||
const tag = reader.uint32();
|
||||
const field = schema[tag >>> 3];
|
||||
|
||||
if (!field) {
|
||||
reader.skipType(tag & 7);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.kind === "map") {
|
||||
const entry = readMapEntry(reader, field);
|
||||
const map = message[field.name] || {};
|
||||
map[entry.key] = entry.value;
|
||||
message[field.name] = map;
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = readValue(reader, field);
|
||||
|
||||
if (field.repeated) {
|
||||
if (!message[field.name]) message[field.name] = [];
|
||||
message[field.name].push(value);
|
||||
} else {
|
||||
message[field.name] = value;
|
||||
}
|
||||
|
||||
if (field.oneof) {
|
||||
message[field.oneof] = field.name;
|
||||
}
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
export function decodeServerMessage(data) {
|
||||
// 后端已确认比赛服 websocket 下发的是原始 protobuf frame,直接 ServerMessage.decode。
|
||||
const message = ServerMessage.decode(toUint8Array(data));
|
||||
return ServerMessage.toObject(message, TO_OBJECT_OPTIONS);
|
||||
// 后端已确认比赛服 websocket 下发的是原始 protobuf frame,直接解 ServerMessage。
|
||||
return decodeMessage("ServerMessage", toUint8Array(data));
|
||||
}
|
||||
|
||||
export function encodeClientMessage(payload) {
|
||||
// ClientMessage 字段名按 proto 定义传入,例如 match_id、user_id。
|
||||
const message = ClientMessage.create(payload);
|
||||
return toArrayBuffer(ClientMessage.encode(message).finish());
|
||||
// ClientMessage 字段名按 proto 定义写入,例如 match_id、user_id。
|
||||
const writer = Writer.create();
|
||||
const matchId = payload.match_id ?? payload.matchId;
|
||||
const userId = payload.user_id ?? payload.userId;
|
||||
|
||||
if (payload.type !== undefined) writer.uint32(8).int32(payload.type);
|
||||
if (matchId !== undefined && matchId !== null) writer.uint32(16).int64(matchId);
|
||||
if (userId !== undefined && userId !== null) writer.uint32(24).int64(userId);
|
||||
if (payload.sequence !== undefined && payload.sequence !== null) {
|
||||
writer.uint32(32).int64(payload.sequence);
|
||||
}
|
||||
if (payload.data !== undefined && payload.data !== null) {
|
||||
writer.uint32(42).bytes(toUint8Array(payload.data));
|
||||
}
|
||||
|
||||
return toArrayBuffer(writer.finish());
|
||||
}
|
||||
|
||||
export function getServerMessageTypeName(type) {
|
||||
|
||||
Reference in New Issue
Block a user