diff --git a/lib/plugins/bed.js b/lib/plugins/bed.js index 9b526f698..4e7287e99 100644 --- a/lib/plugins/bed.js +++ b/lib/plugins/bed.js @@ -1,3 +1,4 @@ +const enumValue = require('../protocol_enum') const { Vec3 } = require('vec3') module.exports = inject @@ -70,7 +71,7 @@ function inject (bot) { } else { bot._client.write('entity_action', { entityId: bot.entity.id, - actionId: 2, + actionId: enumValue(bot, 'entity_action', 'actionId', 'leave_bed', 2), jumpBoost: 0 }) } diff --git a/lib/plugins/creative.js b/lib/plugins/creative.js index 2b894e4c6..d416a9562 100644 --- a/lib/plugins/creative.js +++ b/lib/plugins/creative.js @@ -2,6 +2,7 @@ const assert = require('assert') const { Vec3 } = require('vec3') const { sleep, onceWithCleanup } = require('../promise_utils') const { once } = require('../promise_utils') +const enumValue = require('../protocol_enum') module.exports = inject @@ -46,7 +47,9 @@ function inject (bot) { resolve() }, timeoutMs) pendingStatsRequests.push(request) - bot._client.write('client_command', bot.supportFeature('respawnIsPayload') ? { payload: 1 } : { actionId: 1 }) + bot._client.write('client_command', bot.supportFeature('respawnIsPayload') + ? { payload: 1 } + : { actionId: enumValue(bot, 'client_command', 'actionId', 'request_stats', 1) }) }) } diff --git a/lib/plugins/game.js b/lib/plugins/game.js index d3291b06e..92a3d366a 100644 --- a/lib/plugins/game.js +++ b/lib/plugins/game.js @@ -1,3 +1,4 @@ +const enumValue = require('../protocol_enum') const nbt = require('prismarine-nbt') module.exports = inject @@ -123,7 +124,9 @@ function inject (bot, options) { bot._client.on('game_state_change', (packet) => { if ((packet.reason === 4 || packet.reason === 'win_game') && packet.gameMode === 1) { - bot._client.write('client_command', { action: 0 }) + bot._client.write('client_command', bot.supportFeature('respawnIsPayload') + ? { payload: 0 } + : { actionId: enumValue(bot, 'client_command', 'actionId', 'perform_respawn', 0) }) } if ((packet.reason === 3) || (packet.reason === 'change_game_mode')) { bot.game.gameMode = parseGameMode(packet.gameMode) diff --git a/lib/plugins/health.js b/lib/plugins/health.js index 94ff32590..89687805b 100644 --- a/lib/plugins/health.js +++ b/lib/plugins/health.js @@ -1,3 +1,4 @@ +const enumValue = require('../protocol_enum') module.exports = inject function inject (bot, options) { @@ -41,7 +42,9 @@ function inject (bot, options) { const respawn = () => { if (bot.isAlive) return - bot._client.write('client_command', bot.supportFeature('respawnIsPayload') ? { payload: 0 } : { actionId: 0 }) + bot._client.write('client_command', bot.supportFeature('respawnIsPayload') + ? { payload: 0 } + : { actionId: enumValue(bot, 'client_command', 'actionId', 'perform_respawn', 0) }) } bot.respawn = respawn diff --git a/lib/protocol_enum.js b/lib/protocol_enum.js new file mode 100644 index 000000000..4cf25cadf --- /dev/null +++ b/lib/protocol_enum.js @@ -0,0 +1,8 @@ +// Some enum fields are a string mapper in newer protocols and a plain id in older ones +// (entity_action.actionId from 1.21.6, client_command.actionId from 26.1). protodef only +// writes the form the loaded version declares. +module.exports = function enumValue (bot, packetName, fieldName, name, id) { + const field = bot.registry.protocol.play.toServer.types[`packet_${packetName}`][1] + .find(f => f.name === fieldName) + return Array.isArray(field?.type) && field.type[0] === 'mapper' ? name : id +} diff --git a/test/internalTest.js b/test/internalTest.js index 068368f4d..d66fbba85 100644 --- a/test/internalTest.js +++ b/test/internalTest.js @@ -592,6 +592,56 @@ for (const supportedVersion of mineflayer.testedVersions) { }) describe('game', () => { + it('respawns with the action id or name this version declares', (done) => { + server.on('playerJoin', async (client) => { + await bot.test.pluginsLoaded + const loggedIn = once(bot, 'login') + await client.write('login', bot.test.generateLoginPacket()) + await loggedIn + const writes = [] + // Every write must match this version's packet shape. + bot._client.write = (name, params) => { + bot._client.serializer.createPacketBuffer({ name, params }) + if (name === 'client_command') writes.push(params) + } + bot.isAlive = false + bot.respawn() + try { + const field = registry.protocol.play.toServer.types.packet_client_command[1][0] + const usesNames = Array.isArray(field.type) && field.type[0] === 'mapper' + assert.deepStrictEqual(writes, [{ [field.name]: usesNames ? 'perform_respawn' : 0 }]) + done() + } catch (err) { + done(err) + } + }) + }) + + it('wakes with the action id or name this version declares', (done) => { + server.on('playerJoin', async (client) => { + await bot.test.pluginsLoaded + const loggedIn = once(bot, 'login') + await client.write('login', bot.test.generateLoginPacket()) + await loggedIn + const writes = [] + bot._client.write = (name, params) => { + bot._client.serializer.createPacketBuffer({ name, params }) + if (name === 'entity_action') writes.push(params.actionId) + } + bot.isSleeping = true + bot.wake().then(() => { + try { + const field = registry.protocol.play.toServer.types.packet_entity_action[1][1] + const usesNames = Array.isArray(field.type) && field.type[0] === 'mapper' + assert.deepStrictEqual(writes, [usesNames ? 'leave_bed' : 2]) + done() + } catch (err) { + done(err) + } + }, done) + }) + }) + it('responds to ping / transaction packets', (done) => { // only on 1.17 server.on('playerJoin', async (client) => { if (bot.supportFeature('transactionPacketExists')) {