Skip to content

Minecraft 1.20.2 support #3262

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jan 14, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions lib/plugins/blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,24 @@ 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
let weightedAverage = 2 // The Vanilla client uses nano seconds with its weighted average starting at 2000000 converted to milliseconds that is 2
let oldSampleWeight = 1 // This is used for keeping track of the weight of the old average when updating it.

bot._client.on('chunk_batch_start', (packet) => {
chunkBatchStartTime = Date.now() // Get the time the chunk batch is starting.
})

bot._client.on('chunk_batch_finished', (packet) => {
const milliPerChunk = (Date.now() - chunkBatchStartTime) / packet.batchSize // Gets millisecond per chunk
const clampedMilliPerChunk = Math.min(Math.max(milliPerChunk, weightedAverage / 3.0), weightedAverage * 3.0) // Prevents the MilliPerChunk from being hugely different then the average, Vanilla uses 3 as a constant here.
weightedAverage = ((weightedAverage * oldSampleWeight) + clampedMilliPerChunk) / (oldSampleWeight + 1)
oldSampleWeight = Math.min(49, oldSampleWeight + 1) // 49 is used in Vanilla client to limit it to 50 samples
bot._client.write('chunk_batch_received', {
chunksPerTick: 7 / weightedAverage // 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.
})
})
bot._client.on('map_chunk', (packet) => {
addColumn({
x: packet.x,
Expand Down
96 changes: 51 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,56 @@ 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)
}

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
69 changes: 43 additions & 26 deletions test/internalTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' })
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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)
Expand All @@ -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) => {
Expand All @@ -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()

Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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) => {
Expand All @@ -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
}
Expand All @@ -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())
Expand Down Expand Up @@ -632,17 +632,34 @@ 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.version['>=']('1.20.2')) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be good to make that a feature

Copy link
Member

@extremeheat extremeheat Jan 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, looks like removing dupe PrismarineJS/minecraft-data@587a134 caused range to be incorrect breaking chunk lighting, just fixed that

client.write('spawn_entity', {
entityId: 56,
objectUUID: '1-2-3-4',
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: []
})
}
})
})

Expand All @@ -663,7 +680,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')) {
Expand Down Expand Up @@ -712,7 +729,7 @@ for (const supportedVersion of mineflayer.testedVersions) {
})

it('metadata', (done) => {
server.on('login', (client) => {
server.on('playerJoin', (client) => {
bot.on('entitySpawn', (entity) => {
assert.strictEqual(entity.displayName, 'Creeper')

Expand Down Expand Up @@ -763,7 +780,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

Expand Down Expand Up @@ -870,7 +887,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)
Expand Down Expand Up @@ -952,7 +969,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' }] })
Expand Down