Skip to content

Commit

Permalink
Minecraft 1.20.2 support (#3262)
Browse files Browse the repository at this point in the history
* Implement configuration.registry_data packet handling

* Fix player entities in >= 1.20.2

* add 1.20.2 in tested versions

* Add Chunk Batch support (#3263)

* Add Chunk Batch support

* Fix linting errors.

* I really hate the linter right now.

* Comment what chunkbatches are for.

* Update chunk batch calculation to use Vanilla's calc but updated for Milliseconds vs Nanoseconds

* Fix usage of variables starting with capital letters

* Update package.json dump mcdata

* server on `login` -> `playerJoin`

* try CI on node20

* node20 on mc test

* revert CI to node18

* update entity handling

* fix typo

* update internal tests

* update internalTest to use feature

* Update README.md

* update pathfinder dep in example and add npmrc in examples

---------

Co-authored-by: Crux <[email protected]>
Co-authored-by: William Gaylord <[email protected]>
Co-authored-by: extremeheat <[email protected]>
  • Loading branch information
4 people authored Jan 14, 2024
1 parent 5c71edf commit 2ff9919
Show file tree
Hide file tree
Showing 13 changed files with 170 additions and 86 deletions.
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions examples/anvil_saver/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
engine-strict=true
package-lock=false
2 changes: 2 additions & 0 deletions examples/pathfinder/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
engine-strict=true
package-lock=false
2 changes: 1 addition & 1 deletion examples/pathfinder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions examples/place_end_crystal/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
engine-strict=true
package-lock=false
2 changes: 2 additions & 0 deletions examples/screenshot-with-node-canvas-webgl/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
engine-strict=true
package-lock=false
2 changes: 2 additions & 0 deletions examples/viewer/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
engine-strict=true
package-lock=false
24 changes: 24 additions & 0 deletions lib/plugins/blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
97 changes: 52 additions & 45 deletions lib/plugins/entities.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
})
Expand All @@ -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
Expand All @@ -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)
})

Expand Down
5 changes: 5 additions & 0 deletions lib/plugins/game.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
4 changes: 3 additions & 1 deletion lib/version.js
Original file line number Diff line number Diff line change
@@ -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]

}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading

0 comments on commit 2ff9919

Please sign in to comment.