303 lines
9.1 KiB
JavaScript
303 lines
9.1 KiB
JavaScript
// Proto.js - 游戏协议处理
|
||
// 基于 proto/action.proto 和 proto/define.proto 生成
|
||
|
||
// 协议定义(将由本地 .proto 文件动态填充枚举值)
|
||
const GameProto = {};
|
||
|
||
// 协议编码器
|
||
class ProtoEncoder {
|
||
constructor() {
|
||
this.protobuf = window.protobuf;
|
||
this.root = null;
|
||
this.readyPromise = this.initProtobuf();
|
||
}
|
||
|
||
async initProtobuf() {
|
||
try {
|
||
// 使用本地 .proto 文件,避免硬编码
|
||
const root = await this.protobuf.load([
|
||
"proto/define.proto",
|
||
"proto/action.proto"
|
||
]);
|
||
this.root = root;
|
||
// 缓存枚举,供其他模块使用
|
||
this.MessageID = this.root.lookupEnum('MessageID').values;
|
||
this.ActionID = this.root.lookupEnum('ActionID').values;
|
||
|
||
// 更新全局GameProto枚举(避免硬编码)
|
||
GameProto.MessageID = this.MessageID;
|
||
GameProto.ActionID = this.ActionID;
|
||
utils.info('Protobuf loaded from local files successfully');
|
||
} catch (error) {
|
||
utils.error('Failed to load protobuf files:', error);
|
||
}
|
||
}
|
||
|
||
// 编码消息
|
||
encodeMessage(messageId, payload) {
|
||
try {
|
||
if (!this.root) {
|
||
throw new Error('Protobuf not initialized');
|
||
}
|
||
|
||
const Message = this.root.lookupType('Message');
|
||
const message = {
|
||
ID: messageId,
|
||
Payload: payload
|
||
};
|
||
|
||
const buffer = Message.encode(message).finish();
|
||
return buffer;
|
||
} catch (error) {
|
||
utils.error('Failed to encode message:', error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 编码动作
|
||
encodeAction(actionId, payload) {
|
||
try {
|
||
if (!this.root) {
|
||
throw new Error('Protobuf not initialized');
|
||
}
|
||
|
||
const C2S_Action = this.root.lookupType('C2S_Action');
|
||
const action = {
|
||
Action: actionId,
|
||
Payload: payload
|
||
};
|
||
|
||
const buffer = C2S_Action.encode(action).finish();
|
||
return buffer;
|
||
} catch (error) {
|
||
utils.error('Failed to encode action:', error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 解码消息
|
||
decodeMessage(buffer) {
|
||
try {
|
||
if (!this.root) {
|
||
throw new Error('Protobuf not initialized');
|
||
}
|
||
|
||
const Message = this.root.lookupType('Message');
|
||
const message = Message.decode(buffer);
|
||
return message;
|
||
} catch (error) {
|
||
utils.error('Failed to decode message:', error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 解码动作
|
||
decodeAction(buffer) {
|
||
try {
|
||
if (!this.root) {
|
||
throw new Error('Protobuf not initialized');
|
||
}
|
||
|
||
const C2S_Action = this.root.lookupType('C2S_Action');
|
||
const action = C2S_Action.decode(buffer);
|
||
return action;
|
||
} catch (error) {
|
||
utils.error('Failed to decode action:', error);
|
||
return null;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 协议工具类
|
||
class ProtoUtils {
|
||
constructor() {
|
||
this.encoder = new ProtoEncoder();
|
||
this.readyPromise = this.encoder.readyPromise;
|
||
}
|
||
|
||
// 创建移动指令
|
||
createMoveAction(x, y) {
|
||
const moveData = new Uint8Array([x, y]);
|
||
return this.encoder.encodeAction(this.encoder.ActionID.ACTION_ID_MOVE, moveData);
|
||
}
|
||
|
||
// 创建攻击指令
|
||
createAttackAction(targetId) {
|
||
const attackData = new Uint8Array([targetId]);
|
||
return this.encoder.encodeAction(this.encoder.ActionID.ACTION_ID_ATTACK, attackData);
|
||
}
|
||
|
||
// 创建消息
|
||
createMessage(messageId, payload) {
|
||
return this.encoder.encodeMessage(messageId, payload);
|
||
}
|
||
|
||
// 解析接收到的数据
|
||
parseReceivedData(data) {
|
||
try {
|
||
// 如果是字符串,尝试解析为JSON
|
||
if (typeof data === 'string') {
|
||
const jsonData = JSON.parse(data);
|
||
return {
|
||
type: 'json',
|
||
data: jsonData
|
||
};
|
||
}
|
||
|
||
// 如果是二进制数据,尝试解析为protobuf
|
||
if (data instanceof ArrayBuffer || data instanceof Uint8Array) {
|
||
const message = this.encoder.decodeMessage(data);
|
||
return {
|
||
type: 'protobuf',
|
||
data: message
|
||
};
|
||
}
|
||
|
||
return {
|
||
type: 'unknown',
|
||
data: data
|
||
};
|
||
} catch (error) {
|
||
utils.error('Failed to parse received data:', error);
|
||
return {
|
||
type: 'error',
|
||
data: null,
|
||
error: error.message
|
||
};
|
||
}
|
||
}
|
||
|
||
// 验证坐标数据
|
||
validateCoordinateData(data) {
|
||
if (!Array.isArray(data) || data.length !== 2) {
|
||
return false;
|
||
}
|
||
|
||
const [x, y] = data;
|
||
return utils.validateCoordinates(x, y);
|
||
}
|
||
|
||
// 格式化坐标数据
|
||
formatCoordinateData(data) {
|
||
if (this.validateCoordinateData(data)) {
|
||
const [x, y] = data;
|
||
return {
|
||
x: Math.round(x),
|
||
y: Math.round(y),
|
||
timestamp: Date.now()
|
||
};
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// 创建进入实例消息(外层Message)
|
||
createEnterInstanceMessage(instanceId = 0) {
|
||
try {
|
||
if (!this.encoder.root) {
|
||
utils.warn('Protobuf root not ready');
|
||
return null;
|
||
}
|
||
const C2S_EnterInstance = this.encoder.root.lookupType('C2S_EnterInstance');
|
||
const enterInstance = {
|
||
InstanceID: instanceId
|
||
};
|
||
const innerBuffer = C2S_EnterInstance.encode(enterInstance).finish();
|
||
return this.encoder.encodeMessage(
|
||
this.encoder.MessageID.MESSAGE_ID_ENTER_INSTANCE,
|
||
innerBuffer
|
||
);
|
||
} catch (error) {
|
||
utils.error('Failed to create EnterInstance message:', error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 创建动作消息(外层Message)
|
||
createActionMessage(actionId, x, y) {
|
||
try {
|
||
if (!this.encoder.root) {
|
||
utils.warn('Protobuf root not ready');
|
||
return null;
|
||
}
|
||
|
||
// 创建移动指令的payload
|
||
let payload;
|
||
if (actionId === this.encoder.ActionID.ACTION_ID_MOVE) {
|
||
// 创建包含x,y坐标的二进制数据
|
||
// 注意:这里使用Float64Array来存储双精度浮点数,因为proto中X和Y是double类型
|
||
const buffer = new ArrayBuffer(16); // 8字节 * 2 = 16字节
|
||
const view = new Float64Array(buffer);
|
||
view[0] = x;
|
||
view[1] = y;
|
||
payload = new Uint8Array(buffer);
|
||
} else {
|
||
// 其他类型的动作可以在这里添加
|
||
utils.warn('Unsupported action ID:', actionId);
|
||
return null;
|
||
}
|
||
|
||
// 创建C2S_Action消息
|
||
const C2S_Action = this.encoder.root.lookupType('C2S_Action');
|
||
const action = {
|
||
Action: actionId,
|
||
Payload: payload
|
||
};
|
||
const actionBuffer = C2S_Action.encode(action).finish();
|
||
|
||
// 封装到外层Message
|
||
return this.encoder.encodeMessage(
|
||
this.encoder.MessageID.MESSAGE_ID_ACTION,
|
||
actionBuffer
|
||
);
|
||
} catch (error) {
|
||
utils.error('Failed to create Action message:', error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 创建WASD方向动作消息(外层Message)
|
||
createDirectionActionMessage(directionBits) {
|
||
try {
|
||
if (!this.encoder.root) {
|
||
utils.warn('Protobuf root not ready');
|
||
return null;
|
||
}
|
||
|
||
// 创建C2S_Action消息,payload为空
|
||
const C2S_Action = this.encoder.root.lookupType('C2S_Action');
|
||
const action = {
|
||
Action: directionBits, // 使用位运算结果作为动作ID
|
||
Payload: new Uint8Array(0) // 空payload
|
||
};
|
||
const actionBuffer = C2S_Action.encode(action).finish();
|
||
|
||
// 封装到外层Message
|
||
return this.encoder.encodeMessage(
|
||
this.encoder.MessageID.MESSAGE_ID_ACTION,
|
||
actionBuffer
|
||
);
|
||
} catch (error) {
|
||
utils.error('Failed to create Direction Action message:', error);
|
||
return null;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 创建全局协议工具实例
|
||
const protoUtils = new ProtoUtils();
|
||
|
||
// 导出到全局
|
||
window.GameProto = GameProto;
|
||
window.ProtoEncoder = ProtoEncoder;
|
||
window.ProtoUtils = ProtoUtils;
|
||
window.protoUtils = protoUtils;
|
||
|
||
// 兼容性处理
|
||
if (typeof module !== 'undefined' && module.exports) {
|
||
module.exports = {
|
||
GameProto,
|
||
ProtoEncoder,
|
||
ProtoUtils,
|
||
protoUtils
|
||
};
|
||
}
|