diff --git a/examples/client_chat.js b/examples/client_chat.js index 4d7bbb5..7e9ac2e 100644 --- a/examples/client_chat.js +++ b/examples/client_chat.js @@ -44,10 +44,10 @@ var rl = readline.createInterface({ }); function print_help() { - console.log("usage: node minechat.js "); + console.log("usage: node client_chat.js []"); } -if (process.argv.length < 5) { +if (process.argv.length < 4) { console.log("Too few arguments!"); print_help(); process.exit(1); @@ -72,7 +72,6 @@ if (host.indexOf(':') != -1) { console.log("connecting to " + host + ":" + port); console.log("user: " + user); -console.log("passwd: " + Array(passwd.length).join('*')); var client = mc.createClient({ host: host, @@ -80,8 +79,8 @@ var client = mc.createClient({ username: user, password: passwd }); - -client.on([states.PLAY, 0x40], function(packet) { + +client.on([states.PLAY, 0x40], function(packet) { // you can listen for packets by [state, id], too console.info(color('Kicked for ' + packet.reason, "blink+red")); process.exit(1); }); @@ -95,7 +94,7 @@ client.on('connect', function() { client.on('state', function(newState) { if (newState === states.PLAY) { chats.forEach(function(chat) { - client.write(0x01, {message: chat}); + client.write('chat_message', {message: chat}); }); } }) @@ -118,7 +117,7 @@ rl.on('line', function(line) { } }); -client.on([states.PLAY, 0x02], function(packet) { +client.on('chat', function(packet) { var j = JSON.parse(packet.message); var chat = parseChat(j, {}); console.info(chat); diff --git a/examples/client_echo.js b/examples/client_echo.js index 7f6192b..0ac3f21 100644 --- a/examples/client_echo.js +++ b/examples/client_echo.js @@ -8,12 +8,12 @@ var client = mc.createClient({ client.on('connect', function() { console.info('connected'); }); -client.on([states.PLAY, 0x02], function(packet) { +client.on('chat', function(packet) { var jsonMsg = JSON.parse(packet.message); if (jsonMsg.translate == 'chat.type.announcement' || jsonMsg.translate == 'chat.type.text') { var username = jsonMsg.with[0]; var msg = jsonMsg.with[1]; if (username === client.username) return; - client.write(0x01, {message: msg}); + client.write('chat_message', {message: msg}); } }); \ No newline at end of file diff --git a/examples/server.js b/examples/server.js index 23bcb0f..6ba4ea7 100644 --- a/examples/server.js +++ b/examples/server.js @@ -1,8 +1,6 @@ var mc = require('../'); var states = mc.protocol.states; -var yellow = '§e'; - var options = { motd: 'Vox Industries', 'max-players': 127, @@ -13,17 +11,17 @@ var options = { var server = mc.createServer(options); server.on('login', function(client) { - broadcast(yellow + client.username+' joined the game.'); + broadcast(client.username+' joined the game.'); var addr = client.socket.remoteAddress + ':' + client.socket.remotePort; console.log(client.username+' connected', '('+addr+')'); client.on('end', function() { - broadcast(yellow + client.username+' left the game.', client); + broadcast(client.username+' left the game.', client); console.log(client.username+' disconnected', '('+addr+')'); }); // send init data so client will start rendering world - client.write(0x01, { + client.write('join_game', { entityId: client.id, levelType: 'default', gameMode: 1, @@ -31,7 +29,7 @@ server.on('login', function(client) { difficulty: 2, maxPlayers: server.maxPlayers }); - client.write(0x08, { + client.write('player_position', { x: 0, y: 256, z: 0, @@ -40,7 +38,7 @@ server.on('login', function(client) { onGround: true }); - client.on([states.PLAY, 0x01], function(data) { + client.on('chat_message', function(data) { var message = '<'+client.username+'>' + ' ' + data.message; broadcast(message, client, client.username); console.log(message); @@ -68,10 +66,10 @@ function broadcast(message, exclude, username) { translate: translate, "with": [ username, - 'Hello, world!' + message ] }; - client.write(0x02, { message: JSON.stringify(msg) }); + client.write('chat', { message: JSON.stringify(msg) }); } } } diff --git a/examples/server_helloworld.js b/examples/server_helloworld.js index 42d787d..c2fe899 100644 --- a/examples/server_helloworld.js +++ b/examples/server_helloworld.js @@ -15,7 +15,7 @@ server.on('login', function(client) { }); // send init data so client will start rendering world - client.write(0x01, { + client.write('join_game', { entityId: client.id, levelType: 'default', gameMode: 0, @@ -23,7 +23,7 @@ server.on('login', function(client) { difficulty: 2, maxPlayers: server.maxPlayers }); - client.write(0x08, { + client.write('player_position', { x: 0, y: 1.62, z: 0, @@ -39,7 +39,7 @@ server.on('login', function(client) { 'Hello, world!' ] }; - client.write(0x02, { message: JSON.stringify(msg) }); + client.write('chat', { message: JSON.stringify(msg) }); }); server.on('error', function(error) { diff --git a/lib/client.js b/lib/client.js index b2981b7..58f5bbe 100644 --- a/lib/client.js +++ b/lib/client.js @@ -51,6 +51,10 @@ Client.prototype.setSocket = function(socket) { } packet = parsed.results; incomingBuffer = incomingBuffer.slice(parsed.size); + + var packetName = protocol.packetNames[self.state][self.isServer ? 'toServer' : 'toClient'][packet.id]; + if (packetName !== undefined) self.emit(packetName, packet); + self.emit([self.state, packet.id], packet); self.emit(packet.id, packet); self.emit('packet', packet); diff --git a/lib/protocol.js b/lib/protocol.js index 357aaa6..394f806 100644 --- a/lib/protocol.js +++ b/lib/protocol.js @@ -13,119 +13,123 @@ var states = { } var packets = { - "handshaking": { - "toServer": { - 0x00: [ + handshaking: { + toClient: {}, + toServer: { + handshake: {id: 0x00, fields: [ { name: "protocolVersion", type: "varint" }, { name: "serverHost", type: "string" }, { name: "serverPort", type: "ushort" }, { name: "nextState", type: "varint" } - ] - } - }, - "status": { - "toClient": { - 0x00: [ - { name: "response", type: "string" } - ], - 0x01: [ - { name: "time", type: "long" } - ] + ]} }, - "toServer": { - 0x00: [], - 0x01: [ + }, + + status: { + toClient: { + status_response: {id: 0x00, fields: [ + { name: "response", type: "string" } + ]}, + status_ping: {id: 0x01, fields: [ { name: "time", type: "long" } - ] + ]} + }, + toServer: { + status_request: {id: 0x00, fields: []}, + status_ping: {id: 0x01, fields: [ + { name: "time", type: "long" } + ]} } }, - "login": { - "toClient": { - 0x00: [ + + login: { + toClient: { + login_disconnect: {id: 0x00, fields: [ { name: "reason", type: "string" } - ], - 0x01: [ + ]}, + encryption_request: {id: 0x01, fields: [ { name: "serverId", type: "string" }, { name: "publicKey", type: "byteArray16" }, { name: "verifyToken", type: "byteArray16" } - ], - 0x02: [ + ]}, + login_success: {id: 0x02, fields: [ { name: "uuid", type: "string" }, { name: "username", type: "string" } - ] + ]} }, - "toServer": { - 0x00: [ + toServer: { + login_start: {id: 0x00, fields: [ { name: "username", type: "string" } - ], - 0x01: [ + ]}, + encryption_response:{id: 0x01, fields: [ { name: "sharedSecret", type: "byteArray16" }, { name: "verifyToken", type: "byteArray16" } - ] + ]} } }, - "play": { - "toClient": { - 0x00: [ + + play: { + toClient: { + keep_alive: {id: 0x00, fields: [ { name: "keepAliveId", type: "int" }, - ], - 0x01: [ + ]}, + join_game: {id: 0x01, fields: [ { name: "entityId", type: "int" }, { name: "gameMode", type: "ubyte" }, { name: "dimension", type: "byte" }, { name: "difficulty", type: "ubyte" }, { name: "maxPlayers", type: "ubyte" }, { name: "levelType", type: "string" }, - ], - 0x02: [ + ]}, + chat: {id: 0x02, fields: [ { name: "message", type: "ustring" }, - ], - 0x03: [ + ]}, + time_update: {id: 0x03, fields: [ { name: "age", type: "long" }, { name: "time", type: "long" }, - ], - 0x04: [ + ]}, + entity_equipment: {id: 0x04, fields: [ { name: "entityId", type: "int" }, { name: "slot", type: "short" }, - ], - 0x05: [ + ]}, + spawn_position: {id: 0x05, fields: [ { name: "x", type: "int" }, { name: "y", type: "int" }, { name: "z", type: "int" } - ], - 0x06: [ + ]}, + update_health: {id: 0x06, fields: [ { name: "health", type: "float" }, { name: "food", type: "short" }, { name: "foodSaturation", type: "float" } - ], - 0x07: [ + ]}, + respawn: {id: 0x07, fields: [ { name: "dimension", type: "int" }, { name: "difficulty", type: "ubyte" }, { name: "gamemode", type: "ubyte" }, { name: "levelType", type: "string" } - ], - 0x08: [ + ]}, + player_position: {id: 0x08, fields: [ { name: "x", type: "double" }, { name: "y", type: "double" }, { name: "z", type: "double" }, { name: "yaw", type: "float" }, { name: "pitch", type: "float" }, { name: "onGround", type: "bool" } - ], - 0x09: [ + ]}, + held_item_change: {id: 0x09, fields: [ { name: "slot", type: "byte" } - ], - 0x0A: [ + ]}, + use_bed: {id: 0x0a, fields: [ { name: "entityId", type: "int" }, { name: "x", type: "int" }, { name: "y", type: "ubyte" }, { name: "z", type: "int" } - ], - 0x0B: [ + ]}, + animation: {id: 0x0b, fields: [ { name: "entityId", type: "varint" }, { name: "animation", type: "byte" } - ], - 0x0C: [ + ]}, + spawn_player: {id: 0x0c, fields: [ { name: "entityId", type: "varint" }, { name: "playerUUID", type: "string" }, { name: "playerName", type: "string" }, @@ -136,12 +140,12 @@ var packets = { { name: "pitch", type: "byte" }, { name: "currentItem", type: "short" }, { name: "metadata", type: "entityMetadata" } - ], - 0x0D: [ + ]}, + collect_item: {id: 0x0d, fields: [ { name: "collectedEntityId", type: "int" }, { name: "collectorEntityId", type: "int" } - ], - 0x0E: [ + ]}, + spawn_object: {id: 0x0e, fields: [ { name: "entityId", type: "varint" }, { name: "type", type: "byte" }, { name: "x", type: "int" }, @@ -150,8 +154,8 @@ var packets = { { name: "pitch", type: "byte" }, { name: "yaw", type: "byte" }, { name: "objectData", type: "objectData" } - ], - 0x0F: [ + ]}, + spawn_mob: {id: 0x0f, fields: [ { name: "entityId", type: "varint" }, { name: "type", type: "ubyte" }, { name: "x", type: "int" }, @@ -164,137 +168,137 @@ var packets = { { name: "velocityY", type: "short" }, { name: "velocityZ", type: "short" }, { name: "metadata", type: "entityMetadata" }, - ], - 0x10: [ + ]}, + spawn_painting: {id: 0x10, fields: [ { name: "entityId", type: "varint" }, { name: "title", type: "string" }, { name: "x", type: "int" }, { name: "y", type: "int" }, { name: "z", type: "int" }, { name: "direction", type: "int" } - ], - 0x11: [ + ]}, + spawn_experience_orb: {id: 0x11, fields: [ { name: "entityId", type: "varint" }, { name: "x", type: "int" }, { name: "y", type: "int" }, { name: "z", type: "int" }, { name: "count", type: "short" } - ], - 0x12: [ + ]}, + entity_velocity: {id: 0x12, fields: [ { name: "entityId", type: "int" }, { name: "velocityX", type: "short" }, { name: "velocityY", type: "short" }, { name: "velocityZ", type: "short" } - ], - 0x13: [ + ]}, + destroy_entities: {id: 0x13, fields: [ { name: "entityIds", type: "intArray8" } - ], - 0x14: [ + ]}, + entity: {id: 0x14, fields: [ { name: "entityId", type: "int" } - ], - 0x15: [ + ]}, + entity_relative_move: {id: 0x15, fields: [ { name: "entityId", type: "int" }, { name: "dX", type: "byte" }, { name: "dY", type: "byte" }, { name: "dZ", type: "byte" } - ], - 0x16: [ + ]}, + entity_look: {id: 0x16, fields: [ { name: "entityId", type: "int" }, { name: "yaw", type: "byte" }, { name: "pitch", type: "byte" } - ], - 0x17: [ + ]}, + entity_look_and_relative_move: {id: 0x17, fields: [ { name: "entityId", type: "int" }, { name: "dX", type: "byte" }, { name: "dY", type: "byte" }, { name: "dZ", type: "byte" }, { name: "yaw", type: "byte" }, { name: "pitch", type: "byte" } - ], - 0x18: [ + ]}, + entity_teleport: {id: 0x18, fields: [ { name: "entityId", type: "int" }, { name: "x", type: "int" }, { name: "y", type: "int" }, { name: "z", type: "int" }, { name: "yaw", type: "byte" }, { name: "pitch", type: "byte" } - ], - 0x19: [ + ]}, + entity_head_look: {id: 0x19, fields: [ { name: "entityId", type: "int" }, { name: "headYaw", type: "byte" }, - ], - 0x1A: [ + ]}, + entity_status: {id: 0x1a, fields: [ { name: "entityId", type: "int" }, { name: "entityStatus", type: "byte" } - ], - 0x1B: [ + ]}, + attach_entity: {id: 0x1b, fields: [ { name: "entityId", type: "int" }, { name: "vehicleId", type: "int" }, { name: "leash", type: "bool" } - ], - 0x1C: [ + ]}, + entity_metadata: {id: 0x1c, fields: [ { name: "entityId", type: "int" }, { name: "metadata", type: "entityMetadata" } - ], - 0x1D: [ + ]}, + entity_effect: {id: 0x1d, fields: [ { name: "entityId", type: "int" }, { name: "effectId", type: "byte" }, { name: "amplifier", type: "byte" }, { name: "duration", type: "short" } - ], - 0x1E: [ + ]}, + remove_entity_effect: {id: 0x1e, fields: [ { name: "entityId", type: "int" }, { name: "effectId", type: "byte" } - ], - 0x1F: [ + ]}, + set_experience: {id: 0x1f, fields: [ { name: "experienceBar", type: "float" }, { name: "level", type: "short" }, { name: "totalExperience", type: "short" } - ], - 0x20: [ + ]}, + entity_properties: {id: 0x20, fields: [ { name: "entityId", type: "int" }, { name: "properties", type: "propertyArray" } - ], - 0x21: [ + ]}, + chunk_data: {id: 0x21, fields: [ { name: "x", type: "int" }, { name: "z", type: "int" }, { name: "groundUp", type: "bool" }, { name: "bitMap", type: "ushort" }, { name: "addBitMap", type: "ushort" }, { name: "compressedChunkData", type: "byteArray32" } - ], - 0x22: [ + ]}, + multi_block_change: {id: 0x22, fields: [ { name: "chunkX", type: "int" }, { name: "chunkZ", type: "int" }, { name: "recordCount", type: "short" }, { name: "data", type: "byteArray32" } - ], - 0x23: [ + ]}, + block_change: {id: 0x23, fields: [ { name: "x", type: "int" }, { name: "y", type: "ubyte" }, { name: "z", type: "int" }, { name: "type", type: "varint" }, { name: "metadata", type: "ubyte" } - ], - 0x24: [ + ]}, + block_action: {id: 0x24, fields: [ { name: "x", type: "int" }, { name: "y", type: "short" }, { name: "z", type: "int" }, { name: "byte1", type: "ubyte" }, { name: "byte2", type: "ubyte" }, { name: "blockId", type: "varint" } - ], - 0x25: [ + ]}, + block_break_anim: {id: 0x25, fields: [ { name: "entityId", type: "varint" }, { name: "x", type: "int" }, { name: "y", type: "int" }, { name: "z", type: "int" }, { name: "destroyStage", type: "byte" } - ], - 0x26: [ + ]}, + map_chunk_bulk: {id: 0x26, fields: [ { name: "data", type: "mapChunkBulk" } - ], - 0x27: [ + ]}, + explosion: {id: 0x27, fields: [ { name: "x", type: "float" }, { name: "y", type: "float" }, { name: "z", type: "float" }, @@ -303,24 +307,24 @@ var packets = { { name: "playerMotionX", type: "float" }, { name: "playerMotionY", type: "float" }, { name: "playerMotionZ", type: "float" } - ], - 0x28: [ + ]}, + effect: {id: 0x28, fields: [ { name: "effectId", type: "int" }, { name: "x", type: "int" }, { name: "y", type: "byte" }, { name: "z", type: "int" }, { name: "data", type: "int" }, { name: "global", type: "bool" } - ], - 0x29: [ + ]}, + sound_effect: {id: 0x29, fields: [ { name: "soundName", type: "string" }, { name: "x", type: "int" }, { name: "y", type: "int" }, { name: "z", type: "int" }, { name: "volume", type: "float" }, { name: "pitch", type: "ubyte" } - ], - 0x2A: [ + ]}, + particle: {id: 0x2a, fields: [ { name: "particleName", type: "string" }, { name: "x", type: "float" }, { name: "y", type: "float" }, @@ -330,19 +334,19 @@ var packets = { { name: "offsetZ", type: "float" }, { name: "particleSpeed", type: "float" }, { name: "particles", type: "int" } - ], - 0x2B: [ + ]}, + change_game_state: {id: 0x2b, fields: [ { name: "reason", type: "ubyte" }, { name: "gameMode", type: "float" } - ], - 0x2C: [ + ]}, + spawn_global_entity:{id: 0x2c, fields: [ { name: "entityId", type: "varint" }, { name: "type", type: "byte" }, { name: "x", type: "int" }, { name: "y", type: "int" }, { name: "z", type: "int" } - ], - 0x2D: [ + ]}, + open_window: {id: 0x2d, fields: [ { name: "windowId", type: "ubyte" }, { name: "inventoryType", type: "ubyte" }, { name: "windowTitle", type: "string" }, @@ -351,30 +355,30 @@ var packets = { { name: "entityId", type: "int", condition: function(field_values) { return field_values['inventoryType'] == 11; } } - ], - 0x2E: [ + ]}, + close_window: {id: 0x2e, fields: [ { name: "windowId", type: "ubyte" } - ], - 0x2F: [ + ]}, + set_slot: {id: 0x2f, fields: [ { name: "windowId", type: "ubyte" }, { name: "slot", type: "short" }, { name: "item", type: "slot" } - ], - 0x30: [ + ]}, + window_items: {id: 0x30, fields: [ { name: "windowId", type: "ubyte" }, { name: "items", type: "slotArray" } - ], - 0x31: [ + ]}, + window_property: {id: 0x31, fields: [ { name: "windowId", type: "ubyte" }, { name: "property", type: "short" }, { name: "value", type: "short" } - ], - 0x32: [ + ]}, + confirm_transaction:{id: 0x32, fields: [ { name: "windowId", type: "ubyte" }, { name: "action", type: "short" }, { name: "accepted", type: "bool" } - ], - 0x33: [ + ]}, + update_sign: {id: 0x33, fields: [ { name: "x", type: "int" }, { name: "y", type: "short" }, { name: "z", type: "int" }, @@ -382,45 +386,45 @@ var packets = { { name: "text2", type: "string" }, { name: "text3", type: "string" }, { name: "text4", type: "string" } - ], - 0x34: [ + ]}, + maps: {id: 0x34, fields: [ { name: "itemDamage", type: "varint" }, { name: "data", type: "byteArray16" }, - ], - 0x35: [ + ]}, + update_block_entity:{id: 0x35, fields: [ { name: "x", type: "int" }, { name: "y", type: "short" }, { name: "z", type: "int" }, { name: "action", type: "ubyte" }, { name: "nbtData", type: "byteArray16" } - ], - 0x36: [ + ]}, + sign_editor_open: {id: 0x36, fields: [ { name: "x", type: "int" }, { name: "y", type: "int" }, { name: "z", type: "int" } - ], - 0x37: [ + ]}, + statistics: {id: 0x37, fields: [ { name: "count", type: "statisticArray" } - ], - 0x38: [ + ]}, + player_list_item: {id: 0x38, fields: [ { name: "playerName", type: "string" }, { name: "online", type: "bool" }, { name: "ping", type: "short" } - ], - 0x39: [ + ]}, + player_abilities: {id: 0x39, fields: [ { name: "flags", type: "byte" }, { name: "flyingSpeed", type: "float" }, { name: "walkingSpeed", type: "float" } - ], - 0x3A: [ + ]}, + tab_complete: {id: 0x3a, fields: [ { name: "matches", type: "matchArray" } - ], - 0x3B: [ + ]}, + scoreboard_objective: {id: 0x3b, fields: [ { name: "name", type: "string" }, { name: "displayText", type: "string" }, { name: "action", type: "byte" } - ], - 0x3C: [ + ]}, + update_score: {id: 0x3c, fields: [ { name: "itemName", type: "string" }, { name: "remove", type: "bool" }, { name: "scoreName", type: "string", condition: function(field_values) { @@ -429,12 +433,12 @@ var packets = { { name: "value", type: "int", condition: function(field_values) { return !field_values['remove'] } } - ], - 0x3D: [ + ]}, + display_scoreboard: {id: 0x3d, fields: [ { name: "position", type: "byte" }, { name: "name", type: "string" } - ], - 0x3E: [ + ]}, + teams: {id: 0x3e, fields: [ { name: "team", type: "string" }, { name: "mode", type: "byte" }, { name: "name", type: "string", condition: function(field_values) { @@ -452,42 +456,42 @@ var packets = { { name: "players", type: "stringArray", condition: function(field_values) { return field_values['mode'] == 0 || field_values['mode'] == 3 || field_values['mode'] == 4; } } - ], - 0x3F: [ + ]}, + plugin_message: {id: 0x3f, fields: [ { name: "channel", type: "string" }, { name: "data", type: "byteArray16" } - ], - 0x40: [ + ]}, + disconnect: {id: 0x40, fields: [ { name: "reason", type: "string" } - ] + ]} }, - "toServer": { - 0x00: [ + toServer: { + keep_alive: {id: 0x00, fields: [ { name: "keepAliveId", type: "int" } - ], - 0x01: [ + ]}, + chat_message: {id: 0x01, fields: [ { name: "message", type: "string" } - ], - 0x02: [ + ]}, + use_entity: {id: 0x02, fields: [ { name: "target", type: "int" }, { name: "leftClick", type: "byte" } - ], - 0x03: [ + ]}, + player: {id: 0x03, fields: [ { name: "onGround", type: "bool" } - ], - 0x04: [ + ]}, + player_position: {id: 0x04, fields: [ { name: "x", type: "double" }, { name: "stance", type: "double" }, { name: "y", type: "double" }, { name: "z", type: "double" }, { name: "onGround", type: "bool" } - ], - 0x05: [ + ]}, + player_look: {id: 0x05, fields: [ { name: "yaw", type: "float" }, { name: "pitch", type: "float" }, { name: "onGround", type: "bool" } - ], - 0x06: [ + ]}, + player_position_and_look: {id: 0x06, fields: [ { name: "x", type: "double" }, { name: "stance", type: "double" }, { name: "y", type: "double" }, @@ -495,15 +499,15 @@ var packets = { { name: "yaw", type: "float" }, { name: "pitch", type: "float" }, { name: "onGround", type: "bool" } - ], - 0x07: [ + ]}, + player_digging: {id: 0x07, fields: [ { name: "status", type: "byte" }, { name: "x", type: "int" }, { name: "y", type: "ubyte" }, { name: "z", type: "int" }, { name: "face", type: "byte" } - ], - 0x08: [ + ]}, + player_block_placement: {id: 0x08, fields: [ { name: "x", type: "int" }, { name: "y", type: "ubyte" }, { name: "z", type: "int" }, @@ -512,50 +516,50 @@ var packets = { { name: "cursorX", type: "byte" }, { name: "cursorY", type: "byte" }, { name: "cursorZ", type: "byte" } - ], - 0x09: [ + ]}, + held_item_change: {id: 0x09, fields: [ { name: "slotId", type: "short" } - ], - 0x0A: [ + ]}, + animation: {id: 0x0a, fields: [ { name: "entityId", type: "int" }, { name: "animation", type: "byte" } - ], - 0x0B: [ + ]}, + entity_action: {id: 0x0b, fields: [ { name: "entityId", type: "int" }, { name: "actionId", type: "byte" }, { name: "jumpBoost", type: "int" } - ], - 0x0C: [ + ]}, + steer_vehicle: {id: 0x0c, fields: [ { name: "sideways", type: "float" }, { name: "forward", type: "float" }, { name: "jump", type: "bool" }, { name: "unmount", type: "bool" } - ], - 0x0D: [ + ]}, + close_window: {id: 0x0d, fields: [ { name: "windowId", type: "byte" } - ], - 0x0E: [ + ]}, + click_window: {id: 0x0e, fields: [ { name: "windowId", type: "byte" }, { name: "slot", type: "short" }, { name: "mouseButton", type: "byte" }, { name: "action", type: "short" }, { name: "mode", type: "byte" }, { name: "item", type: "slot" } - ], - 0x0F: [ + ]}, + confirm_transaction: {id: 0x0f, fields: [ { name: "windowId", type: "byte" }, { name: "action", type: "short" }, { name: "accepted", type: "bool" } - ], - 0x10: [ + ]}, + creative_inventory_action: {id: 0x10, fields: [ { name: "slot", type: "short" }, { name: "item", type: "slot" } - ], - 0x11: [ + ]}, + enchant_item: {id: 0x11, fields: [ { name: "windowId", type: "byte" }, { name: "enchantment", type: "byte" } - ], - 0x12: [ + ]}, + update_sign: {id: 0x12, fields: [ { name: "x", type: "int" }, { name: "y", type: "short" }, { name: "z", type: "int" }, @@ -563,34 +567,70 @@ var packets = { { name: "text2", type: "string" }, { name: "text3", type: "string" }, { name: "text4", type: "string" } - ], - 0x13: [ + ]}, + player_abilities: {id: 0x13, fields: [ { name: "flags", type: "byte" }, { name: "flyingSpeed", type: "float" }, { name: "walkingSpeed", type: "float" } - ], - 0x14: [ + ]}, + tab_complete: {id: 0x14, fields: [ { name: "text", type: "string" } - ], - 0x15: [ + ]}, + client_settings: {id: 0x15, fields: [ { name: "locale", type: "string" }, { name: "viewDistance", type: "byte" }, { name: "chatFlags", type: "byte" }, { name: "chatColors", type: "bool" }, { name: "difficulty", type: "byte" }, { name: "showCape", type: "bool" } - ], - 0x16: [ + ]}, + client_status: {id: 0x16, fields: [ { name: "payload", type: "byte" } - ], - 0x17: [ + ]}, + plugin_message: {id: 0x17, fields: [ { name: "channel", type: "string" }, { name: "data", type: "byteArray16" } - ], + ]} } } }; +var packetFields = {}; +var packetNames = {}; +var packetIds = {}; +var packetStates = {toClient: {}, toServer: {}}; +(function() { + for (var stateName in states) { + var state = states[stateName]; + + packetFields[state] = {toClient: [], toServer: []}; + packetNames[state] = {toClient: [], toServer: []}; + packetIds[state] = {toClient: [], toServer: []}; + + ['toClient', 'toServer'].forEach(function(direction) { + for (var name in packets[state][direction]) { + var info = packets[state][direction][name]; + var id = info.id; + var fields = info.fields; + + assert(id !== undefined, 'missing id for packet '+name); + assert(fields !== undefined, 'missing fields for packet '+name); + assert(packetNames[state][direction][id] === undefined, 'duplicate packet id '+id+' for '+name); + assert(packetIds[state][direction][name] === undefined, 'duplicate packet name '+name+' for '+id); + assert(packetFields[state][direction][id] === undefined, 'duplicate packet id '+id+' for '+name); + assert(packetStates[direction][name] === undefined, 'duplicate packet name '+name+' for '+id+', must be unique across all states'); + + packetNames[state][direction][id] = name; + packetIds[state][direction][name] = id; + packetFields[state][direction][id] = fields; + packetStates[direction][name] = state; + } + }); + } +})(); + + + var types = { 'int': [readInt, writeInt, 4], 'short': [readShort, writeShort, 2], @@ -1557,7 +1597,7 @@ function writeMatchArray(value, buffer, offset) { function get(packetId, state, toServer) { var direction = toServer ? "toServer" : "toClient"; - var packetInfo = packets[state][direction][packetId]; + var packetInfo = packetFields[state][direction][packetId]; if (!packetInfo) { return null; } @@ -1577,6 +1617,14 @@ function sizeOf(type, value) { function createPacketBuffer(packetId, state, params, isServer) { var length = 0; + if (typeof packetId === 'string' && typeof state !== 'string' && !params) { + // simplified two-argument usage, createPacketBuffer(name, params) + params = state; + state = packetStates[!isServer ? 'toServer' : 'toClient'][packetId]; + } + if (typeof packetId === 'string') packetId = packetIds[state][!isServer ? 'toServer' : 'toClient'][packetId]; + assert.notEqual(packetId, undefined); + var packet = get(packetId, state, !isServer); assert.notEqual(packet, null); packet.forEach(function(fieldInfo) { @@ -1617,6 +1665,7 @@ function parsePacket(buffer, state, isServer) { return readResults; } + if (state == null) state == states.PLAY; var cursor = 0; var lengthField = readVarInt(buffer, 0); if (!!!lengthField) return null; @@ -1680,7 +1729,9 @@ module.exports = { parsePacket: parsePacket, createPacketBuffer: createPacketBuffer, STRING_MAX_LENGTH: STRING_MAX_LENGTH, - packets: packets, + packetIds: packetIds, + packetNames: packetNames, + packetFields: packetFields, states: states, get: get, debug: debug, diff --git a/test/test.js b/test/test.js index 95c2c1d..ff74845 100644 --- a/test/test.js +++ b/test/test.js @@ -136,17 +136,17 @@ describe("packets", function() { client.end(); }); var packetId, packetInfo, field; - for(state in protocol.packets) { - if (!protocol.packets.hasOwnProperty(state)) continue; - for(packetId in protocol.packets[state].toServer) { - if (!protocol.packets[state].toServer.hasOwnProperty(packetId)) continue; + for(state in protocol.packetFields) { + if (!protocol.packetFields.hasOwnProperty(state)) continue; + for(packetId in protocol.packetFields[state].toServer) { + if (!protocol.packetFields[state].toServer.hasOwnProperty(packetId)) continue; packetId = parseInt(packetId, 10); packetInfo = protocol.get(packetId, state, true); it(state + ",ServerBound,0x" + zfill(parseInt(packetId, 10).toString(16), 2), callTestPacket(packetId, packetInfo, state, true)); } - for(packetId in protocol.packets[state].toClient) { - if (!protocol.packets[state].toClient.hasOwnProperty(packetId)) continue; + for(packetId in protocol.packetFields[state].toClient) { + if (!protocol.packetFields[state].toClient.hasOwnProperty(packetId)) continue; packetId = parseInt(packetId, 10); packetInfo = protocol.get(packetId, state, false); it(state + ",ClientBound,0x" + zfill(parseInt(packetId, 10).toString(16), 2),