diff --git a/docs/README.md b/docs/README.md index 9160eba96..d321c684f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -17,7 +17,7 @@ First time using Node.js? You may want to start with the [tutorial](tutorial.md) ## Features - * Supports Minecraft 1.8 to 1.20.1 (1.8, 1.9, 1.10, 1.11, 1.12, 1.13, 1.14, 1.15, 1.16, 1.17, 1.18, 1.19 and 1.20) + * Supports Minecraft 1.8 to 1.20.2 (1.8, 1.9, 1.10, 1.11, 1.12, 1.13, 1.14, 1.15, 1.16, 1.17, 1.18, 1.19 and 1.20) * Entity knowledge and tracking. * Block knowledge. You can query the world around you. Milliseconds to find any block. * Physics and movement - handle all bounding boxes diff --git a/examples/anvil_saver/.npmrc b/examples/anvil_saver/.npmrc new file mode 100644 index 000000000..4c1bf7793 --- /dev/null +++ b/examples/anvil_saver/.npmrc @@ -0,0 +1,2 @@ +engine-strict=true +package-lock=false diff --git a/examples/pathfinder/.npmrc b/examples/pathfinder/.npmrc new file mode 100644 index 000000000..4c1bf7793 --- /dev/null +++ b/examples/pathfinder/.npmrc @@ -0,0 +1,2 @@ +engine-strict=true +package-lock=false diff --git a/examples/pathfinder/package.json b/examples/pathfinder/package.json index e94536838..a0a435fe9 100644 --- a/examples/pathfinder/package.json +++ b/examples/pathfinder/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "private": true, "dependencies": { - "mineflayer-pathfinder": "^1.6.1", + "mineflayer-pathfinder": "^2.4.5", "mineflayer": "file:../../" }, "description": "A mineflayer example" diff --git a/examples/place_end_crystal/.npmrc b/examples/place_end_crystal/.npmrc new file mode 100644 index 000000000..4c1bf7793 --- /dev/null +++ b/examples/place_end_crystal/.npmrc @@ -0,0 +1,2 @@ +engine-strict=true +package-lock=false diff --git a/examples/screenshot-with-node-canvas-webgl/.npmrc b/examples/screenshot-with-node-canvas-webgl/.npmrc new file mode 100644 index 000000000..4c1bf7793 --- /dev/null +++ b/examples/screenshot-with-node-canvas-webgl/.npmrc @@ -0,0 +1,2 @@ +engine-strict=true +package-lock=false diff --git a/examples/viewer/.npmrc b/examples/viewer/.npmrc new file mode 100644 index 000000000..4c1bf7793 --- /dev/null +++ b/examples/viewer/.npmrc @@ -0,0 +1,2 @@ +engine-strict=true +package-lock=false diff --git a/lib/plugins/blocks.js b/lib/plugins/blocks.js index 0a8f5fc13..d8c0b41d0 100644 --- a/lib/plugins/blocks.js +++ b/lib/plugins/blocks.js @@ -268,6 +268,30 @@ function inject (bot, { version, storageBuilder, hideErrors }) { } }) + // Chunk batches are used by the server to throttle the chunks per tick for players based on their connection speed. + let chunkBatchStartTime = 0 + // The Vanilla client uses nano seconds with its weighted average starting at 2000000 converted to milliseconds that is 2 + let weightedAverage = 2 + // This is used for keeping track of the weight of the old average when updating it. + let oldSampleWeight = 1 + + bot._client.on('chunk_batch_start', (packet) => { + // Get the time the chunk batch is starting. + chunkBatchStartTime = Date.now() + }) + + bot._client.on('chunk_batch_finished', (packet) => { + const milliPerChunk = (Date.now() - chunkBatchStartTime) / packet.batchSize + // Prevents the MilliPerChunk from being hugely different then the average, Vanilla uses 3 as a constant here. + const clampedMilliPerChunk = Math.min(Math.max(milliPerChunk, weightedAverage / 3.0), weightedAverage * 3.0) + weightedAverage = ((weightedAverage * oldSampleWeight) + clampedMilliPerChunk) / (oldSampleWeight + 1) + // 49 is used in Vanilla client to limit it to 50 samples + oldSampleWeight = Math.min(49, oldSampleWeight + 1) + bot._client.write('chunk_batch_received', { + // Vanilla uses 7000000 as a constant here, since we are using milliseconds that is now 7. Not sure why they pick this constant to convert from nano seconds per chunk to chunks per tick. + chunksPerTick: 7 / weightedAverage + }) + }) bot._client.on('map_chunk', (packet) => { addColumn({ x: packet.x, diff --git a/lib/plugins/entities.js b/lib/plugins/entities.js index 8ee6f6742..135219521 100644 --- a/lib/plugins/entities.js +++ b/lib/plugins/entities.js @@ -26,7 +26,7 @@ const entityStatusEvents = { } function inject (bot) { - const { mobs, entitiesArray } = bot.registry + const { mobs } = bot.registry const Entity = require('prismarine-entity')(bot.version) const Item = require('prismarine-item')(bot.version) const ChatMessage = require('prismarine-chat')(bot.registry) @@ -129,33 +129,6 @@ function inject (bot) { if (eventName) bot.emit(eventName, entity) }) - bot._client.on('named_entity_spawn', (packet) => { - // in case player_info packet was not sent before named_entity_spawn : ignore named_entity_spawn (see #213) - if (packet.playerUUID in bot.uuidToUsername) { - // spawn named entity - const entity = fetchEntity(packet.entityId) - entity.type = 'player' - entity.name = 'player' - entity.username = bot.uuidToUsername[packet.playerUUID] - entity.uuid = packet.playerUUID - entity.dataBlobs = packet.data - if (bot.supportFeature('fixedPointPosition')) { - entity.position.set(packet.x / 32, packet.y / 32, packet.z / 32) - } else if (bot.supportFeature('doublePosition')) { - entity.position.set(packet.x, packet.y, packet.z) - } - entity.yaw = conv.fromNotchianYawByte(packet.yaw) - entity.pitch = conv.fromNotchianPitchByte(packet.pitch) - entity.height = NAMED_ENTITY_HEIGHT - entity.width = NAMED_ENTITY_WIDTH - entity.metadata = parseMetadata(packet.metadata, entity.metadata) - if (bot.players[entity.username] !== undefined && !bot.players[entity.username].entity) { - bot.players[entity.username].entity = entity - } - bot.emit('entitySpawn', entity) - } - }) - bot.on('entityCrouch', (entity) => { entity.height = CROUCH_HEIGHT }) @@ -171,10 +144,11 @@ function inject (bot) { bot.emit('playerCollect', collector, collected) }) + // What is internalId? + const entityDataByInternalId = Object.fromEntries(bot.registry.entitiesArray.map((e) => [e.internalId, e])) + function setEntityData (entity, type, entityData) { - if (entityData === undefined) { - entityData = entitiesArray.find(entity => entity.internalId === type) - } + entityData ??= entityDataByInternalId[type] if (entityData) { entity.type = entityData.type || 'object' entity.displayName = entityData.displayName @@ -193,24 +167,57 @@ function inject (bot) { } } - // spawn object/vehicle on versions < 1.19, on versions > 1.19 handles all non-player entities - bot._client.on('spawn_entity', (packet) => { - const entity = fetchEntity(packet.entityId) - const entityData = bot.registry.entities[packet.type] - setEntityData(entity, packet.type, entityData) - + function updateEntityPos (entity, pos) { if (bot.supportFeature('fixedPointPosition')) { - entity.position.set(packet.x / 32, packet.y / 32, packet.z / 32) + entity.position.set(pos.x / 32, pos.y / 32, pos.z / 32) } else if (bot.supportFeature('doublePosition')) { - entity.position.set(packet.x, packet.y, packet.z) - } else if (bot.supportFeature('consolidatedEntitySpawnPacket')) { - entity.headPitch = conv.fromNotchianPitchByte(packet.headPitch) + entity.position.set(pos.x, pos.y, pos.z) } + entity.yaw = conv.fromNotchianYawByte(pos.yaw) + entity.pitch = conv.fromNotchianPitchByte(pos.pitch) + } - entity.uuid = packet.objectUUID - entity.yaw = conv.fromNotchianYawByte(packet.yaw) - entity.pitch = conv.fromNotchianPitchByte(packet.pitch) - entity.objectData = packet.objectData + function addNewPlayer (entityId, uuid, pos) { + const entity = fetchEntity(entityId) + entity.type = 'player' + entity.name = 'player' + entity.username = bot.uuidToUsername[uuid] + entity.uuid = uuid + updateEntityPos(entity, pos) + entity.height = NAMED_ENTITY_HEIGHT + entity.width = NAMED_ENTITY_WIDTH + if (bot.players[entity.username] !== undefined && !bot.players[entity.username].entity) { + bot.players[entity.username].entity = entity + } + return entity + } + + function addNewNonPlayer (entityId, uuid, entityType, pos) { + const entity = fetchEntity(entityId) + const entityData = bot.registry.entities[entityType] + setEntityData(entity, entityType, entityData) + updateEntityPos(entity, pos) + return entity + } + + bot._client.on('named_entity_spawn', (packet) => { + // in case player_info packet was not sent before named_entity_spawn : ignore named_entity_spawn (see #213) + if (packet.playerUUID in bot.uuidToUsername) { + // spawn named entity + const entity = addNewPlayer(packet.entityId, packet.playerUUID, packet, packet.metadata) + entity.dataBlobs = packet.data // this field doesn't appear to be listed on any version + entity.metadata = parseMetadata(packet.metadata, entity.metadata) // 1.8 + bot.emit('entitySpawn', entity) + } + }) + + // spawn object/vehicle on versions < 1.19, on versions > 1.19 handles all non-player entities + // on versions >= 1.20.2, this also handles player entities + bot._client.on('spawn_entity', (packet) => { + const entityData = entityDataByInternalId[packet.type] + const entity = entityData?.type === 'player' + ? addNewPlayer(packet.entityId, packet.objectUUID, packet) + : addNewNonPlayer(packet.entityId, packet.objectUUID, packet.type, packet) bot.emit('entitySpawn', entity) }) diff --git a/lib/plugins/game.js b/lib/plugins/game.js index 218e2f5ca..4006d145f 100644 --- a/lib/plugins/game.js +++ b/lib/plugins/game.js @@ -71,6 +71,11 @@ function inject (bot, options) { const brandChannel = getBrandCustomChannelName() bot._client.registerChannel(brandChannel, ['string', []]) + // 1.20.2 + bot._client.on('registry_data', (packet) => { + bot.registry.loadDimensionCodec(packet.codec) + }) + bot._client.on('login', (packet) => { handleRespawnPacketData(packet) diff --git a/lib/version.js b/lib/version.js index 6d056eb96..059ef3dea 100644 --- a/lib/version.js +++ b/lib/version.js @@ -1,6 +1,8 @@ -const testedVersions = ['1.8.8', '1.9.4', '1.10.2', '1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.5', '1.17.1', '1.18.2', '1.19', '1.19.2', '1.19.3', '1.19.4', '1.20.1'] +const testedVersions = ['1.8.8', '1.9.4', '1.10.2', '1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.5', '1.17.1', '1.18.2', '1.19', '1.19.2', '1.19.3', '1.19.4', '1.20.1', '1.20.2'] module.exports = { + testedVersions, latestSupportedVersion: testedVersions[testedVersions.length - 1], oldestSupportedVersion: testedVersions[0] + } diff --git a/package.json b/package.json index f148771ab..b8eb0618f 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ }, "license": "MIT", "dependencies": { - "minecraft-data": "^3.44.0", + "minecraft-data": "^3.56.0", "minecraft-protocol": "^1.44.0", "prismarine-biome": "^1.1.1", "prismarine-block": "^1.17.0", diff --git a/test/internalTest.js b/test/internalTest.js index 913b8e35b..25b44e774 100644 --- a/test/internalTest.js +++ b/test/internalTest.js @@ -104,7 +104,7 @@ for (const supportedVersion of mineflayer.testedVersions) { assert.strictEqual(message, 'hello') bot.chat('hi') }) - server.on('login', (client) => { + server.on('playerJoin', (client) => { client.write('login', bot.test.generateLoginPacket()) const message = hasSignedChat ? JSON.stringify({ text: 'hello' }) @@ -180,7 +180,7 @@ for (const supportedVersion of mineflayer.testedVersions) { // Versions prior to 1.11 have capital first letter const entities = bot.registry.entitiesByName const creeperId = entities.creeper ? entities.creeper.id : entities.Creeper.id - server.on('login', (client) => { + server.on('playerJoin', (client) => { client.write(bot.registry.supportFeature('consolidatedEntitySpawnPacket') ? 'spawn_entity' : 'spawn_entity_living', { entityId: 8, // random entityUUID: '00112233-4455-6677-8899-aabbccddeeff', @@ -215,7 +215,7 @@ for (const supportedVersion of mineflayer.testedVersions) { assert.strictEqual(bot.blockAt(pos).type, goldId) done() }) - server.on('login', (client) => { + server.on('playerJoin', (client) => { client.write('login', bot.test.generateLoginPacket()) const chunk = bot.test.buildChunk() chunk.setBlockType(pos, goldId) @@ -238,7 +238,7 @@ for (const supportedVersion of mineflayer.testedVersions) { flags: 0, teleportId: 0 } - server.on('login', async (client) => { + server.on('playerJoin', async (client) => { await client.write('login', bot.test.generateLoginPacket()) await client.write('position', basePosition) client.on('packet', (data, meta) => { @@ -261,7 +261,7 @@ for (const supportedVersion of mineflayer.testedVersions) { }) }) it('absolute position & relative position (velocity)', (done) => { - server.on('login', async (client) => { + server.on('playerJoin', async (client) => { await client.write('login', bot.test.generateLoginPacket()) const chunk = await bot.test.buildChunk() @@ -383,7 +383,7 @@ for (const supportedVersion of mineflayer.testedVersions) { assert.strictEqual(bot.entity.onGround, false) } }) - server.on('login', (client) => { + server.on('playerJoin', (client) => { client.write('login', bot.test.generateLoginPacket()) const chunk = bot.test.buildChunk() @@ -451,7 +451,7 @@ for (const supportedVersion of mineflayer.testedVersions) { flags: 0, teleportId: 0 } - server.on('login', async (client) => { + server.on('playerJoin', async (client) => { bot.once('respawn', () => { assert.ok(bot.world.getColumn(0, 0) !== undefined) bot.once('respawn', () => { @@ -482,7 +482,7 @@ for (const supportedVersion of mineflayer.testedVersions) { describe('game', () => { it('responds to ping / transaction packets', (done) => { // only on 1.17 - server.on('login', async (client) => { + server.on('playerJoin', async (client) => { if (bot.supportFeature('transactionPacketExists')) { const transactionPacket = { windowId: 0, action: 42, accepted: false } client.once('transaction', (data, meta) => { @@ -506,7 +506,7 @@ for (const supportedVersion of mineflayer.testedVersions) { describe('entities', () => { it('entity id changes on login', (done) => { const loginPacket = bot.test.generateLoginPacket() - server.on('login', (client) => { + server.on('playerJoin', (client) => { if (bot.supportFeature('usesLoginPacket')) { loginPacket.entityId = 0 // Default login packet in minecraft-data 1.16.5 is 1, so set it to 0 } @@ -524,7 +524,7 @@ for (const supportedVersion of mineflayer.testedVersions) { }) it('player displayName', (done) => { - server.on('login', (client) => { + server.on('playerJoin', (client) => { bot.on('entitySpawn', (entity) => { const player = bot.players[entity.username] assert.strictEqual(entity.username, player.displayName.toString()) @@ -632,17 +632,35 @@ for (const supportedVersion of mineflayer.testedVersions) { }) } - client.write('named_entity_spawn', { - entityId: 56, - playerUUID: '1-2-3-4', - x: 1, - y: 2, - z: 3, - yaw: 0, - pitch: 0, - currentItem: -1, - metadata: [] - }) + if (bot.registry.supportFeature('unifiedPlayerAndEntitySpawnPacket')) { + client.write('spawn_entity', { + entityId: 56, + objectUUID: '1-2-3-4', + type: bot.registry.entitiesByName.player.internalId, + x: 1, + y: 2, + z: 3, + pitch: 0, + yaw: 0, + headPitch: 0, + objectData: 1, + velocityX: 0, + velocityY: 0, + velocityZ: 0 + }) + } else { + client.write('named_entity_spawn', { + entityId: 56, + playerUUID: '1-2-3-4', + x: 1, + y: 2, + z: 3, + yaw: 0, + pitch: 0, + currentItem: -1, + metadata: [] + }) + } }) }) @@ -663,7 +681,7 @@ for (const supportedVersion of mineflayer.testedVersions) { assert.strictEqual(bot.players[entity.username], undefined) done() }) - server.on('login', (client) => { + server.on('playerJoin', (client) => { serverClient = client if (registry.supportFeature('playerInfoActionIsBitfield')) { @@ -697,22 +715,40 @@ for (const supportedVersion of mineflayer.testedVersions) { }) } - client.write('named_entity_spawn', { - entityId: 56, - playerUUID: '1-2-3-4', - x: 1, - y: 2, - z: 3, - yaw: 0, - pitch: 0, - currentItem: -1, - metadata: [] - }) + if (bot.registry.supportFeature('unifiedPlayerAndEntitySpawnPacket')) { + client.write('spawn_entity', { + entityId: 56, + objectUUID: '1-2-3-4', + type: bot.registry.entitiesByName.player.internalId, + x: 1, + y: 2, + z: 3, + pitch: 0, + yaw: 0, + headPitch: 0, + objectData: 1, + velocityX: 0, + velocityY: 0, + velocityZ: 0 + }) + } else { + client.write('named_entity_spawn', { + entityId: 56, + playerUUID: '1-2-3-4', + x: 1, + y: 2, + z: 3, + yaw: 0, + pitch: 0, + currentItem: -1, + metadata: [] + }) + } }) }) it('metadata', (done) => { - server.on('login', (client) => { + server.on('playerJoin', (client) => { bot.on('entitySpawn', (entity) => { assert.strictEqual(entity.displayName, 'Creeper') @@ -763,7 +799,7 @@ for (const supportedVersion of mineflayer.testedVersions) { itemCount: 5 } - server.on('login', (client) => { + server.on('playerJoin', (client) => { bot.on('itemDrop', (entity) => { const slotPosition = metadataPacket.metadata[0].key @@ -870,7 +906,7 @@ for (const supportedVersion of mineflayer.testedVersions) { done() }) - server.once('login', (client) => { + server.once('playerJoin', (client) => { bot.time.timeOfDay = 18000 const loginPacket = bot.test.generateLoginPacket() client.write('login', loginPacket) @@ -952,7 +988,7 @@ for (const supportedVersion of mineflayer.testedVersions) { }) }) - server.on('login', (client) => { + server.on('playerJoin', (client) => { client.write('playerlist_header', { header: JSON.stringify({ text: '', extra: [{ text: HEADER, color: 'yellow' }] }), footer: JSON.stringify({ text: '', extra: [{ text: FOOTER, color: 'yellow' }] })