Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
56 changes: 56 additions & 0 deletions examples/velocity_manual_resourcepack.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* This example shows how to disable the velocity plugin
* and manually handle resource packs during Velocity transfers
*
* Use this if you want full control over resource pack acceptance
* instead of the automatic behavior.
*
* WARNING: Disabling the velocity plugin will cause bot disconnections
* during Velocity server transfers unless you handle the configuration
* phase manually (advanced users only).
*/
const mineflayer = require('mineflayer')

if (process.argv.length < 4 || process.argv.length > 6) {
console.log('Usage : node velocity_manual_resourcepack.js <host> <port> [<name>] [online?]')
process.exit(1)
}

const bot = mineflayer.createBot({
host: process.argv[2],
port: parseInt(process.argv[3]),
username: process.argv[4] ? process.argv[4] : 'manual_bot',
auth: process.argv[5] ? 'microsoft' : 'offline',
// Disable the velocity plugin
plugins: {
velocity: false
}
})

bot.on('login', () => {
console.log('Logged in (velocity plugin disabled)')
})

bot.on('spawn', () => {
console.log('Spawned in server:', bot.game.dimension)
})

// Manually accept resource packs
bot.on('resourcePack', (url, uuid) => {
console.log('Resource pack received:', url)
console.log('Accepting manually...')
bot.acceptResourcePack()
})

bot.on('end', (reason) => {
console.log('Bot disconnected:', reason)
})

bot.on('error', (err) => {
console.error('Error:', err.message)
})

bot.on('kicked', (reason) => {
console.log('Kicked:', reason)
console.log('Note: If kicked during transfer, you may need to enable the velocity plugin')
})
69 changes: 69 additions & 0 deletions examples/velocity_transfer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* This example demonstrates connecting to a Velocity proxy server
* and handling server transfers (1.20.2+)
*
* The velocity plugin is loaded by default and will automatically:
* - Handle the configuration phase during transfers
* - Accept resource packs during configuration
* - Block physics packets while in configuration phase
* - Re-enable physics after configuration completes
*
* To disable the velocity plugin, add to createBot options:
* plugins: { velocity: false }
*
* Note: The plugin is harmless even if loaded - it only activates
* during configuration phase (Velocity transfers, not normal gameplay)
*
* This fixes the issue: https://github.com/PrismarineJS/mineflayer/issues/3764
*/
const mineflayer = require('mineflayer')

if (process.argv.length < 4 || process.argv.length > 6) {
console.log('Usage : node velocity_transfer.js <host> <port> [<name>] [online?]')
process.exit(1)
}

const bot = mineflayer.createBot({
host: process.argv[2],
port: parseInt(process.argv[3]),
username: process.argv[4] ? process.argv[4] : 'velocity_bot',
auth: process.argv[5] ? 'microsoft' : 'offline'
// The velocity plugin is enabled by default
// To disable it, uncomment the following line:
// plugins: { velocity: false }
})

bot.on('login', () => {
console.log('Logged in to Velocity proxy')
})

bot.on('spawn', () => {
console.log('Spawned in server:', bot.game.dimension)
})

// Track configuration phase events
bot.on('configurationPhase', (phase) => {
if (phase === 'start') {
console.log('Entering configuration phase (server transfer starting...)')
} else if (phase === 'end') {
console.log('Exiting configuration phase (server transfer complete)')
}
})

// Optional: Track resource pack acceptance
bot.on('resourcePack', (url, uuid) => {
console.log('Resource pack received:', url)
console.log('Note: Resource packs are automatically accepted during configuration phase')
})

bot.on('end', (reason) => {
console.log('Bot disconnected:', reason)
})

bot.on('error', (err) => {
console.error('Error:', err.message)
})

bot.on('kicked', (reason) => {
console.log('Kicked:', reason)
})
1 change: 1 addition & 0 deletions lib/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const plugins = {
health: require('./plugins/health'),
inventory: require('./plugins/inventory'),
kick: require('./plugins/kick'),
velocity: require('./plugins/velocity'),
physics: require('./plugins/physics'),
place_block: require('./plugins/place_block'),
rain: require('./plugins/rain'),
Expand Down
12 changes: 12 additions & 0 deletions lib/plugins/physics.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ function inject (bot, { physicsEnabled, maxCatchupTicks }) {
}

function sendPacketPosition (position, onGround) {
// Don't send position packets during configuration phase (Velocity support)
if (bot.inConfigurationPhase) return

// sends data, no logic
const oldPos = new Vec3(lastSent.x, lastSent.y, lastSent.z)
lastSent.x = position.x
Expand All @@ -110,6 +113,9 @@ function inject (bot, { physicsEnabled, maxCatchupTicks }) {
}

function sendPacketLook (yaw, pitch, onGround) {
// Don't send look packets during configuration phase (Velocity support)
if (bot.inConfigurationPhase) return

// sends data, no logic
const oldPos = new Vec3(lastSent.x, lastSent.y, lastSent.z)
lastSent.yaw = yaw
Expand All @@ -121,6 +127,9 @@ function inject (bot, { physicsEnabled, maxCatchupTicks }) {
}

function sendPacketPositionAndLook (position, yaw, pitch, onGround) {
// Don't send position_look packets during configuration phase (Velocity support)
if (bot.inConfigurationPhase) return

// sends data, no logic
const oldPos = new Vec3(lastSent.x, lastSent.y, lastSent.z)
lastSent.x = position.x
Expand Down Expand Up @@ -154,6 +163,9 @@ function inject (bot, { physicsEnabled, maxCatchupTicks }) {
// Only send updates for 20 ticks after death
if (isEntityRemoved()) return

// Don't send any position packets during configuration phase (Velocity support)
if (bot.inConfigurationPhase) return

// Increment the yaw in baby steps so that notchian clients (not the server) can keep up.
const dYaw = deltaYaw(bot.entity.yaw, lastSentYaw)
const dPitch = bot.entity.pitch - (lastSentPitch || 0)
Expand Down
180 changes: 180 additions & 0 deletions lib/plugins/velocity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/**
* Velocity proxy support for Minecraft 1.20.2+ configuration phase
*
* This plugin manages the configuration phase introduced in Minecraft 1.20.2
* and extended in 1.21+, which is required for proper server transfers via
* Velocity proxy.
*
* During the configuration phase:
* - Gameplay packets (movement, physics) are not allowed
* - Only configuration packets should be sent
* - Resource packs must be handled correctly
*
* Issue: https://github.com/PrismarineJS/mineflayer/issues/3764
*/

module.exports = inject

function inject (bot) {
// Track whether we're in configuration phase
bot.inConfigurationPhase = false
let configurationFinished = false

// Resource pack response codes
const TEXTURE_PACK_RESULTS = {
SUCCESSFULLY_LOADED: 0,
DECLINED: 1,
FAILED_DOWNLOAD: 2,
ACCEPTED: 3
}

/**
* Enter configuration phase
*/
function enterConfigurationPhase () {
if (bot.inConfigurationPhase) return

bot.inConfigurationPhase = true
configurationFinished = false

// Disable physics during configuration to prevent movement packets
if (bot.physicsEnabled !== undefined) {
bot._physicsWasEnabled = bot.physicsEnabled
bot.physicsEnabled = false
}

bot.emit('configurationPhase', 'start')
}

/**
* Exit configuration phase
*/
function exitConfigurationPhase () {
if (!bot.inConfigurationPhase) return

bot.inConfigurationPhase = false

// Re-enable physics after configuration
setTimeout(() => {
if (bot._physicsWasEnabled && !bot._ended) {
bot.physicsEnabled = true
delete bot._physicsWasEnabled
}
}, 2000)

bot.emit('configurationPhase', 'end')
}

// === CONFIGURATION PHASE DETECTION ===

// Method 1: start_configuration event (1.20.2+)
bot._client.on('start_configuration', () => {
enterConfigurationPhase()
})

// Method 2: select_known_packs event (1.21+)
bot._client.on('select_known_packs', (packet) => {
enterConfigurationPhase()
// Note: No response needed for select_known_packs
})

// Method 3: registry_data event (1.20.2+)
bot._client.on('registry_data', (packet) => {
// Only enter if not already in configuration phase
// (registry_data can be sent multiple times)
if (!bot.inConfigurationPhase) {
enterConfigurationPhase()
}
})

// Method 4: transfer event (1.20.5+)
bot._client.on('transfer', (packet) => {
enterConfigurationPhase()
})

// === AUTOMATIC RESOURCE PACK ACCEPTANCE ===

/**
* Automatically accept resource packs during configuration phase
* This is required for Velocity transfers to complete successfully
*/

// Handle add_resource_pack (1.20.3+)
bot._client.prependListener('add_resource_pack', (data) => {
if (!bot.inConfigurationPhase) return

// Send ACCEPTED immediately
bot._client.write('resource_pack_receive', {
uuid: data.uuid,
result: TEXTURE_PACK_RESULTS.ACCEPTED
})

// Send SUCCESSFULLY_LOADED immediately
// Server needs this before sending finish_configuration
bot._client.write('resource_pack_receive', {
uuid: data.uuid,
result: TEXTURE_PACK_RESULTS.SUCCESSFULLY_LOADED
})
})

// Handle resource_pack_send (older versions with UUID support)
bot._client.prependListener('resource_pack_send', (data) => {
if (!bot.inConfigurationPhase) return
if (!data.uuid) return // Only handle UUID-based resource packs

const UUID = require('uuid-1345')
const uuid = new UUID(data.uuid)

// Send ACCEPTED immediately
bot._client.write('resource_pack_receive', {
uuid: uuid,
result: TEXTURE_PACK_RESULTS.ACCEPTED
})

// Send SUCCESSFULLY_LOADED immediately
bot._client.write('resource_pack_receive', {
uuid: uuid,
result: TEXTURE_PACK_RESULTS.SUCCESSFULLY_LOADED
})
})

// === FINISH CONFIGURATION ===

bot._client.on('finish_configuration', () => {
configurationFinished = true

// Acknowledge finish_configuration
try {
bot._client.write('finish_configuration', {})
} catch (err) {
// Ignore if packet doesn't exist for this version
}

// Exit configuration phase after a short delay
setTimeout(() => {
exitConfigurationPhase()
}, 500)
})

// === FALLBACKS ===

// Fallback: Exit on login if we're still in configuration phase
bot.on('login', () => {
if (bot.inConfigurationPhase && configurationFinished) {
exitConfigurationPhase()
}
})

// Fallback: Exit on spawn if we're still in configuration phase
bot.on('spawn', () => {
if (bot.inConfigurationPhase) {
exitConfigurationPhase()
}
})

// Cleanup on end
bot.on('end', () => {
bot.inConfigurationPhase = false
configurationFinished = false
})
}
Loading