master d1f4d7fdbebc cached
38 files
201.6 KB
56.6k tokens
165 symbols
1 requests
Download .txt
Showing preview only (213K chars total). Download the full file or copy to clipboard to get everything.
Repository: PrismarineJS/mineflayer-pathfinder
Branch: master
Commit: d1f4d7fdbebc
Files: 38
Total size: 201.6 KB

Directory structure:
gitextract_nd4wfh09/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── ci.yml
│       ├── commands.yml
│       └── npm-publish.yml
├── .gitignore
├── .gitpod.yml
├── .npmrc
├── LICENSE
├── examples/
│   ├── bench.js
│   ├── blockInteraction.js
│   ├── callback.js
│   ├── chaining-goals.js
│   ├── example.js
│   ├── exclusionArea.js
│   ├── movements.js
│   ├── multiple.js
│   ├── promise.js
│   └── tutorial/
│       ├── basic.js
│       ├── goalComposite.js
│       └── goalsExplained.md
├── history.md
├── index.d.ts
├── index.js
├── lib/
│   ├── astar.js
│   ├── goals.js
│   ├── goto.js
│   ├── heap.js
│   ├── interactable.json
│   ├── lock.js
│   ├── move.js
│   ├── movements.js
│   ├── passableEntities.json
│   ├── physics.js
│   └── shapes.js
├── package.json
├── readme.md
└── test/
    ├── internalTest.js
    └── schematics/
        └── parkour1.schem

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: npm
  directory: "/"
  schedule:
    interval: daily
  open-pull-requests-limit: 10


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [24.x]

    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm install
    - run: npm test

================================================
FILE: .github/workflows/commands.yml
================================================
name: Repo Commands

on:
  issue_comment:        # Handle comment commands
    types: [created]
  pull_request:         # Handle renamed PRs
    types: [edited]
permissions:
  contents: write

jobs:
  comment-trigger:
    runs-on: ubuntu-latest
    steps:
    - name: Check out repository
      uses: actions/checkout@v3
    - name: Run command handlers
      uses: PrismarineJS/prismarine-repo-actions@master
      with:
        token: ${{ secrets.PAT_PASSWORD }}
        install-command: npm install
        /fixlint.fix-command: npm run fix


================================================
FILE: .github/workflows/npm-publish.yml
================================================
name: npm-publish
on:
  push:
    branches:
      - master # Change this to your default branch
jobs:
  npm-publish:
    name: npm-publish
    runs-on: ubuntu-latest
    steps:
    - name: Checkout repository
      uses: actions/checkout@master
    - name: Set up Node.js
      uses: actions/setup-node@master
      with:
        node-version: 14.0.0
    - id: publish
      uses: JS-DevTools/npm-publish@v1
      with:
        token: ${{ secrets.NPM_AUTH_TOKEN }}
    - name: Create Release
      if: steps.publish.outputs.type != 'none'
      id: create_release
      uses: actions/create-release@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        tag_name: ${{ steps.publish.outputs.version }}
        release_name: Release ${{ steps.publish.outputs.version }}
        body: ${{ steps.publish.outputs.version }}
        draft: false
        prerelease: false


================================================
FILE: .gitignore
================================================
node_modules
package-lock.json
yarn.lock
.vscode


================================================
FILE: .gitpod.yml
================================================
tasks:
- command: npm install


================================================
FILE: .npmrc
================================================
package-lock=false


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2020 Karang

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: examples/bench.js
================================================
// Simple test to evaluate how much time it takes to find a path of 100 blocks

const mineflayer = require('mineflayer')
const { pathfinder, Movements } = require('mineflayer-pathfinder')
const { GoalXZ } = require('mineflayer-pathfinder').goals
const { performance } = require('perf_hooks')

if (process.argv.length > 6) {
  console.log('Usage : node bench.js [<host>] [<port>] [<name>] [<password>]')
  process.exit(1)
}

const bot = mineflayer.createBot({
  host: process.argv[2] || 'localhost',
  port: parseInt(process.argv[3]) || 25565,
  username: process.argv[4] || 'bench',
  password: process.argv[5]
})

bot.loadPlugin(pathfinder)

bot.on('error', (err) => console.log(err))

const createTime = performance.now()
bot.once('spawn', () => {
  console.log('Spawning took ' + (performance.now() - createTime).toFixed(2) + ' ms.')

  const defaultMove = new Movements(bot)
  const goal = new GoalXZ(bot.entity.position.x + 100, bot.entity.position.z)
  const results = bot.pathfinder.getPathTo(defaultMove, goal, 10000)
  console.log('I can get there in ' + results.path.length + ' moves. Computation took ' + results.time.toFixed(2) + ' ms.')

  bot.quit()
  process.exit()
})


================================================
FILE: examples/blockInteraction.js
================================================
const mineflayer = require('mineflayer')
const { pathfinder, Movements } = require('mineflayer-pathfinder')
const { GoalNear, GoalBlock, GoalXZ, GoalY, GoalFollow, GoalPlaceBlock, GoalLookAtBlock } = require('mineflayer-pathfinder').goals
const Vec3 = require('vec3').Vec3

if (process.argv.length > 6) {
  console.log('Usage : node blockInteraction.js [<host>] [<port>] [<name>] [<password>]')
  process.exit(1)
}

const bot = mineflayer.createBot({
  host: process.argv[2] || 'localhost',
  port: parseInt(process.argv[3]) || 25565,
  username: process.argv[4] || 'blockPlacer',
  password: process.argv[5]
})

bot.once('spawn', () => {
  console.info('Joined the server')

  bot.loadPlugin(pathfinder)
  const defaultMove = new Movements(bot)
  bot.pathfinder.setMovements(defaultMove)

  bot.on('chat', async (username, message) => {
    const target = bot.players[username].entity

    if (message.startsWith('place')) {
      const [, itemName] = message.split(' ')
      if (!target) {
        bot.chat('I can\'t see you')
        return
      }
      const itemsInInventory = bot.inventory.items().filter(item => item.name.includes(itemName))
      if (itemsInInventory.length === 0) {
        bot.chat('I dont have ' + itemName)
        return
      }

      try {
        const rayBlock = rayTraceEntitySight(target)
        if (!rayBlock) {
          bot.chat('Block is out of reach')
          return
        }
        const face = directionToVector(rayBlock.face)
        await bot.pathfinder.goto(new GoalPlaceBlock(rayBlock.position.offset(face.x, face.y, face.z), bot.world, {
          range: 4
        }))
        await bot.equip(itemsInInventory[0], 'hand')
        await bot.lookAt(rayBlock.position.offset(face.x * 0.5 + 0.5, face.y * 0.5 + 0.5, face.z * 0.5 + 0.5))
        await bot.placeBlock(rayBlock, face)
      } catch (e) {
        console.error(e)
      }
    } else if (message.startsWith('break')) {
      if (!target) {
        bot.chat('I can\'t see you')
        return
      }

      try {
        const rayBlock = rayTraceEntitySight(target)
        if (!rayBlock) {
          bot.chat('Block is out of reach')
          return
        }
        await bot.pathfinder.goto(new GoalLookAtBlock(rayBlock.position, bot.world, { range: 4 }))
        const bestHarvestTool = bot.pathfinder.bestHarvestTool(bot.blockAt(rayBlock.position))
        if (bestHarvestTool) await bot.equip(bestHarvestTool, 'hand')
        await bot.dig(bot.blockAt(rayBlock.position), true, 'raycast')
      } catch (e) {
        console.error(e)
      }
    } else if (message === 'come') {
      if (!target) {
        bot.chat('I don\'t see you !')
        return
      }
      const p = target.position

      bot.pathfinder.setMovements(defaultMove)
      bot.pathfinder.setGoal(new GoalNear(p.x, p.y, p.z, 1))
    } else if (message === 'stop') {
      bot.pathfinder.stop()
    } else if (message === 'follow') {
      bot.pathfinder.setMovements(defaultMove)
      bot.pathfinder.setGoal(new GoalFollow(target, 1), true)
      // follow is a dynamic goal: setGoal(goal, dynamic=true)
      // when reached, the goal will stay active and will not
      // emit an event
    } else if (message.startsWith('goto')) {
      const cmd = message.split(' ')

      if (cmd.length === 4) { // goto x y z
        const x = parseInt(cmd[1], 10)
        const y = parseInt(cmd[2], 10)
        const z = parseInt(cmd[3], 10)

        bot.pathfinder.setMovements(defaultMove)
        bot.pathfinder.setGoal(new GoalBlock(x, y, z))
      } else if (cmd.length === 3) { // goto x z
        const x = parseInt(cmd[1], 10)
        const z = parseInt(cmd[2], 10)

        bot.pathfinder.setMovements(defaultMove)
        bot.pathfinder.setGoal(new GoalXZ(x, z))
      } else if (cmd.length === 2) { // goto y
        const y = parseInt(cmd[1], 10)

        bot.pathfinder.setMovements(defaultMove)
        bot.pathfinder.setGoal(new GoalY(y))
      }
    }
  })

  const rayTraceEntitySight = function (entity) {
    if (bot.world?.raycast) {
      const { height, position, yaw, pitch } = entity
      const x = -Math.sin(yaw) * Math.cos(pitch)
      const y = Math.sin(pitch)
      const z = -Math.cos(yaw) * Math.cos(pitch)
      const rayBlock = bot.world.raycast(position.offset(0, height, 0), new Vec3(x, y, z), 120)
      if (rayBlock) {
        return rayBlock
      }
    } else {
      throw Error('bot.world.raycast does not exists. Try updating prismarine-world.')
    }
  }
})

bot.on('error', console.error)
bot.on('kicked', console.error)

function directionToVector (dir) {
  if (dir > 5 || dir < 0) return null
  if (dir === 0) {
    return new Vec3(0, -1, 0)
  } else if (dir === 1) {
    return new Vec3(0, 1, 0)
  } else if (dir === 2) {
    return new Vec3(0, 0, -1)
  } else if (dir === 3) {
    return new Vec3(0, 0, 1)
  } else if (dir === 4) {
    return new Vec3(-1, 0, 0)
  } else if (dir === 5) {
    return new Vec3(1, 0, 0)
  }
}


================================================
FILE: examples/callback.js
================================================
// This example uses promises instead of events like "goal_reached" for a cleaner look

const mineflayer = require('mineflayer')
const pathfinder = require('mineflayer-pathfinder').pathfinder
const Movements = require('mineflayer-pathfinder').Movements
const { GoalNear } = require('mineflayer-pathfinder').goals
const bot = mineflayer.createBot({ username: 'Player' })

// Load plugins
bot.loadPlugin(pathfinder)

bot.once('spawn', () => {
  // Set pathfinder movements
  const defaultMove = new Movements(bot)
  bot.pathfinder.setMovements(defaultMove)

  bot.on('chat', async (username, message) => {
    // If username is the same as the Bot's username, don't continue
    if (username === bot.username) return

    // Only continue when the message is "come"
    if (message === 'come') {
      const target = bot.players[username] ? bot.players[username].entity : null

      // If Player doesn't exist, don't continue
      if (!target) return bot.chat("I don't see you !")

      // Await pathfinder to complete the goal, then move to bot.chat and print "I've arrived !"
      bot.pathfinder.goto(new GoalNear(target.position.x, target.position.y, target.position.z, 1)).then(announceArrived)
    }
  })

  function announceArrived () {
    const botPosition = bot.entity.position
    bot.chat(`I've arrived, I'm at ${botPosition.x}, ${botPosition.y}, ${botPosition.z}!`)
  }
})


================================================
FILE: examples/chaining-goals.js
================================================
/* Pathfinder Chaining Goals example

This example shows how to chain goals together.
Run this example with:

node examples/chaining-goals.js [host] [port] [mail/username] [is online `true`]

If you want to connect to an offline server use a username instead of an email
and no password. If you want to join a online server use your email and follow
the instructions in the command line to authenticated with microsoft auth.

In Game Chat commands:
come
  - Path finds to the chatting player's position when in render distance.

follow
  - Follows the chatting player's entity until `stop` is chatted

stop
  - Stops the bot from following or path finding

point
  - Set a checkpoint at the chatting player's position

walk
  - Walk to all set checkpoints
*/

// Import all the modules we need
const mineflayer = require('mineflayer')
const { pathfinder, Movements } = require('mineflayer-pathfinder')
const {
  GoalNear, GoalBlock, GoalFollow
} = require('mineflayer-pathfinder').goals

if (process.argv.length > 6) {
  console.log('Usage : node chaining-goals.js [<host>] [<port>] ' +
    '[<microsoft email/name>] [<is online `true`>]')
  process.exit(1)
}

// Create the bot
const bot = mineflayer.createBot({
  host: process.argv[2] || 'localhost',
  port: parseInt(process.argv[3]) || 25565,
  username: process.argv[4] || 'checkpointBot',
  // to join offline servers auth type has to be 'mojang' (???)
  auth: process.argv[5] === 'true' ? 'microsoft' : 'mojang',
  // Skip validation when joining a offline server
  skipValidation: process.argv[5] !== 'true'
})

// Load the pathfinder plugin
bot.loadPlugin(pathfinder)

// Wait for the bot to spawn in the world
bot.once('spawn', () => {
  // We create different movement generators for different type of activity
  const defaultMove = new Movements(bot)
  bot.pathfinder.setMovements(defaultMove)

  // Print debug messages when the path changes
  bot.on('path_update', (r) => {
    const nodesPerTick = (r.visitedNodes * 50 / r.time).toFixed(2)
    console.log(`I can get there in ${r.path.length} moves. ` +
      `Computation took ${r.time.toFixed(2)} ms (${r.visitedNodes} nodes` +
      `, ${nodesPerTick} nodes/tick)`)
  })

  bot.on('goal_reached', (goal) => {
    console.log('Here I am !')
  })

  bot.on('path_reset', (reason) => {
    console.log(`Path was reset for reason: ${reason}`)
  })

  let checkpoints = []

  // Make pathfinder walk to all checkpoints in order
  async function walkCheckpoints () {
    if (checkpoints.length === 0) {
      bot.chat('There are no checkpoints')
      return
    }

    // Remove all checkpoints when starting to walking
    const checkPointCopy = [...checkpoints]
    checkpoints = []
    for (const checkpoint of checkPointCopy) {
      // Make a new goal to goto. GoalBlock will make the bot walk to the
      // block position off checkpoint.
      const goal = new GoalBlock(checkpoint.x, checkpoint.y, checkpoint.z)
      try {
        // Use await to make sure the bot is at the checkpoint before moving on
        await bot.pathfinder.goto(goal)
      } catch (error) {
        console.log('Got error from goto', error.message)
        // If we get an error we quit the loop
        return
      }
    }
  }

  // Listen for chat messages chatted by other players
  // Note: This may not work on every server as mineflayer uses regex to match
  // chat messages. Some servers may use chat messages that do not match the
  // regex.
  bot.on('chat', (username, message) => {
    if (username === bot.username) return // Ignore our own messages

    // Get the player entity from the username.
    // Note: This might not work on some servers where the players nametag name
    // dose not match the chat message name.
    const target = bot.players[username] ? bot.players[username].entity : null
    if (message === 'come') {
      if (!target) {
        bot.chat('I don\'t see you !')
        return
      }
      const p = target.position

      bot.pathfinder.setGoal(new GoalNear(p.x, p.y, p.z, 1))
    } else if (message === 'follow') {
      bot.pathfinder.setGoal(new GoalFollow(target, 3), true)
      // follow is a dynamic goal: setGoal(goal, dynamic=true)
      // when reached, the goal will stay active and will not
      // emit an event
    } else if (message === 'stop') {
      bot.pathfinder.stop() // Also resets the current goal
    } else if (message === 'point') {
      if (!target) {
        bot.chat('I don\'t see you !')
        return
      }
      const pos = target.position.floored()
      checkpoints.push(pos)
      bot.chat(`Checkpoint ${pos} set`)
    } else if (message === 'walk') {
      walkCheckpoints()
        .then(() => {
          bot.chat('Done')
        })
        .catch(console.error)
    }
  })
})


================================================
FILE: examples/example.js
================================================
const mineflayer = require('mineflayer')
const { pathfinder, Movements } = require('mineflayer-pathfinder')
const { GoalNear, GoalBlock, GoalXZ, GoalY, GoalInvert, GoalFollow, GoalBreakBlock } = require('mineflayer-pathfinder').goals

if (process.argv.length > 6) {
  console.log('Usage : node example.js [<host>] [<port>] [<name>] [<password>]')
  process.exit(1)
}

const bot = mineflayer.createBot({
  host: process.argv[2] || 'localhost',
  port: parseInt(process.argv[3]) || 25565,
  username: process.argv[4] || 'pathfinder',
  password: process.argv[5]
})

bot.loadPlugin(pathfinder)

bot.once('spawn', () => {
  // We create different movement generators for different type of activity
  const defaultMove = new Movements(bot)

  bot.on('path_update', (r) => {
    const nodesPerTick = (r.visitedNodes * 50 / r.time).toFixed(2)
    console.log(`I can get there in ${r.path.length} moves. Computation took ${r.time.toFixed(2)} ms (${r.visitedNodes} nodes, ${nodesPerTick} nodes/tick)`)
  })

  bot.on('goal_reached', (goal) => {
    console.log('Here I am !')
  })

  bot.on('path_reset', (reason) => {
    console.log(`Path was reset for reason: ${reason}`)
  })

  bot.on('chat', (username, message) => {
    if (username === bot.username) return

    const target = bot.players[username] ? bot.players[username].entity : null
    if (message === 'come') {
      if (!target) {
        bot.chat('I don\'t see you !')
        return
      }
      const p = target.position

      bot.pathfinder.setMovements(defaultMove)
      bot.pathfinder.setGoal(new GoalNear(p.x, p.y, p.z, 1))
    } else if (message.startsWith('goto')) {
      const cmd = message.split(' ')

      if (cmd.length === 4) { // goto x y z
        const x = parseInt(cmd[1], 10)
        const y = parseInt(cmd[2], 10)
        const z = parseInt(cmd[3], 10)

        bot.pathfinder.setMovements(defaultMove)
        bot.pathfinder.setGoal(new GoalBlock(x, y, z))
      } else if (cmd.length === 3) { // goto x z
        const x = parseInt(cmd[1], 10)
        const z = parseInt(cmd[2], 10)

        bot.pathfinder.setMovements(defaultMove)
        bot.pathfinder.setGoal(new GoalXZ(x, z))
      } else if (cmd.length === 2) { // goto y
        const y = parseInt(cmd[1], 10)

        bot.pathfinder.setMovements(defaultMove)
        bot.pathfinder.setGoal(new GoalY(y))
      }
    } else if (message === 'follow') {
      bot.pathfinder.setMovements(defaultMove)
      bot.pathfinder.setGoal(new GoalFollow(target, 3), true)
      // follow is a dynamic goal: setGoal(goal, dynamic=true)
      // when reached, the goal will stay active and will not
      // emit an event
    } else if (message === 'avoid') {
      bot.pathfinder.setMovements(defaultMove)
      bot.pathfinder.setGoal(new GoalInvert(new GoalFollow(target, 5)), true)
    } else if (message === 'stop') {
      bot.pathfinder.stop()
    } else if (message === 'break') {
      if (!target) {
        bot.chat('I can\'t see you!')
        return
      }
      const p = target.position.offset(0, -1, 0)
      const goal = new GoalBreakBlock(p.x, p.y, p.z, bot)
      bot.pathfinder.goto(goal)
        .then(() => {
          bot.dig(bot.blockAt(p), 'raycast')
            .catch(err => console.error('digging error', err))
        }, (err) => {
          console.error('Pathfing error', err)
        })
    }
  })
})


================================================
FILE: examples/exclusionArea.js
================================================
/* Pathfinder Exclusion Area example

This example shows the use of exclusion areas with the Movement Class.

In Game Chat commands:
come
  - Path finds to the chatting player's position when in render distance.
exclude this (break | step | place) <radius>
  - Exclude a spherical area off size <radius> of type break, step or place at the chatting
  player's position  when in render distance
goto (x y z) | (x z) | y
  - Goto a specific coordinate
follow
  - Follows the chatting player's entity until stop is chatted
stop
  - Stops the bot from following or path finding
*/

const mineflayer = require('mineflayer')
const { pathfinder, Movements } = require('mineflayer-pathfinder')
const { GoalNear, GoalBlock, GoalXZ, GoalY, GoalFollow } = require('mineflayer-pathfinder').goals

if (process.argv.length > 6) {
  console.log('Usage : node example.js [<host>] [<port>] [<name>] [<password>]')
  process.exit(1)
}

const bot = mineflayer.createBot({
  host: process.argv[2] || 'localhost',
  port: parseInt(process.argv[3]) || 25565,
  username: process.argv[4] || 'exclusionAreaBot',
  password: process.argv[5]
})

bot.loadPlugin(pathfinder)

bot.once('spawn', () => {
  // We create different movement generators for different type of activity
  const defaultMove = new Movements(bot)
  bot.pathfinder.setMovements(defaultMove)

  bot.on('path_update', (r) => {
    const nodesPerTick = (r.visitedNodes * 50 / r.time).toFixed(2)
    console.log(`I can get there in ${r.path.length} moves. ` +
      `Computation took ${r.time.toFixed(2)} ms (${r.visitedNodes} nodes` +
      `, ${nodesPerTick} nodes/tick)`)
  })

  bot.on('goal_reached', (goal) => {
    console.log('Here I am !')
  })

  bot.on('path_reset', (reason) => {
    console.log(`Path was reset for reason: ${reason}`)
  })

  bot.on('chat', (username, message) => {
    if (username === bot.username) return

    const target = bot.players[username] ? bot.players[username].entity : null
    if (message === 'come') {
      if (!target) {
        bot.chat('I don\'t see you !')
        return
      }
      const p = target.position

      bot.pathfinder.setGoal(new GoalNear(p.x, p.y, p.z, 1))
    } else if (message.startsWith('exclude')) {
      const cmd = message.split(' ')
      if (cmd[1] === 'this') {
        if (!target) {
          bot.chat('I can\'t see you')
          return
        }
        const type = cmd[2].trim()
        if (!['break', 'step', 'place'].includes(type.toLowerCase())) {
          return bot.chat('type must be "break", "step" or "place"')
        }
        const radius = Number(cmd[3])
        const center = target.position.floored()
        if (isNaN(radius)) return bot.chat('Radius must be a number')
        // Import typings for intellisense
        /**
         * @param {import('mineflayer-pathfinder').SafeBlock} block block */
        const isExcluded = (block) => {
          return block.position.distanceTo(center) <= radius ? 0 : 100
        }
        switch (type.toLowerCase()) {
          case 'step':
            bot.pathfinder.movements.exclusionAreasStep.push(isExcluded)
            break
          case 'break':
            bot.pathfinder.movements.exclusionAreasBreak.push(isExcluded)
            break
          case 'place':
            bot.pathfinder.movements.exclusionAreasPlace.push(isExcluded)
            break
        }
        // At 5. The bot avoids the area most of the time but can still move into and out of it.
        bot.pathfinder.movements.exclusionAreaPower = 5
        bot.pathfinder.setMovements(bot.pathfinder.movements)
        bot.chat(`Added exclusion area circle around ${center.toString()} with radius ${radius}`)
      } else {
        bot.chat('Usage: exclude this (break | step | place) <radius>')
      }
    } else if (message.startsWith('goto')) {
      const cmd = message.split(' ')

      if (cmd.length === 4) { // goto x y z
        const x = parseInt(cmd[1], 10)
        const y = parseInt(cmd[2], 10)
        const z = parseInt(cmd[3], 10)

        bot.pathfinder.setGoal(new GoalBlock(x, y, z))
      } else if (cmd.length === 3) { // goto x z
        const x = parseInt(cmd[1], 10)
        const z = parseInt(cmd[2], 10)

        bot.pathfinder.setGoal(new GoalXZ(x, z))
      } else if (cmd.length === 2) { // goto y
        const y = parseInt(cmd[1], 10)

        bot.pathfinder.setGoal(new GoalY(y))
      }
    } else if (message === 'follow') {
      bot.pathfinder.setGoal(new GoalFollow(target, 3), true)
      // follow is a dynamic goal: setGoal(goal, dynamic=true)
      // when reached, the goal will stay active and will not
      // emit an event
    } else if (message === 'stop') {
      bot.pathfinder.stop() // Also resets the current goal
    }
  })
})


================================================
FILE: examples/movements.js
================================================
/*
 * This example demonstrates how easy it is to change the default movement
 *
 * Below are a few options you can edit in the Movement Class
 * but remember to check out the API documentation to find even more!
 *
 * This bot also follows a player when called called out to it.
 */

const mineflayer = require('mineflayer')
const { pathfinder, Movements } = require('mineflayer-pathfinder')
const { GoalNear } = require('mineflayer-pathfinder').goals

const bot = mineflayer.createBot({
  host: process.argv[2],
  port: parseInt(process.argv[3]),
  username: process.argv[4] ? process.argv[4] : 'movementsbot',
  password: process.argv[5]
})

bot.loadPlugin(pathfinder)

bot.once('spawn', () => {
  /*
   * pathfinder comes with default moves preinitialized (a instance of the movement class)
   * the moves come with default logic, like how much it can fall
   * what blocks are used to scaffold, and what blocks to avoid.
   */

  // To get started create a instance of the Movements class
  const customMoves = new Movements(bot)
  // To make changes to the behaviour, customize the properties of the instance
  customMoves.canDig = false
  customMoves.allow1by1towers = false
  customMoves.scafoldingBlocks.push(bot.registry.itemsByName.stone.id)
  // Thing to note scaffoldingBlocks are an array while other namespaces are usually sets
  customMoves.blocksToAvoid.add(bot.registry.blocksByName.carrot.id)

  // To initialize the new movements use the .setMovements method.
  bot.pathfinder.setMovements(customMoves)

  bot.on('chat', function (username, message) {
    if (username === bot.username) return

    if (message === 'come') {
      const target = bot.players[username]?.entity
      if (!target) {
        bot.chat('I don\'t see you !')
        return
      }
      const p = target.position

      bot.pathfinder.setGoal(new GoalNear(p.x, p.y, p.z, 1))
    }
  })
})


================================================
FILE: examples/multiple.js
================================================
const mineflayer = require('mineflayer')
const { pathfinder, Movements } = require('mineflayer-pathfinder')
const { GoalInvert, GoalFollow } = require('mineflayer-pathfinder').goals

mineflayer.multiple = (bots, constructor) => {
  const { Worker, isMainThread, workerData } = require('worker_threads')
  if (isMainThread) {
    const threads = []
    for (const i in bots) {
      threads.push(new Worker(__filename, { workerData: bots[i] }))
    }
  } else {
    constructor(workerData)
  }
}

const bots = []
for (let i = 0; i < 40; i++) {
  bots.push({ username: `Bot${i}` })
}

mineflayer.multiple(bots, ({ username }) => {
  const bot = mineflayer.createBot({ username, viewDistance: 'tiny' })

  bot.loadPlugin(pathfinder)

  bot.once('spawn', () => {
    // We create different movement generators for different type of activity
    const defaultMove = new Movements(bot)
    defaultMove.allowFreeMotion = true
    bot.pathfinder.searchRadius = 10

    bot.on('path_update', (results) => {
      console.log('[' + username + '] I can get there in ' + results.path.length + ' moves. Computation took ' + results.time.toFixed(2) + ' ms.')
    })

    bot.on('goal_reached', (goal) => {
      console.log('[' + username + '] Here I am !')
    })

    bot.on('chat', (username, message) => {
      if (username === bot.username) return

      const target = bot.players[username].entity
      if (message === 'follow') {
        bot.pathfinder.setMovements(defaultMove)
        bot.pathfinder.setGoal(new GoalFollow(target, 5), true)
      } else if (message === 'avoid') {
        bot.pathfinder.setMovements(defaultMove)
        bot.pathfinder.setGoal(new GoalInvert(new GoalFollow(target, 5)), true)
      } else if (message === 'stop') {
        bot.pathfinder.setGoal(null)
      }
    })
  })
})


================================================
FILE: examples/promise.js
================================================
// This example uses promises instead of events like "goal_reached" for a cleaner look

const mineflayer = require('mineflayer')
const pathfinder = require('mineflayer-pathfinder').pathfinder
const Movements = require('mineflayer-pathfinder').Movements
const { GoalNear } = require('mineflayer-pathfinder').goals
const bot = mineflayer.createBot({ username: 'Player' })

// Load pathfinder
bot.loadPlugin(pathfinder)

bot.once('spawn', () => {
  // Set pathfinder movements
  const defaultMove = new Movements(bot)
  bot.pathfinder.setMovements(defaultMove)

  bot.on('chat', async (username, message) => {
    // If username is the same as the Bot's username, don't continue
    if (username === bot.username) return

    // Only continue when the message is "come"
    if (message === 'come') {
      const target = bot.players[username] ? bot.players[username].entity : null

      // If Player doesn't exist, don't continue
      if (!target) return bot.chat("I don't see you !")

      // Await pathfinder to complete the goal, then move to bot.chat and print "I've arrived !"
      await bot.pathfinder.goto(new GoalNear(target.position.x, target.position.y, target.position.z, 1))
      bot.chat("I've arrived!")
    }
  })
})


================================================
FILE: examples/tutorial/basic.js
================================================
/*
 * This example shows the usage of the GoalBlock
 * goal for mineflayer-pathfinder
 *
 * See a more detailed explanation here:
 * https://github.com/PrismarineJS/mineflayer-pathfinder/blob/master/examples/tutorial/goalsExplained.md
 *
 * Made by Jovan04 06/07/2023
*/

const mineflayer = require('mineflayer') // import mineflayer, pathfinder, the Movements class, and our goal(s)
const { pathfinder, Movements, goals: { GoalBlock } } = require('mineflayer-pathfinder')

const bot = mineflayer.createBot({ // create our bot
  host: 'localhost',
  port: 25565,
  username: 'Pathfinder',
  auth: 'offline'
})

bot.once('spawn', () => {
  bot.loadPlugin(pathfinder) // load pathfinder plugin into the bot
  const defaultMovements = new Movements(bot) // create a new instance of the `Movements` class
  bot.pathfinder.setMovements(defaultMovements) // set the bot's movements to the `Movements` we just created
})

bot.on('chat', async (username, message) => {
  if (username === bot.username) return // make bot ignore its own messages

  if (message === 'go') { // this is our trigger message (only works on servers with vanilla chat)
    bot.chat('Going to my goal!')
    const myGoal = new GoalBlock(15, 3, 75)
    await bot.pathfinder.goto(myGoal)
    bot.chat('Arrived at my goal!')
  }
})


================================================
FILE: examples/tutorial/goalComposite.js
================================================
/*
 * This example shows the usage of the
 * GoalCompositeAny and GoalCompositeAll
 * goals for mineflayer-pathfinder
 *
 * See a more detailed explanation here:
 * https://github.com/PrismarineJS/mineflayer-pathfinder/blob/master/examples/tutorial/goalsExplained.md
 *
 * Made by Jovan04 06/07/2023
*/

// import mineflayer & related libraries
const mineflayer = require('mineflayer')
const { pathfinder, Movements, goals: { GoalNear, GoalCompositeAny, GoalCompositeAll } } = require('mineflayer-pathfinder')

// create mineflayer bot
const bot = mineflayer.createBot({
  host: 'localhost',
  port: 25565,
  version: '1.18.2',
  auth: 'offline',
  username: 'biffed'
})

// load pathfinder plugin and set our bot's Movements
bot.once('spawn', () => {
  bot.loadPlugin(pathfinder)
  bot.pathfinder.setMovements(new Movements(bot))
})

bot.on('chat', async (username, message) => {
  if (username === bot.username) return

  // create three separate GoalNear goals at different locations, with a range of 5; the bot needs to be within 5 blocks of a given goal to satisfy it
  const LapisGoal = new GoalNear(0, 1, 3, 5)
  const GoldGoal = new GoalNear(3, 1, -2, 5)
  const DiamondGoal = new GoalNear(-3, 1, -2, 5)

  const goalsArray = [LapisGoal, GoldGoal, DiamondGoal]

  if (message === 'GoalCompositeAny') {
    bot.chat('Traveling with GoalCompositeAny')
    // create a new GoalCompositeAny: see documentation for a more detailed explanation
    const goalAny = new GoalCompositeAny(goalsArray)
    // and travel to it
    await bot.pathfinder.goto(goalAny)
    bot.chat('Done traveling with GoalCompositeAny')
  }

  if (message === 'GoalCompositeAll') {
    bot.chat('Traveling with GoalCompositeAll')
    // create a new GoalCompositeAll: see documentation for a more detailed explanation
    const goalAll = new GoalCompositeAll(goalsArray)
    // and travel to it
    await bot.pathfinder.goto(goalAll)
    bot.chat('Done traveling with GoalCompositeAll')
  }
})


================================================
FILE: examples/tutorial/goalsExplained.md
================================================
<!-- Explanation of how to use goals in mineflayer-pathfinder. Made by Jovan04 06/07/2023 -->

# Mineflayer-Pathfinder: Goals
## A (more) detailed explanation by Jovan04

This page is an explanation about goals in mineflayer-pathfinder. A `Goal` is an instance of a class that allows the user to specify a location that they want a mineflayer bot to go to. Goals are the backbone of mineflayer because they provide an easy way to control your mineflayer bot with mineflayer-pathfinder.  

### General Goals
It's useful to think about goals in pathfinder as conditions that need to be fulfilled. For example, one of the most common goals is `GoalBlock`. As the [documentation](../../readme.md#goals) for the `GoalBlock` goal says:

> One specific block that the player should stand inside at foot level

Thus, we have our condition. In order to complete the `GoalBlock` goal, our bot needs to get its feet inside the specified block. Simple as that.  
That's great and all, but how do we use it?  
[Here's](./basic.js) a quick example. Let's walk through everything it does.

First, we need to import mineflayer, as well as pathfinder and things related to it:  
```js
const mineflayer = require('mineflayer')
const { pathfinder, Movements, goals:{ GoalBlock } } = require('mineflayer-pathfinder')
```
Here, we import a few things from pathfinder:
* the pathfinder plugin itself
* the Movements() class, which defines how our bot is allowed to move
* the `goals` object, and from it the `GoalBlock` goal  

Next, we'll create our bot. This is just like creating any other Mineflayer bot:  
```js
const bot = mineflayer.createBot({
  host: 'localhost',
  port: 25565,
  username: 'Pathfinder',
  auth: 'offline'
})
```

We'll also add a `spawn` event listener to load the pathfinder plugin and create the bot's Movements class:  
```js
bot.once('spawn', () => {
  bot.loadPlugin(pathfinder) // load pathfinder plugin into the bot
  const defaultMovements = new Movements(bot) // create a new instance of the `Movements` class
  bot.pathfinder.setMovements(defaultMovements) // set the bot's movements to the `Movements` we just created
})
```
The `Movements` class essentially tells the bot what moves it's allowed to make. This includes, but isn't limited to: What blocks (if any) it can place/break, whether it can pillar straight up, and how far it's allowed to fall.  
What we've done so far is mostly boilerplate; you'll probably use it every time you use pathfinder.  

Now, we'll create a chat listener that we'll use to make pathfinder move:  
```js
bot.on('chat', async (username, message) => {
  if (username === bot.username) return // make bot ignore its own messages

  if (message === 'go') { // this is our trigger message (only works on servers with vanilla chat)
    // our pathfinder code goes here!
  }
})
```

Now, let's go back to the `GoalBlock` goal. If we look at the documentation again, we can see that the GoalBlock takes three arguments: an `x` coordinate, a `y` coordinate, and a `z` coordinate, all integers. In order to use the `GoalBlock` goal, we would need to create a new instance of the `GoalBlock` class with those coordinates. If we wanted our bot to pathfind to the coordinates **15, 3, 75**, we could do that like this:
```js
const myGoal = new GoalBlock(15, 3, 75)
```

Now that we have our goal, how do we use it? Simple! Pathfinder has a method for traveling to a goal, `goto`. We can access it through `bot.pathfinder.goto(goal)`, and we'll use the `await` Javascript keyword to make our code wait for the bot to finish walking to the goal beore continuing:
```js
await bot.pathfinder.goto(myGoal)
```

We can also use `bot.chat` to have the bot tell us when it starts and finishes going to the goal. If we add that to the lines we just wrote, it can look something like this:
```js
bot.chat('Going to my goal!')
const myGoal = new GoalBlock(15, 3, 75)
await bot.pathfinder.goto(myGoal)
bot.chat('Arrived at my goal!')
```

Let's put those four lines inside our chat listener, likeso:
```js
bot.on('chat', async (username, message) => {
  if (username === bot.username) return // make bot ignore its own messages

  if (message === 'go') { // this is our trigger message (only works on servers with vanilla chat)
    bot.chat('Going to my goal!')
    const myGoal = new GoalBlock(15, 3, 75)
    await bot.pathfinder.goto(myGoal)
    bot.chat('Arrived at my goal!')
  }
})
```

And now we're done! We can type `go` in chat, and the bot will walk to the coordinates we specified. Remember that `GoalBlock` makes the bot put its feet in the block we specified. You can view and download the full example script [here](./basic.js).

And, there you have it. That's how you use the `GoalBlock` goal! Most of the other goals are used in a similar way, but with different arguments. You can look at [the documentation](../../readme.md#goals) for those. However, there are a few goals that are a little confusing.  

### Composite Goals
The Composite goals, `GoalCompositeAny` and `GoalCompositeAll`, are both quite different from most other goals. Instead of being standalone goals themselves, they allow you to combine other goals in interesting ways. They're called *compos*ite goals because they're *compos*ed of (or made up of) other goals. But before we can talk about the composite goals in more detail, we should talk about the `GoalNear` goal. The composite goals are made up of other goals, and `GoalNear` is a good example.  

`GoalNear` is almost the same as `GoalBlock`, but with one extra argument. Where `GoalBlock` only has `x, y, z`, `GoalNear` has `x, y, z, range`. The additional argument, `range`, specifies how far away from the target block the bot can be in order to still satisfy the goal. For example, the goal `GoalNear(15, 3, 75, 5)` would be satisfied once the bot is within 5 blocks of **15, 3, 75**.  

Now, let's set up our program to use composite goals.  

In the [composite goal example](./goalComposite.js), we make three goals: `LapisGoal`, `GoldGoal`, and `DiamondGoal`. They correspond to standing within 5 blocks of a Lapis block, a Gold block, and a Diamond block, respectively (see picture below):  
```js
  const LapisGoal = new GoalNear(0, 1, 3, 5) // our bot needs to stand within 5 blocks of the point (0, 1, 3) in order to satisfy this goal (blue circle below)
  const GoldGoal = new GoalNear(3, 1, -2, 5) // our bot needs to stand within 5 blocks of the point (3, 1, -2) in order to satisfy this goal (yellow circle below)
  const DiamondGoal = new GoalNear(-3, 1, -2, 5) // our bot needs to stand within 5 blocks of the point (-3, 1, -2) in order to satisfy this goal (white circle below)
```
![Diagram of the three goals](goalComposite-goals.png)

#### GoalCompositeAny
The first type of composite goal is called `GoalCompositeAny`. Being a composite goal, `GoalCompositeAny` is a goal made up of other goals. In order for your bot to complete `GoalCompositeAny`, it needs to satisfy *any one* of the contained goals. Above, we made three goals (`LapisGoal`, `GoldGoal`, and `DiamondGoal`).  
Let's put them into an array:  
```js
const goalsArray = [LapisGoal, GoldGoal, DiamondGoal] // array containing all three of our goals; we'll use this array in our `GoalCompositeAny` goal
```
And create a new `GoalCompositeAny` with that array:  
```js
const goalAny = new GoalCompositeAny(goalsArray)
```
Now, just like any other goal, we can tell pathfinder to `goto` our new `GoalCompositeAny`:  
```js
await bot.pathfinder.goto(goalAny)
```
The `GoalCompositeAny` is completed when the bot completes any one of the goals it was created with. For our example above, `goalAny` will be completed when the bot completes any one of `LapisGoal`, `GoldGoal`, or `DiamondGoal`. In other words, `goalAny` will be completed when the bot's location is inside the blue circle *or* the yellow circle *or* the white circle (see image above).  

#### GoalCompositeAll
The other type of composite goal is called `GoalCompositeAll`. Being a composite goal, `GoalCompositeAll` is a goal made up of other goals. In order for your bot to complete `GoalCompositeAll`, it needs to satisfy *all* of the contained goals. Above, we made three goals (`LapisGoal`, `GoldGoal`, and `DiamondGoal`).  
Let's put them into an array:  
```js
const goalsArray = [LapisGoal, GoldGoal, DiamondGoal] // array containing all three of our goals; we'll use this array in our `GoalCompositeAll` goal
```
And create a new `GoalCompositeAll` with that array:  
```js
const goalAll = new GoalCompositeAll(goalsArray)
```
Now, just like any other goal, we can tell pathfinder to `goto` our new `GoalCompositeAll`:  
```js
await bot.pathfinder.goto(goalAll)
```
The `GoalCompositeAll` is completed when the bot completes all of the goals it was created with. For our example above, `goalAll` will be completed when the bot completes all of `LapisGoal`, `GoldGoal`, or `DiamondGoal`. In other words, `goalAll` will be completed when the bot's location is inside the blue circle *and* the yellow circle *and* the white circle (see image above). It's important to clarify that the bot needs to be in all three circles at the same time. Going from one circle to another to the last is not a valid path to complete the goal.  

And there you have it! That's a basic introduction to using goals in mineflayer-pathfinder. If any of this was confusing, or you'd like help with something more complicated, feel free to join the [PrismarineJS Discord server](https://discord.gg/GsEFRM8). We're always happy to provide help for mineflayer and other PrismarineJS libraries.


================================================
FILE: history.md
================================================
# History

# 2.4.5
* [Fix block update resets for optimized paths (@m000z0rz)](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/330)
* [reword .stop() for more clarity (@Jovan-04)](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/327)
* [add more detailed tutorial for mineflayer-pathfinder goals (@Jovan-04)](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/326)
* [Fixed typo: "physicsTick" (@FreezeEngine)](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/324)

# 2.4.4
* [Update readme.md (@Vinciepincie)](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/320)
* Fix file linting (@IceTank)
* Fix possible reference error for block updates (@IceTank)
* [Fix a bunch of spelling/grammar errors (@182exe)](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/316)

# 2.4.3
* [Change canOpenDoors default value to false because its buggy (@IceTank)](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/318)
* [Added missing constructor definitions for GoalCompositeAny and GoalCompositeAll (@rutexd)](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/315)
* [Added "sneak" on interact blocks to avoid open it (@sefirosweb)](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/314)
* [Block face position fix (@WhoTho)](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/312)
* [Remove mcData param in movements.js (@rtm516)](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/311)

# 2.4.2
* Fix pathfinder trying to make a parkour jump that fails most times
* Fix pathfinder not going below level 0
* [Fix wheat not being break able (@maximmasiutin)](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/303)
* [Add parameter typing to Composite goals (@Ic3Tank)](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/302)
* [Fix GoalLookAtBlock documentation (@Ic3Tank)](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/301)

# 2.4.1
* [Made some Goal methods none abstract (@IceTank)](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/298)

# 2.4.0
* [mcData to registry refactoring (@Epirito)](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/293)

# 2.3.3
* [Add missing types on GoalPlaceBlock and GoalLookAtBlock (@IceTank)](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/289)

# 2.3.2
* Revert broken goto implementation update.

# 2.3.1
* Fix reference error in `getNumEntitiesAt` (https://github.com/PrismarineJS/mineflayer-pathfinder/commit/2e7b3daff2ee5fa0aaf52db4553f769189b8d03f)

# 2.3.0
* [Add entity Avoidance Feature](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/281)
* [Fix bugs in movements.js](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/276)
* [Update Dependencies](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/277)
* [Fix issue with starting paths](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/280)

# 2.2.0
* [Add events typings](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/266)
* [Force look at blocks when breaking them](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/274)
* [Fix bot sometimes not triggering path end correctly](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/268)
* [Fix missing null check at block update](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/269)
* [Bump mocha to 10.x](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/265)

# 2.1.1
* Fix GitHub action for publishing

# 2.1.0
* [Add automated tests](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/262)
* [Add getPathFromTo function](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/255)
* [Fix path optimization check](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/254)
* [Bumb minecraft data to version 3.x](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/262)
* [Add goal chaining example](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/256)

# 2.0.0
* [Remove callbacks](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/248)
* [Export GoalLookAtBlock and deprecate GoalBreakBlock](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/249)

# 1.10.0

* [Add exclusion area mechanism](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/220)
* [Add movement class example](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/247)
* [Add infiniteLiquidDropdownDistance to movements](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/211)
* [Added dontMineUnderFallingBlock to movements](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/239/files)
* [Add ability to open fence gates](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/217)

* [Bump mineflayer to 4.0.0](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/245)
* [Throw error in goto when stop() is called](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/240)
* [Update README.md](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/246)

* Typing fixes:
  * [tickTimeout](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/241)
  * [GoalLookAtBlock](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/238)

* [Fix dynamic goals with entities](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/206)
* [Fix default scaffolding blocks](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/243)
* [Fix event handler when stop() is called](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/184)

# 1.9.1

* [Fixed unhandled promise rejection introduced in 1.0.0](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/235#event-5854609665)

# 1.9.0

* [Fixed floor check](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/208)
* [Avoid cobwebs](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/210)
* [Fixed diagonal move not considering collision height when jumping up diagonally](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/216)
* [Fixed movements for older versions](https://github.com/PrismarineJS/mineflayer-pathfinder/pull/226)

# 1.8.0

* Fixed placeBlock example
* Fixed Readme typos
* Fixed bot placing wrong blocks as scaffolding
* Fixed GoalNearXZ
* Fixed typings
* Fixed index.d.ts compile errors

# 1.7.0

* Add GoalNearXZ
* Improve docs

# 1.6.3

* Add setGoal null to typings
* Add safeOrBreak check to getMoveDiagonal
* Fix reference to LOSWhenPlacingBlocks
* Fixed raycasting not considering block face
* Add GoalPlaceBlock typing
* Add placeBlock.js example
* Add callback.js and promise.js example
* Fix reference error in GoalPlaceBlock
* Function to stop path finding when safe

# 1.6.2

* Fix swimming in lava
* Fix TypeScript headers
* Add +1 to movement cost when going forward and up 1 block
* Fix bot trying to go underwater
* Add `path_reset` event

# 1.6.1

* Add option to limit search range
* Expose tickTimeout

# 1.6.0

* Add GoalPlaceBlock
* Fix various parkour moves
* Fix goto

# 1.5.0

* Improve diagonal movements (add up/down)
* Expose A* context in result
* Fix fences
* Fix carpets

# 1.4.0

* Legit bot bridging (with sneak)
* Fixed bug for detect when mining is stopped correctly
* Fix GoalGetToBlock

## 1.3.7

* Promisified goto

## 1.3.0

* Add ladder support
* Add ability to drop in water from high places
* Improve movement execution

## 1.2.0

* Use physics to predict motion and choose best controls
* Add more parkour moves
* Sprint and sprint-jump over gaps

## 1.1.2

* Set every non diggable block automatically from mcdata
* Fix jumps in snow

## 1.1.1

* Fix 1x1 towering
* Fix path starting on shorter blocks

## 1.1.0

* Fixed crash with null positions
* API in the readme
* Expose movements and goal

## 1.0.12

* Added `canDig` movements variable
* Added `goto(goal, cb)` function

## 1.0.11

* Added `goal_updated` event
* Movements are now initialized by default
* Fixed Typescript headers
* Fixed bugs with block height when jumping

## 1.0.10

* Fixed "cannot read property 'shapes' of null" bug
* Exposed `thinkTimeout` pathfinder variable

## 1.0.9

* Added simple postprocessing fallback for unsuitable positions

## 1.0.8

* Fixed null pointer exception for "getPositionOnTopOf"

## 1.0.7

* Improved post processing for standing on more block types
* Improved tool selection when breaking blocks
* Retrieve player state from Prismarine-Physics
* Fixed bug with parkour cooldown
* Added Typescript headers
* Fixed bug with clearing controls while recalculating path
* Removed path recalculation detection radius

## 1.0.6

* Added basic parkour movements
* Movement nodes are now stored as classes
* Astar algorithm is now stored as a class
* Improved blocks-to-break estimation in the path
* Fixed 12.x Node.js compatibility in example bot

## 1.0.5

* Added multiple bot example
* Added experimental "free-motion"
* Added composite goal
* Added `isMoving()` function
* Added `isMining()` function
* Added `isBuilding()` function
* Added `isThinking()` function

## 1.0.4

* Paths are now recalculated on chunk loading to fix long paths
* Minor bug fixes
* Moved scaffolding blocks from index.js to movements.js internally
* Updated readme todo list
* Added 1x1 tower creation

## 1.0.3

* Fixed `goal_reached` not being called if bot is already at the goal
* Control state is cleared when path is reset
* Fixed example bot code in readme
* Improved readme
* Fixed bug with place/dig logic
* Added swimming support

## 1.0.2

* Exposed goals and movements classes
* Fixed bugs with bot stopping incorrectly
* Improved readme
* Added performance benchmarks
* Added build CI support

## 1.0.1

* Added deployment CI support
* Added standard
* Fixed bug with not canceling digging when resetting path
* Fixed undefined pos error
* Added configurable fall height
* Added dynamic goals
* Added automatic path recalculation
* Added 1x1 digging holes
* Added more movement abilities
* Added internal scaffolding block count

## 1.0.0

* Initial release


================================================
FILE: index.d.ts
================================================
import { Bot } from 'mineflayer';
import { IndexedData } from 'minecraft-data';
import { Item } from 'prismarine-item';
import { Vec3 } from 'vec3';
import { Block } from 'prismarine-block';
import { Entity } from 'prismarine-entity';
import { World } from 'prismarine-world'
import AStar from './lib/astar';

declare module 'mineflayer-pathfinder' {
	export function pathfinder(bot: Bot): void;

	export interface Pathfinder {
		thinkTimeout: number;
		/** ms, amount of thinking per tick (max 50 ms) */
		tickTimeout: number;
		readonly goal: goals.Goal | null;
		readonly movements: Movements;

		bestHarvestTool(block: Block): Item | null;
		getPathTo(
			movements: Movements,
			goal: goals.Goal,
			timeout?: number
		): ComputedPath;
		getPathFromTo(
			movements: Movements,
			startPos: Vec3 | null, 
			goal: goals.Goal, 
			options?: {
				optimizePath?: boolean,
				resetEntityIntersects?: boolean,
				timeout?: number,
				tickTimeout?: number,
				searchRadius?: number,
				startMove?: Move
			}
		): IterableIterator<{ result: ComputedPath, astarContext: AStar }>

		setGoal(goal: goals.Goal | null, dynamic?: boolean): void;
		setMovements(movements: Movements): void;
		goto(goal: goals.Goal, callback?: Callback): Promise<void>;
		stop(): void;

		isMoving(): boolean;
		isMining(): boolean;
		isBuilding(): boolean;
	}

	export namespace goals {
		export abstract class Goal {
			public abstract heuristic(node: Move): number;
			public abstract isEnd(node: Move): boolean;
			public hasChanged(): boolean;
			public isValid(): boolean;
		}

		export class GoalBlock extends Goal {
			public constructor(x: number, y: number, z: number);

			public x: number;
			public y: number;
			public z: number;

			public heuristic(node: Move): number;
			public isEnd(node: Move): boolean;
			public hasChanged(): boolean;
		}

		export class GoalNear extends Goal {
			public constructor(x: number, y: number, z: number, range: number);

			public x: number;
			public y: number;
			public z: number;
			public rangeSq: number;

			public heuristic(node: Move): number;
			public isEnd(node: Move): boolean;
			public hasChanged(): boolean;
		}

		export class GoalXZ extends Goal {
			public constructor(x: number, z: number);

			public x: number;
			public z: number;

			public heuristic(node: Move): number;
			public isEnd(node: Move): boolean;
			public hasChanged(): boolean;
		}

		export class GoalNearXZ extends Goal {
			public constructor(x: number, z: number, range: number);

			public x: number;
			public z: number;
			public rangeSq: number;

			public heuristic(node: Move): number;
			public isEnd(node: Move): boolean;
			public hasChanged(): boolean;
		}

		export class GoalY extends Goal {
			public constructor(y: number);

			public y: number;

			public heuristic(node: Move): number;
			public isEnd(node: Move): boolean;
			public hasChanged(): boolean;
		}

		export class GoalGetToBlock extends Goal {
			public constructor(x: number, y: number, z: number);

			public x: number;
			public y: number;
			public z: number;

			public heuristic(node: Move): number;
			public isEnd(node: Move): boolean;
			public hasChanged(): boolean;
		}

		export class GoalCompositeAny<T extends Goal> extends Goal {
			public constructor(goals: T[] = []);
			public goals: T[];
			
			public push(goal: Goal): void;
			public heuristic(node: Move): number;
			public isEnd(node: Move): boolean;
			public hasChanged(): boolean;
		}

		export class GoalCompositeAll<T extends Goal> extends Goal {
			public constructor(goals: T[] = []);
			public goals: T[];

			public push(goal: Goal): void;
			public heuristic(node: Move): number;
			public isEnd(node: Move): boolean;
			public hasChanged(): boolean;
		}

		export class GoalInvert extends Goal {
			public constructor(goal: Goal);
			
			public goal: Goal;

			public heuristic(node: Move): number;
			public isEnd(node: Move): boolean;
			public hasChanged(): boolean;
		}

		export class GoalFollow extends Goal {
			public constructor(entity: Entity, range: number);

			public x: number;
			public y: number;
			public z: number;
			public entity: Entity;
			public rangeSq: number;

			public heuristic(node: Move): number;
			public isEnd(node: Move): boolean;
			public hasChanged(): boolean;
		}

		export class GoalPlaceBlock extends Goal {
			public options: {
				range: number;
				LOS: boolean;
				faces: [Vec3, Vec3, Vec3, Vec3, Vec3, Vec3];
				facing: number;
				half: boolean;
			}
			public heuristic(node: Move): number;
			public isEnd(node: Move): boolean;
			public hasChanged(): boolean;
			public constructor(pos: Vec3, world: World, options: GoalPlaceBlockOptions)
		}
		
		export class GoalLookAtBlock  extends Goal {
			public constructor(pos: Vec3, world: World, options?: { reach?: number, entityHeight?: number })
			
			public pos: Vec3;
			public reach: number;
			public entityHeight: number;
			public world: World;

			public heuristic(node: Move): number;
			public isEnd(node: Move): boolean;
			public hasChanged(): boolean;
		}

		export class GoalBreakBlock extends GoalLookAtBlock {}
	}

	export class Movements {
		public constructor(bot: Bot);

		public bot: Bot;

		public canDig: boolean;
		public canOpenDoors: boolean;
		public dontCreateFlow: boolean;
		public dontMineUnderFallingBlock: boolean;
		public allow1by1towers: boolean;
		public allowFreeMotion: boolean;
		public allowParkour: boolean;
		public allowSprinting: boolean;
 		/**
 		 * Test for entities that may obstruct path or prevent block placement. Grabs updated entities every new path
 		 */
 		public allowEntityDetection: boolean;
		
		/**
		 * Set of entities (by mcdata name) to completely avoid when using entity detection
		 */
		public entitiesToAvoid: Set<string>;
		/**
		 * Set of entities (by mcdata name) to ignore when using entity detection
		 */
		public passableEntities: Set<string>;
		/**
		 * Set of blocks (by mcdata name) that pathfinder should not attempt to place blocks or 'right click' on
		 */
		public interactableBlocks: Set<string>;
		public blocksCantBreak: Set<number>;
		public blocksToAvoid: Set<number>;
		public liquids: Set<number>;
		public gravityBlocks: Set<number>;
		public climbables: Set<number>
		public emptyBlocks: Set<number>
		public replaceables: Set<number>
		public fences: Set<number>
		public carpets: Set<number>
		public openable: Set<number>

		public scafoldingBlocks: number[];

		public maxDropDown: number;
		public infiniteLiquidDropdownDistance: boolean;
		public digCost: number;
		public placeCost: number;
 		/**
 		 * Extra cost multiplier for moving through an entity hitbox (besides passable ones).
 		 */
 		public entityCost: number;

		/** Exclusion Area that adds extra cost or prevents the bot from stepping onto positions included.
		 * @example
		 * ```js
			movements.exclusionAreas = [(block) => {
				return block.type === someIdType ? 100 : 0 // Prevents the bot from breaking a specific block. By adding 100 to the cost.
			},
			(block) => {
				return someVec3Pos.distanceTo(block.position) < 5 ? 100 : 0 // Prevents the bot from getting near to a specific location
			}]
			``` */
		public exclusionAreasStep: [(block: SafeBlock) => number];
		/**
		 * Exclusion area for blocks to break. Works in the same way as {@link exclusionAreasStep} does. 
		 */
		public exclusionAreasBreak: [(block: SafeBlock) => number];
		/**
		 * Exclusion area for placing blocks. Note only works for positions not block values as placed blocks are determined by the bots inventory content. Works in the same way as {@link exclusionAreasStep} does. 
		 */
		public exclusionAreasPlace: [(block: SafeBlock) => number];
        
 		/**
 		 * A dictionary of the number of entities intersecting each floored block coordinate.
 		 * Updated automatically each path but, you may mix in your own entries before calculating a path if desired (generally for testing).
 		 * To prevent this from being cleared automatically before generating a path see getPathFromTo()
 		 * formatted entityIntersections['x,y,z'] = #ents
 		 */
		public entityIntersections: {string: number};

		public exclusionPlace(block: SafeBlock): number;
		public exclusionStep(block: SafeBlock): number;
		public exclusionBreak(block: SafeBlock): number;
		public countScaffoldingItems(): number;
		public getScaffoldingItem(): Item | null;
		public clearCollisionIndex(): void;
		/**
		 * Finds blocks intersected by entity bounding boxes
		 * and sets the number of ents intersecting in a dict.
		 * Ignores entities that do not affect block placement
		 */
		public updateCollisionIndex(): void;
		/**
		 * Gets number of entities who's bounding box intersects the node + offset
		 * @param {import('vec3').Vec3} pos node position
		 * @param {number} dx X axis offset
		 * @param {number} dy Y axis offset
		 * @param {number} dz Z axis offset
		 * @returns {number} Number of entities intersecting block
		 */
		public getNumEntitiesAt(pos: Vec3, dx: number, dy: number, dz: number): number;
		public getBlock(pos: Vec3, dx: number, dy: number, dz: number): SafeBlock;
		public safeToBreak(block: SafeBlock): boolean;
		public safeOrBreak(block: SafeBlock): number;
		public getMoveJumpUp(node: Move, dir: XZCoordinates, neighbors: Move[]): void;
		public getMoveForward(node: Move, dir: XZCoordinates, neighbors: Move[]): void;
		public getMoveDiagonal(node: Move, dir: XZCoordinates, neighbors: Move[]): void;
		public getMoveDropDown(node: Move, dir: XZCoordinates, neighbors: Move[]): void;
		public getMoveParkourForward(node: Move, dir: XZCoordinates, neighbors: Move[]): void;
		public getMoveJumpUp(node: Move, dir: XZCoordinates, neighbors: Move[]): void;
		public getMoveUp(node: Move, neighbors: Move[]): void;
		public getMoveDown(node: Move, neighbors: Move[]): void;
		public getLandingBlock(node: Move, dir: XZCoordinates): SafeBlock;
		public getNeighbors(node: Move): Move[];
	}

	// this is a class, but its not exported so we use an interface
	export interface Move extends XYZCoordinates {
		remainingBlocks: number;
		cost: number;
		toBreak: Move[];
		toPlace: Move[];
		parkour: boolean;
		hash: string;
	}

	type Callback = (error?: Error) => void;

	interface PathBase {
		cost: number;
		time: number;
		visitedNodes: number;
		generatedNodes: number;
		path: Move[];
	}

	export interface ComputedPath extends PathBase {
		status: 'noPath' | 'timeout' | 'success';
	}

	export interface PartiallyComputedPath extends PathBase {
		status: 'noPath' | 'timeout' | 'success' | 'partial';
	}

	export interface XZCoordinates {
		x: number;
		z: number;
	}

	export interface XYZCoordinates extends XZCoordinates {
		y: number;
	}

	export interface SafeBlock extends Block {
		safe: boolean;
		physical: boolean;
		liquid: boolean;
		height: number;
		replaceable: boolean;
		climbable: boolean;
		openable: boolean;
	}

	export interface GoalPlaceBlockOptions {
		range: number;
		LOS: boolean;
		faces: Vec3[];
		facing: 'north' | 'east' | 'south' | 'west' | 'up' | 'down';
	}
}

declare module 'mineflayer' {
	interface BotEvents {
		goal_reached: (goal: Goal) => void;
		path_update: (path: PartiallyComputedPath) => void;
		goal_updated: (goal: Goal, dynamic: boolean) => void;
		path_reset: (
			reason: 'goal_updated' | 'movements_updated' |
				'block_updated' | 'chunk_loaded' | 'goal_moved' | 'dig_error' |
				'no_scaffolding_blocks' | 'place_error' | 'stuck'
		) => void;
		path_stop: () => void;
	}

	interface Bot {
		pathfinder: Pathfinder
	}
}


================================================
FILE: index.js
================================================
const { performance } = require('perf_hooks')

const AStar = require('./lib/astar')
const Move = require('./lib/move')
const Movements = require('./lib/movements')
const gotoUtil = require('./lib/goto')
const Lock = require('./lib/lock')

const Vec3 = require('vec3').Vec3

const Physics = require('./lib/physics')
const nbt = require('prismarine-nbt')
const interactableBlocks = require('./lib/interactable.json')

function inject (bot) {
  const waterType = bot.registry.blocksByName.water.id
  const ladderId = bot.registry.blocksByName.ladder.id
  const vineId = bot.registry.blocksByName.vine.id
  let stateMovements = new Movements(bot)
  let stateGoal = null
  let astarContext = null
  let astartTimedout = false
  let dynamicGoal = false
  let path = []
  let pathUpdated = false
  let digging = false
  let placing = false
  let placingBlock = null
  let lastNodeTime = performance.now()
  let returningPos = null
  let stopPathing = false
  const physics = new Physics(bot)
  const lockPlaceBlock = new Lock()
  const lockEquipItem = new Lock()
  const lockUseBlock = new Lock()

  bot.pathfinder = {}

  bot.pathfinder.thinkTimeout = 5000 // ms
  bot.pathfinder.tickTimeout = 40 // ms, amount of thinking per tick (max 50 ms)
  bot.pathfinder.searchRadius = -1 // in blocks, limits of the search area, -1: don't limit the search
  bot.pathfinder.enablePathShortcut = false // disabled by default as it can cause bugs in specific configurations
  bot.pathfinder.LOSWhenPlacingBlocks = true

  bot.pathfinder.bestHarvestTool = (block) => {
    const availableTools = bot.inventory.items()
    const effects = bot.entity.effects

    let fastest = Number.MAX_VALUE
    let bestTool = null
    for (const tool of availableTools) {
      const enchants = (tool && tool.nbt) ? nbt.simplify(tool.nbt).Enchantments : []
      const digTime = block.digTime(tool ? tool.type : null, false, false, false, enchants, effects)
      if (digTime < fastest) {
        fastest = digTime
        bestTool = tool
      }
    }

    return bestTool
  }

  bot.pathfinder.getPathTo = (movements, goal, timeout) => {
    const generator = bot.pathfinder.getPathFromTo(movements, bot.entity.position, goal, { timeout })
    const { value: { result, astarContext: context } } = generator.next()
    astarContext = context
    return result
  }

  bot.pathfinder.getPathFromTo = function * (movements, startPos, goal, options = {}) {
    const optimizePath = options.optimizePath ?? true
    const resetEntityIntersects = options.resetEntityIntersects ?? true
    const timeout = options.timeout ?? bot.pathfinder.thinkTimeout
    const tickTimeout = options.tickTimeout ?? bot.pathfinder.tickTimeout
    const searchRadius = options.searchRadius ?? bot.pathfinder.searchRadius
    let start
    if (options.startMove) {
      start = options.startMove
    } else {
      const p = startPos.floored()
      const dy = startPos.y - p.y
      const b = bot.blockAt(p) // The block we are standing in
      // Offset the floored bot position by one if we are standing on a block that has not the full height but is solid
      const offset = (b && dy > 0.001 && bot.entity.onGround && !stateMovements.emptyBlocks.has(b.type)) ? 1 : 0
      start = new Move(p.x, p.y + offset, p.z, movements.countScaffoldingItems(), 0)
    }
    if (movements.allowEntityDetection) {
      if (resetEntityIntersects) {
        movements.clearCollisionIndex()
      }
      movements.updateCollisionIndex()
    }
    const astarContext = new AStar(start, movements, goal, timeout, tickTimeout, searchRadius)
    let result = astarContext.compute()
    if (optimizePath) result.path = postProcessPath(result.path)
    yield { result, astarContext }
    while (result.status === 'partial') {
      result = astarContext.compute()
      if (optimizePath) result.path = postProcessPath(result.path)
      yield { result, astarContext }
    }
  }

  Object.defineProperties(bot.pathfinder, {
    goal: {
      get () {
        return stateGoal
      }
    },
    movements: {
      get () {
        return stateMovements
      }
    }
  })

  function detectDiggingStopped () {
    digging = false
    bot.removeAllListeners('diggingAborted', detectDiggingStopped)
    bot.removeAllListeners('diggingCompleted', detectDiggingStopped)
  }

  function resetPath (reason, clearStates = true) {
    if (!stopPathing && path.length > 0) bot.emit('path_reset', reason)
    path = []
    if (digging) {
      bot.on('diggingAborted', detectDiggingStopped)
      bot.on('diggingCompleted', detectDiggingStopped)
      bot.stopDigging()
    }
    placing = false
    pathUpdated = false
    astarContext = null
    lockEquipItem.release()
    lockPlaceBlock.release()
    lockUseBlock.release()
    stateMovements.clearCollisionIndex()
    if (clearStates) bot.clearControlStates()
    if (stopPathing) return stop()
  }

  bot.pathfinder.setGoal = (goal, dynamic = false) => {
    stateGoal = goal
    dynamicGoal = dynamic
    bot.emit('goal_updated', goal, dynamic)
    resetPath('goal_updated')
  }

  bot.pathfinder.setMovements = (movements) => {
    stateMovements = movements
    resetPath('movements_updated')
  }

  bot.pathfinder.isMoving = () => path.length > 0
  bot.pathfinder.isMining = () => digging
  bot.pathfinder.isBuilding = () => placing

  bot.pathfinder.goto = (goal) => {
    return gotoUtil(bot, goal)
  }

  bot.pathfinder.stop = () => {
    stopPathing = true
  }

  bot.on('physicsTick', monitorMovement)

  function postProcessPath (path) {
    for (let i = 0; i < path.length; i++) {
      const curPoint = path[i]
      if (curPoint.toBreak.length > 0 || curPoint.toPlace.length > 0) break
      const b = bot.blockAt(new Vec3(curPoint.x, curPoint.y, curPoint.z))
      if (b && (b.type === waterType || ((b.type === ladderId || b.type === vineId) && i + 1 < path.length && path[i + 1].y < curPoint.y))) {
        curPoint.x = Math.floor(curPoint.x) + 0.5
        curPoint.y = Math.floor(curPoint.y)
        curPoint.z = Math.floor(curPoint.z) + 0.5
        continue
      }
      let np = getPositionOnTopOf(b)
      if (np === null) np = getPositionOnTopOf(bot.blockAt(new Vec3(curPoint.x, curPoint.y - 1, curPoint.z)))
      if (np) {
        curPoint.x = np.x
        curPoint.y = np.y
        curPoint.z = np.z
      } else {
        curPoint.x = Math.floor(curPoint.x) + 0.5
        curPoint.y = curPoint.y - 1
        curPoint.z = Math.floor(curPoint.z) + 0.5
      }
    }

    if (!bot.pathfinder.enablePathShortcut || stateMovements.exclusionAreasStep.length !== 0 || path.length === 0) return path

    const newPath = []
    let lastNode = bot.entity.position
    for (let i = 1; i < path.length; i++) {
      const node = path[i]
      if (Math.abs(node.y - lastNode.y) > 0.5 || node.toBreak.length > 0 || node.toPlace.length > 0 || !physics.canStraightLineBetween(lastNode, node)) {
        newPath.push(path[i - 1])
        lastNode = path[i - 1]
      }
    }
    newPath.push(path[path.length - 1])
    return newPath
  }

  function pathFromPlayer (path) {
    if (path.length === 0) return
    let minI = 0
    let minDistance = 1000
    for (let i = 0; i < path.length; i++) {
      const node = path[i]
      if (node.toBreak.length !== 0 || node.toPlace.length !== 0) break
      const dist = bot.entity.position.distanceSquared(node)
      if (dist < minDistance) {
        minDistance = dist
        minI = i
      }
    }
    // check if we are between 2 nodes
    const n1 = path[minI]
    // check if node already reached
    const dx = n1.x - bot.entity.position.x
    const dy = n1.y - bot.entity.position.y
    const dz = n1.z - bot.entity.position.z
    const reached = Math.abs(dx) <= 0.35 && Math.abs(dz) <= 0.35 && Math.abs(dy) < 1
    if (minI + 1 < path.length && n1.toBreak.length === 0 && n1.toPlace.length === 0) {
      const n2 = path[minI + 1]
      const d2 = bot.entity.position.distanceSquared(n2)
      const d12 = n1.distanceSquared(n2)
      minI += d12 > d2 || reached ? 1 : 0
    }

    path.splice(0, minI)
  }

  function isPositionNearPath (pos, path) {
    let prevNode = null
    for (const node of path) {
      let comparisonPoint = null
      if (
        prevNode === null ||
        (
          Math.abs(prevNode.x - node.x) <= 2 &&
          Math.abs(prevNode.y - node.y) <= 2 &&
          Math.abs(prevNode.z - node.z) <= 2
        )
      ) {
        // Unoptimized path, or close enough to last point
        // to just check against the current point
        comparisonPoint = node
      } else {
        // Optimized path - the points are far enough apart
        //   that we need to check the space between them too

        // First, a quick check - if point it outside the path
        // segment's AABB, then it isn't near.
        const minBound = prevNode.min(node)
        const maxBound = prevNode.max(node)
        if (
          pos.x - 0.5 < minBound.x - 1 ||
          pos.x - 0.5 > maxBound.x + 1 ||
          pos.y - 0.5 < minBound.y - 2 ||
          pos.y - 0.5 > maxBound.y + 2 ||
          pos.z - 0.5 < minBound.z - 1 ||
          pos.z - 0.5 > maxBound.z + 1
        ) {
          continue
        }

        comparisonPoint = closestPointOnLineSegment(pos, prevNode, node)
      }

      const dx = Math.abs(comparisonPoint.x - pos.x - 0.5)
      const dy = Math.abs(comparisonPoint.y - pos.y - 0.5)
      const dz = Math.abs(comparisonPoint.z - pos.z - 0.5)
      if (dx <= 1 && dy <= 2 && dz <= 1) return true

      prevNode = node
    }

    return false
  }

  function closestPointOnLineSegment (point, segmentStart, segmentEnd) {
    const segmentLength = segmentEnd.minus(segmentStart).norm()

    if (segmentLength === 0) {
      return segmentStart
    }

    // t is like an interpolation from segmentStart to segmentEnd
    //  for the closest point on the line
    let t = (point.minus(segmentStart)).dot(segmentEnd.minus(segmentStart)) / segmentLength

    // bound t to be on the segment
    t = Math.max(0, Math.min(1, t))

    return segmentStart.plus(segmentEnd.minus(segmentStart).scaled(t))
  }

  // Return the average x/z position of the highest standing positions
  // in the block.
  function getPositionOnTopOf (block) {
    if (!block || block.shapes.length === 0) return null
    const p = new Vec3(0.5, 0, 0.5)
    let n = 1
    for (const shape of block.shapes) {
      const h = shape[4]
      if (h === p.y) {
        p.x += (shape[0] + shape[3]) / 2
        p.z += (shape[2] + shape[5]) / 2
        n++
      } else if (h > p.y) {
        n = 2
        p.x = 0.5 + (shape[0] + shape[3]) / 2
        p.y = h
        p.z = 0.5 + (shape[2] + shape[5]) / 2
      }
    }
    p.x /= n
    p.z /= n
    return block.position.plus(p)
  }

  /**
   * Stop the bot's movement and recenter to the center off the block when the bot's hitbox is partially beyond the
   * current blocks dimensions.
   */
  function fullStop () {
    bot.clearControlStates()

    // Force horizontal velocity to 0 (otherwise inertia can move us too far)
    // Kind of cheaty, but the server will not tell the difference
    bot.entity.velocity.x = 0
    bot.entity.velocity.z = 0

    const blockX = Math.floor(bot.entity.position.x) + 0.5
    const blockZ = Math.floor(bot.entity.position.z) + 0.5

    // Make sure our bounding box don't collide with neighboring blocks
    // otherwise recenter the position
    if (Math.abs(bot.entity.position.x - blockX) > 0.2) { bot.entity.position.x = blockX }
    if (Math.abs(bot.entity.position.z - blockZ) > 0.2) { bot.entity.position.z = blockZ }
  }

  function moveToEdge (refBlock, edge) {
    // If allowed turn instantly should maybe be a bot option
    const allowInstantTurn = false
    function getViewVector (pitch, yaw) {
      const csPitch = Math.cos(pitch)
      const snPitch = Math.sin(pitch)
      const csYaw = Math.cos(yaw)
      const snYaw = Math.sin(yaw)
      return new Vec3(-snYaw * csPitch, snPitch, -csYaw * csPitch)
    }
    // Target viewing direction while approaching edge
    // The Bot approaches the edge while looking in the opposite direction from where it needs to go
    // The target Pitch angle is roughly the angle the bot has to look down for when it is in the position
    // to place the next block
    const targetBlockPos = refBlock.offset(edge.x + 0.5, edge.y, edge.z + 0.5)
    const targetPosDelta = bot.entity.position.clone().subtract(targetBlockPos)
    const targetYaw = Math.atan2(-targetPosDelta.x, -targetPosDelta.z)
    const targetPitch = -1.421
    const viewVector = getViewVector(targetPitch, targetYaw)
    // While the bot is not in the right position rotate the view and press back while crouching
    if (bot.entity.position.distanceTo(refBlock.clone().offset(edge.x + 0.5, 1, edge.z + 0.5)) > 0.4) {
      bot.lookAt(bot.entity.position.offset(viewVector.x, viewVector.y, viewVector.z), allowInstantTurn)
      bot.setControlState('sneak', true)
      bot.setControlState('back', true)
      return false
    }
    bot.setControlState('back', false)
    return true
  }

  function moveToBlock (pos) {
    // minDistanceSq = Min distance sqrt to the target pos were the bot is centered enough to place blocks around him
    const minDistanceSq = 0.2 * 0.2
    const targetPos = pos.clone().offset(0.5, 0, 0.5)
    if (bot.entity.position.distanceSquared(targetPos) > minDistanceSq) {
      bot.lookAt(targetPos)
      bot.setControlState('forward', true)
      return false
    }
    bot.setControlState('forward', false)
    return true
  }

  function stop () {
    stopPathing = false
    stateGoal = null
    path = []
    bot.emit('path_stop')
    fullStop()
  }

  bot.on('blockUpdate', (oldBlock, newBlock) => {
    if (!oldBlock || !newBlock) return
    if (isPositionNearPath(oldBlock.position, path) && oldBlock.type !== newBlock.type) {
      resetPath('block_updated', false)
    }
  })

  bot.on('chunkColumnLoad', (chunk) => {
    // Reset only if the new chunk is adjacent to a visited chunk
    if (astarContext) {
      const cx = chunk.x >> 4
      const cz = chunk.z >> 4
      if (astarContext.visitedChunks.has(`${cx - 1},${cz}`) ||
          astarContext.visitedChunks.has(`${cx},${cz - 1}`) ||
          astarContext.visitedChunks.has(`${cx + 1},${cz}`) ||
          astarContext.visitedChunks.has(`${cx},${cz + 1}`)) {
        resetPath('chunk_loaded', false)
      }
    }
  })

  function monitorMovement () {
    // Test freemotion
    if (stateMovements && stateMovements.allowFreeMotion && stateGoal && stateGoal.entity) {
      const target = stateGoal.entity
      if (physics.canStraightLine([target.position])) {
        bot.lookAt(target.position.offset(0, 1.6, 0))

        if (target.position.distanceSquared(bot.entity.position) > stateGoal.rangeSq) {
          bot.setControlState('forward', true)
        } else {
          bot.clearControlStates()
        }
        return
      }
    }
    if (stateGoal) {
      if (!stateGoal.isValid()) {
        stop()
      } else if (stateGoal.hasChanged()) {
        resetPath('goal_moved', false)
      }
    }

    if (astarContext && astartTimedout) {
      const results = astarContext.compute()
      results.path = postProcessPath(results.path)
      pathFromPlayer(results.path)
      bot.emit('path_update', results)
      path = results.path
      astartTimedout = results.status === 'partial'
    }

    if (bot.pathfinder.LOSWhenPlacingBlocks && returningPos) {
      if (!moveToBlock(returningPos)) return
      returningPos = null
    }

    if (path.length === 0) {
      lastNodeTime = performance.now()
      if (stateGoal && stateMovements) {
        if (stateGoal.isEnd(bot.entity.position.floored())) {
          if (!dynamicGoal) {
            bot.emit('goal_reached', stateGoal)
            stateGoal = null
            fullStop()
          }
        } else if (!pathUpdated) {
          const results = bot.pathfinder.getPathTo(stateMovements, stateGoal)
          bot.emit('path_update', results)
          path = results.path
          astartTimedout = results.status === 'partial'
          pathUpdated = true
        }
      }
    }

    if (path.length === 0) {
      return
    }

    let nextPoint = path[0]
    const p = bot.entity.position

    // Handle digging
    if (digging || nextPoint.toBreak.length > 0) {
      if (!digging && bot.entity.onGround) {
        digging = true
        const b = nextPoint.toBreak.shift()
        const block = bot.blockAt(new Vec3(b.x, b.y, b.z), false)
        const tool = bot.pathfinder.bestHarvestTool(block)
        fullStop()

        const digBlock = () => {
          bot.dig(block, true)
            .catch(_ignoreError => {
              resetPath('dig_error')
            })
            .then(function () {
              lastNodeTime = performance.now()
              digging = false
            })
        }

        if (!tool) {
          digBlock()
        } else {
          bot.equip(tool, 'hand')
            .catch(_ignoreError => {})
            .then(() => digBlock())
        }
      }
      return
    }
    // Handle block placement
    // TODO: sneak when placing or make sure the block is not interactive
    if (placing || nextPoint.toPlace.length > 0) {
      if (!placing) {
        placing = true
        placingBlock = nextPoint.toPlace.shift()
        fullStop()
      }

      // Open gates or doors
      if (placingBlock?.useOne) {
        if (!lockUseBlock.tryAcquire()) return
        bot.activateBlock(bot.blockAt(new Vec3(placingBlock.x, placingBlock.y, placingBlock.z))).then(() => {
          lockUseBlock.release()
          placingBlock = nextPoint.toPlace.shift()
        }, err => {
          console.error(err)
          lockUseBlock.release()
        })
        return
      }
      const block = stateMovements.getScaffoldingItem()
      if (!block) {
        resetPath('no_scaffolding_blocks')
        return
      }
      if (bot.pathfinder.LOSWhenPlacingBlocks && placingBlock.y === bot.entity.position.floored().y - 1 && placingBlock.dy === 0) {
        if (!moveToEdge(new Vec3(placingBlock.x, placingBlock.y, placingBlock.z), new Vec3(placingBlock.dx, 0, placingBlock.dz))) return
      }
      let canPlace = true
      if (placingBlock.jump) {
        bot.setControlState('jump', true)
        canPlace = placingBlock.y + 1 < bot.entity.position.y
      }
      if (canPlace) {
        if (!lockEquipItem.tryAcquire()) return
        bot.equip(block, 'hand')
          .then(function () {
            lockEquipItem.release()
            const refBlock = bot.blockAt(new Vec3(placingBlock.x, placingBlock.y, placingBlock.z), false)
            if (!lockPlaceBlock.tryAcquire()) return
            if (interactableBlocks.includes(refBlock.name)) {
              bot.setControlState('sneak', true)
            }
            bot.placeBlock(refBlock, new Vec3(placingBlock.dx, placingBlock.dy, placingBlock.dz))
              .then(function () {
                // Dont release Sneak if the block placement was not successful
                bot.setControlState('sneak', false)
                if (bot.pathfinder.LOSWhenPlacingBlocks && placingBlock.returnPos) returningPos = placingBlock.returnPos.clone()
              })
              .catch(_ignoreError => {
                resetPath('place_error')
              })
              .then(() => {
                lockPlaceBlock.release()
                placing = false
                lastNodeTime = performance.now()
              })
          })
          .catch(_ignoreError => {})
      }
      return
    }

    let dx = nextPoint.x - p.x
    const dy = nextPoint.y - p.y
    let dz = nextPoint.z - p.z
    if (Math.abs(dx) <= 0.35 && Math.abs(dz) <= 0.35 && Math.abs(dy) < 1) {
      // arrived at next point
      lastNodeTime = performance.now()
      if (stopPathing) {
        stop()
        return
      }
      path.shift()
      if (path.length === 0) { // done
        // If the block the bot is standing on is not a full block only checking for the floored position can fail as
        // the distance to the goal can get greater then 0 when the vector is floored.
        if (!dynamicGoal && stateGoal && (stateGoal.isEnd(p.floored()) || stateGoal.isEnd(p.floored().offset(0, 1, 0)))) {
          bot.emit('goal_reached', stateGoal)
          stateGoal = null
        }
        fullStop()
        return
      }
      // not done yet
      nextPoint = path[0]
      if (nextPoint.toBreak.length > 0 || nextPoint.toPlace.length > 0) {
        fullStop()
        return
      }
      dx = nextPoint.x - p.x
      dz = nextPoint.z - p.z
    }

    bot.look(Math.atan2(-dx, -dz), 0)
    bot.setControlState('forward', true)
    bot.setControlState('jump', false)

    if (bot.entity.isInWater) {
      bot.setControlState('jump', true)
      bot.setControlState('sprint', false)
    } else if (stateMovements.allowSprinting && physics.canStraightLine(path, true)) {
      bot.setControlState('jump', false)
      bot.setControlState('sprint', true)
    } else if (stateMovements.allowSprinting && physics.canSprintJump(path)) {
      bot.setControlState('jump', true)
      bot.setControlState('sprint', true)
    } else if (physics.canStraightLine(path)) {
      bot.setControlState('jump', false)
      bot.setControlState('sprint', false)
    } else if (physics.canWalkJump(path)) {
      bot.setControlState('jump', true)
      bot.setControlState('sprint', false)
    } else {
      bot.setControlState('forward', false)
      bot.setControlState('sprint', false)
    }

    // check for futility
    if (performance.now() - lastNodeTime > 3500) {
      // should never take this long to go to the next node
      resetPath('stuck')
    }
  }
}

module.exports = {
  pathfinder: inject,
  Movements: require('./lib/movements'),
  goals: require('./lib/goals')
}


================================================
FILE: lib/astar.js
================================================
const { performance } = require('perf_hooks')

const Heap = require('./heap.js')

class PathNode {
  constructor () {
    this.data = null
    this.g = 0
    this.h = 0
    this.f = 0
    this.parent = null
  }

  set (data, g, h, parent = null) {
    this.data = data
    this.g = g
    this.h = h
    this.f = g + h
    this.parent = parent
    return this
  }
}

function reconstructPath (node) {
  const path = []
  while (node.parent) {
    path.push(node.data)
    node = node.parent
  }
  return path.reverse()
}

class AStar {
  constructor (start, movements, goal, timeout, tickTimeout = 40, searchRadius = -1) {
    this.startTime = performance.now()

    this.movements = movements
    this.goal = goal
    this.timeout = timeout
    this.tickTimeout = tickTimeout

    this.closedDataSet = new Set()
    this.openHeap = new Heap()
    this.openDataMap = new Map()

    const startNode = new PathNode().set(start, 0, goal.heuristic(start))
    this.openHeap.push(startNode)
    this.openDataMap.set(startNode.data.hash, startNode)
    this.bestNode = startNode

    this.maxCost = searchRadius < 0 ? -1 : startNode.h + searchRadius
    this.visitedChunks = new Set()
  }

  makeResult (status, node) {
    return {
      status,
      cost: node.g,
      time: performance.now() - this.startTime,
      visitedNodes: this.closedDataSet.size,
      generatedNodes: this.closedDataSet.size + this.openHeap.size(),
      path: reconstructPath(node),
      context: this
    }
  }

  compute () {
    const computeStartTime = performance.now()
    while (!this.openHeap.isEmpty()) {
      if (performance.now() - computeStartTime > this.tickTimeout) { // compute time per tick
        return this.makeResult('partial', this.bestNode)
      }
      if (performance.now() - this.startTime > this.timeout) { // total compute time
        return this.makeResult('timeout', this.bestNode)
      }
      const node = this.openHeap.pop()
      if (this.goal.isEnd(node.data)) {
        return this.makeResult('success', node)
      }
      // not done yet
      this.openDataMap.delete(node.data.hash)
      this.closedDataSet.add(node.data.hash)
      this.visitedChunks.add(`${node.data.x >> 4},${node.data.z >> 4}`)

      const neighbors = this.movements.getNeighbors(node.data)
      for (const neighborData of neighbors) {
        if (this.closedDataSet.has(neighborData.hash)) {
          continue // skip closed neighbors
        }
        const gFromThisNode = node.g + neighborData.cost
        let neighborNode = this.openDataMap.get(neighborData.hash)
        let update = false

        const heuristic = this.goal.heuristic(neighborData)
        if (this.maxCost > 0 && gFromThisNode + heuristic > this.maxCost) continue

        if (neighborNode === undefined) {
          // add neighbor to the open set
          neighborNode = new PathNode()
          // properties will be set later
          this.openDataMap.set(neighborData.hash, neighborNode)
        } else {
          if (neighborNode.g < gFromThisNode) {
            // skip this one because another route is faster
            continue
          }
          update = true
        }
        // found a new or better route.
        // update this neighbor with this node as its new parent
        neighborNode.set(neighborData, gFromThisNode, heuristic, node)
        if (neighborNode.h < this.bestNode.h) this.bestNode = neighborNode
        if (update) {
          this.openHeap.update(neighborNode)
        } else {
          this.openHeap.push(neighborNode)
        }
      }
    }
    // all the neighbors of every accessible node have been exhausted
    return this.makeResult('noPath', this.bestNode)
  }
}

module.exports = AStar


================================================
FILE: lib/goals.js
================================================
const { Vec3 } = require('vec3')
const { getShapeFaceCenters } = require('./shapes')

// Goal base class
class Goal {
  // Return the distance between node and the goal
  heuristic (node) {
    return 0
  }

  // Return true if the node has reach the goal
  isEnd (node) {
    return true
  }

  // Return true if the goal has changed and the current path
  // should be invalidated and computed again
  hasChanged () {
    return false
  }

  // Returns true if the goal is still valid for the goal,
  // for the GoalFollow this would be true if the entity is not null
  isValid () {
    return true
  }
}

// One specific block that the player should stand inside at foot level
class GoalBlock extends Goal {
  constructor (x, y, z) {
    super()
    this.x = Math.floor(x)
    this.y = Math.floor(y)
    this.z = Math.floor(z)
  }

  heuristic (node) {
    const dx = this.x - node.x
    const dy = this.y - node.y
    const dz = this.z - node.z
    return distanceXZ(dx, dz) + Math.abs(dy)
  }

  isEnd (node) {
    return node.x === this.x && node.y === this.y && node.z === this.z
  }
}

// A block position that the player should get within a certain radius of, used for following entities
class GoalNear extends Goal {
  constructor (x, y, z, range) {
    super()
    this.x = Math.floor(x)
    this.y = Math.floor(y)
    this.z = Math.floor(z)
    this.rangeSq = range * range
  }

  heuristic (node) {
    const dx = this.x - node.x
    const dy = this.y - node.y
    const dz = this.z - node.z
    return distanceXZ(dx, dz) + Math.abs(dy)
  }

  isEnd (node) {
    const dx = this.x - node.x
    const dy = this.y - node.y
    const dz = this.z - node.z
    return (dx * dx + dy * dy + dz * dz) <= this.rangeSq
  }
}

// Useful for long-range goals that don't have a specific Y level
class GoalXZ extends Goal {
  constructor (x, z) {
    super()
    this.x = Math.floor(x)
    this.z = Math.floor(z)
  }

  heuristic (node) {
    const dx = this.x - node.x
    const dz = this.z - node.z
    return distanceXZ(dx, dz)
  }

  isEnd (node) {
    return node.x === this.x && node.z === this.z
  }
}

// Useful for finding builds that you don't have an exact Y level for, just an approximate X and Z level
class GoalNearXZ extends Goal {
  constructor (x, z, range) {
    super()
    this.x = Math.floor(x)
    this.z = Math.floor(z)
    this.rangeSq = range * range
  }

  heuristic (node) {
    const dx = this.x - node.x
    const dz = this.z - node.z
    return distanceXZ(dx, dz)
  }

  isEnd (node) {
    const dx = this.x - node.x
    const dz = this.z - node.z
    return (dx * dx + dz * dz) <= this.rangeSq
  }
}

// Goal is a Y coordinate
class GoalY extends Goal {
  constructor (y) {
    super()
    this.y = Math.floor(y)
  }

  heuristic (node) {
    const dy = this.y - node.y
    return Math.abs(dy)
  }

  isEnd (node) {
    return node.y === this.y
  }
}

// Don't get into the block, but get directly adjacent to it. Useful for chests.
class GoalGetToBlock extends Goal {
  constructor (x, y, z) {
    super()
    this.x = Math.floor(x)
    this.y = Math.floor(y)
    this.z = Math.floor(z)
  }

  heuristic (node) {
    const dx = node.x - this.x
    const dy = node.y - this.y
    const dz = node.z - this.z
    return distanceXZ(dx, dz) + Math.abs(dy < 0 ? dy + 1 : dy)
  }

  isEnd (node) {
    const dx = node.x - this.x
    const dy = node.y - this.y
    const dz = node.z - this.z
    return Math.abs(dx) + Math.abs(dy < 0 ? dy + 1 : dy) + Math.abs(dz) === 1
  }
}

// Path into a position were a blockface of block at x y z is visible.
class GoalLookAtBlock extends Goal {
  constructor (pos, world, options = {}) {
    super()
    this.pos = pos
    this.world = world
    this.reach = options.reach || 4.5 // default survival: 4.5 creative: 5
    this.entityHeight = options.entityHeight || 1.6
  }

  heuristic (node) {
    const dx = node.x - this.pos.x
    const dy = node.y - this.pos.y
    const dz = node.z - this.pos.z
    return distanceXZ(dx, dz) + Math.abs(dy < 0 ? dy + 1 : dy)
  }

  isEnd (node) {
    if (node.distanceTo(this.pos.offset(0, this.entityHeight, 0)) > this.reach) return false
    // Check faces that could be seen from the current position. If the delta is smaller then 0.5 that means the bot cam most likely not see the face as the block is 1 block thick
    // this could be false for blocks that have a smaller bounding box then 1x1x1
    const dx = node.x - (this.pos.x + 0.5)
    const dy = node.y + this.entityHeight - (this.pos.y + 0.5) // -0.5 because the bot position is calculated from the block position that is inside its feet so 0.5 - 1 = -0.5
    const dz = node.z - (this.pos.z + 0.5)
    // Check y first then x and z
    const visibleFaces = {
      y: Math.sign(Math.abs(dy) > 0.5 ? dy : 0),
      x: Math.sign(Math.abs(dx) > 0.5 ? dx : 0),
      z: Math.sign(Math.abs(dz) > 0.5 ? dz : 0)
    }
    const validFaces = []
    for (const i in visibleFaces) {
      if (!visibleFaces[i]) {
        // skip as this face is not visible
        continue
      }
      const targetPos = new Vec3(this.pos.x, this.pos.y, this.pos.z).offset(0.5 + (i === 'x' ? visibleFaces[i] * 0.5 : 0), 0.5 + (i === 'y' ? visibleFaces[i] * 0.5 : 0), 0.5 + (i === 'z' ? visibleFaces[i] * 0.5 : 0))
      const startPos = new Vec3(node.x + 0.5, node.y + this.entityHeight, node.z + 0.5)
      const rayPos = this.world.raycast(startPos, targetPos.clone().subtract(startPos).normalize(), this.reach)?.position
      if (rayPos && rayPos.x === this.pos.x && rayPos.y === this.pos.y && rayPos.z === this.pos.z) {
        validFaces.push({
          face: rayPos.face,
          targetPos
        })
      }
    }
    return validFaces.length !== 0
  }
}

// Path into a position were a blockface of block at x y z is visible.
// You'll manually need to break the block. THIS WONT BREAK IT
class GoalBreakBlock extends Goal {
  constructor (x, y, z, bot, options = {}) {
    super()
    this.goal = new GoalLookAtBlock(new Vec3(x, y, z), bot, options)
  }

  isEnd (node) {
    return this.goal.isEnd(node)
  }

  heuristic (node) {
    return this.goal.heuristic(node)
  }
}

// A composite of many goals, any one of which satisfies the composite.
// For example, a GoalCompositeAny of block goals for every oak log in loaded
// chunks would result in it pathing to the easiest oak log to get to
class GoalCompositeAny extends Goal {
  constructor (goals = []) {
    super()
    this.goals = goals
  }

  push (goal) {
    this.goals.push(goal)
  }

  heuristic (node) {
    let min = Number.MAX_VALUE
    for (const i in this.goals) {
      min = Math.min(min, this.goals[i].heuristic(node))
    }
    return min
  }

  isEnd (node) {
    for (const i in this.goals) {
      if (this.goals[i].isEnd(node)) return true
    }
    return false
  }

  hasChanged () {
    for (const i in this.goals) {
      if (this.goals[i].hasChanged()) return true
    }
    return false
  }

  isValid () {
    return this.goals.reduce((pre, curr) => pre && curr.isValid(), true)
  }
}

// A composite of many goals, all of them needs to be satisfied.
class GoalCompositeAll extends Goal {
  constructor (goals = []) {
    super()
    this.goals = goals
  }

  push (goal) {
    this.goals.push(goal)
  }

  heuristic (node) {
    let max = Number.MIN_VALUE
    for (const i in this.goals) {
      max = Math.max(max, this.goals[i].heuristic(node))
    }
    return max
  }

  isEnd (node) {
    for (const i in this.goals) {
      if (!this.goals[i].isEnd(node)) return false
    }
    return true
  }

  hasChanged () {
    for (const i in this.goals) {
      if (this.goals[i].hasChanged()) return true
    }
    return false
  }

  isValid () {
    return this.goals.reduce((pre, curr) => pre && curr.isValid(), true)
  }
}

class GoalInvert extends Goal {
  constructor (goal) {
    super()
    this.goal = goal
  }

  heuristic (node) {
    return -this.goal.heuristic(node)
  }

  isEnd (node) {
    return !this.goal.isEnd(node)
  }

  hasChanged () {
    return this.goal.hasChanged()
  }

  isValid () {
    return this.goal.isValid()
  }
}

class GoalFollow extends Goal {
  constructor (entity, range) {
    super()
    this.entity = entity
    this.x = Math.floor(entity.position.x)
    this.y = Math.floor(entity.position.y)
    this.z = Math.floor(entity.position.z)
    this.rangeSq = range * range
  }

  heuristic (node) {
    const dx = this.x - node.x
    const dy = this.y - node.y
    const dz = this.z - node.z
    return distanceXZ(dx, dz) + Math.abs(dy)
  }

  isEnd (node) {
    const dx = this.x - node.x
    const dy = this.y - node.y
    const dz = this.z - node.z
    return (dx * dx + dy * dy + dz * dz) <= this.rangeSq
  }

  hasChanged () {
    const p = this.entity.position.floored()
    const dx = this.x - p.x
    const dy = this.y - p.y
    const dz = this.z - p.z
    if ((dx * dx + dy * dy + dz * dz) > this.rangeSq) {
      this.x = p.x
      this.y = p.y
      this.z = p.z
      return true
    }
    return false
  }

  isValid () {
    return this.entity != null
  }
}

function distanceXZ (dx, dz) {
  dx = Math.abs(dx)
  dz = Math.abs(dz)
  return Math.abs(dx - dz) + Math.min(dx, dz) * Math.SQRT2
}

/**
 * Options:
 * - range - maximum distance from the clicked face
 * - faces - the directions of the faces the player can click
 * - facing - the direction the player must be facing
 * - facing3D - boolean, facing is 3D (true) or 2D (false)
 * - half - 'top' or 'bottom', the half that must be clicked
 * - LOS - true or false, should the bot have line of sight off the placement face. Default true.
 */
class GoalPlaceBlock extends Goal {
  constructor (pos, world, options) {
    super()
    this.pos = pos.floored()
    this.world = world
    this.options = options
    if (!this.options.range) this.options.range = 5
    if (!('LOS' in this.options)) this.options.LOS = true
    if (!this.options.faces) {
      this.options.faces = [new Vec3(0, -1, 0), new Vec3(0, 1, 0), new Vec3(0, 0, -1), new Vec3(0, 0, 1), new Vec3(-1, 0, 0), new Vec3(1, 0, 0)]
    }
    this.options.facing = ['north', 'east', 'south', 'west', 'up', 'down'].indexOf(this.options.facing)
    this.facesPos = []
    for (const dir of this.options.faces) {
      const ref = this.pos.plus(dir)
      const refBlock = this.world.getBlock(ref)
      if (!refBlock) continue
      for (const center of getShapeFaceCenters(refBlock.shapes, dir.scaled(-1), this.options.half)) {
        this.facesPos.push([dir, center.add(ref), ref])
      }
    }
  }

  heuristic (node) {
    const dx = node.x - this.pos.x
    const dy = node.y - this.pos.y
    const dz = node.z - this.pos.z
    return distanceXZ(dx, dz) + Math.abs(dy < 0 ? dy + 1 : dy)
  }

  isEnd (node) {
    if (this.isStandingIn(node)) return false
    const headPos = node.offset(0.5, 1.6, 0.5)
    return this.getFaceAndRef(headPos) !== null
  }

  getFaceAndRef (headPos) {
    for (const [face, to, ref] of this.facesPos) {
      const dir = to.minus(headPos)
      if (dir.norm() > this.options.range) continue
      if (!this.checkFacing(dir)) continue

      if (!this.options.LOS) {
        return { face, to, ref }
      }

      const block = this.world.raycast(headPos, dir.normalize(), this.options.range)
      if (block && block.position.equals(ref) && block.face === vectorToDirection(face.scaled(-1))) {
        return { face, to, ref }
      }
    }
    return null
  }

  checkFacing (dir) {
    if (this.options.facing < 0) return true

    if (this.options.facing3D) {
      const dH = Math.sqrt(dir.x * dir.x + dir.z * dir.z)
      const vAngle = Math.atan2(dir.y, dH) * 180 / Math.PI
      if (vAngle > 45) return this.options.facing === 4
      if (vAngle < -45) return this.options.facing === 5
    }
    const angle = Math.atan2(dir.x, -dir.z) * 180 / Math.PI + 180 // Convert to [0,360[
    const facing = Math.floor(angle / 90 + 0.5) & 0x3

    if (this.options.facing === facing) return true
    return false
  }

  isStandingIn (node) {
    const dx = node.x - this.pos.x
    const dy = node.y - this.pos.y
    const dz = node.z - this.pos.z
    return (Math.abs(dx) + Math.abs(dy < 0 ? dy + 1 : dy) + Math.abs(dz)) < 1
  }
}

function vectorToDirection (v) {
  if (v.y < 0) {
    return 0
  } else if (v.y > 0) {
    return 1
  } else if (v.z < 0) {
    return 2
  } else if (v.z > 0) {
    return 3
  } else if (v.x < 0) {
    return 4
  } else if (v.x > 0) {
    return 5
  }
}

module.exports = {
  Goal,
  GoalBlock,
  GoalNear,
  GoalXZ,
  GoalNearXZ,
  GoalY,
  GoalGetToBlock,
  GoalCompositeAny,
  GoalCompositeAll,
  GoalInvert,
  GoalFollow,
  GoalPlaceBlock,
  GoalBreakBlock,
  GoalLookAtBlock
}


================================================
FILE: lib/goto.js
================================================
function error (name, message) {
  const err = new Error(message)
  err.name = name
  return err
}

/**
   * Adds a easy-to-use API wrapper for quickly executing a goal and running
   * a callback when that goal is reached. This function serves to remove a
   * lot of boilerplate code for quickly executing a goal.
   *
   * @param {Bot} bot - The bot.
   * @param {Goal} goal - The goal to execute.
   * @returns {Promise} - resolves on success, rejects on error
   */
function goto (bot, goal) {
  return new Promise((resolve, reject) => {
    function goalReached () {
      cleanup()
    }

    function noPathListener (results) {
      if (results.path.length === 0) {
        cleanup()
      } else if (results.status === 'noPath') {
        cleanup(error('NoPath', 'No path to the goal!'))
      } else if (results.status === 'timeout') {
        cleanup(error('Timeout', 'Took to long to decide path to goal!'))
      }
    }

    function goalChangedListener (newGoal) {
      if (newGoal !== goal) {
        cleanup(error('GoalChanged', 'The goal was changed before it could be completed!'))
      }
    }

    function pathStopped () {
      cleanup(error('PathStopped', 'Path was stopped before it could be completed! Thus, the desired goal was not reached.'))
    }

    function cleanup (err) {
      bot.removeListener('goal_reached', goalReached)
      bot.removeListener('path_update', noPathListener)
      bot.removeListener('goal_updated', goalChangedListener)
      bot.removeListener('path_stop', pathStopped)

      // Run callback on next event stack to let pathfinder properly cleanup,
      // otherwise chaining waypoints does not work properly.
      setTimeout(() => {
        if (err) {
          reject(err)
        } else {
          resolve()
        }
      }, 0)
    }

    bot.on('path_stop', pathStopped)
    bot.on('goal_reached', goalReached)
    bot.on('path_update', noPathListener)
    bot.on('goal_updated', goalChangedListener)
    bot.pathfinder.setGoal(goal)
  })
}

module.exports = goto


================================================
FILE: lib/heap.js
================================================
class BinaryHeapOpenSet {
  constructor () {
    // Initialing the array heap and adding a dummy element at index 0
    this.heap = [null]
  }

  size () {
    return this.heap.length - 1
  }

  isEmpty () {
    return this.heap.length === 1
  }

  push (val) {
    // Inserting the new node at the end of the heap array
    this.heap.push(val)

    // Finding the correct position for the new node
    let current = this.heap.length - 1
    let parent = current >>> 1

    // Traversing up the parent node until the current node is greater than the parent
    while (current > 1 && this.heap[parent].f > this.heap[current].f) {
      [this.heap[parent], this.heap[current]] = [this.heap[current], this.heap[parent]]
      current = parent
      parent = current >>> 1
    }
  }

  update (val) {
    let current = this.heap.indexOf(val)
    let parent = current >>> 1

    // Traversing up the parent node until the current node is greater than the parent
    while (current > 1 && this.heap[parent].f > this.heap[current].f) {
      [this.heap[parent], this.heap[current]] = [this.heap[current], this.heap[parent]]
      current = parent
      parent = current >>> 1
    }
  }

  pop () {
    // Smallest element is at the index 1 in the heap array
    const smallest = this.heap[1]

    this.heap[1] = this.heap[this.heap.length - 1]
    this.heap.splice(this.heap.length - 1)

    const size = this.heap.length - 1

    if (size < 2) return smallest

    const val = this.heap[1]
    let index = 1
    let smallerChild = 2
    const cost = val.f
    do {
      let smallerChildNode = this.heap[smallerChild]
      if (smallerChild < size - 1) {
        const rightChildNode = this.heap[smallerChild + 1]
        if (smallerChildNode.f > rightChildNode.f) {
          smallerChild++
          smallerChildNode = rightChildNode
        }
      }
      if (cost <= smallerChildNode.f) {
        break
      }
      this.heap[index] = smallerChildNode
      this.heap[smallerChild] = val
      index = smallerChild

      smallerChild *= 2
    } while (smallerChild <= size)

    return smallest
  }
}

module.exports = BinaryHeapOpenSet


================================================
FILE: lib/interactable.json
================================================
[
  "acacia_door",
  "acacia_fence_gate",
  "acacia_button",
  "acacia_trapdoor",
  "anvil",
  "armor_stand",
  "barrel",
  "beacon",
  "bed_block",
  "bell",
  "birch_boat",
  "birch_button",
  "birch_door",
  "birch_fence_gate",
  "birch_trapdoor",
  "black_bed",
  "black_shulker_box",
  "blast_furnace",
  "blue_bed",
  "blue_shulker_box",
  "brewing_stand",
  "brown_bed",
  "brown_shulker_box",
  "campfire",
  "cauldron",
  "chest",
  "chest_minecart",
  "chipped_anvil",
  "command",
  "command_block",
  "command_block_minecart",
  "comparator",
  "composter",
  "crafting_table",
  "cyan_bed",
  "cyan_shulker_box",
  "damaged_anvil",
  "dark_oak_boat",
  "dark_oak_button",
  "dark_oak_fence_gate",
  "dark_oak_trapdoor",
  "dark_oak_door",
  "daylight_detector",
  "daylight_detector_inverted",
  "diode",
  "diode_block_off",
  "diode_block_on",
  "dispenser",
  "door",
  "dragon_egg",
  "dropper",
  "enchanting_table",
  "enchantment_table",
  "end_crystal",
  "end_portal_frame",
  "ender_portal_frame",
  "ender_chest",
  "explosive_minecart",
  "farmland",
  "fence_gate",
  "fletching_table",
  "flower_pot",
  "furnace",
  "furnace_minecart",
  "gray_bed",
  "gray_shulker_box",
  "green_bed",
  "green_shulker_box",
  "hopper",
  "hopper_minecart",
  "iron_door",
  "iron_trapdoor",
  "item_frame",
  "jukebox",
  "jungle_button",
  "jungle_boat",
  "jungle_door",
  "jungle_fence_gate",
  "jungle_trapdoor",
  "lever",
  "light_blue_bed",
  "light_blue_shulker_box",
  "light_gray_bed",
  "light_gray_shulker_box",
  "lime_bed",
  "lime_shulker_box",
  "magenta_bed",
  "magenta_shulker_box",
  "minecart",
  "note_block",
  "oak_boat",
  "oak_button",
  "oak_door",
  "oak_fence_gate",
  "oak_trapdoor",
  "orange_bed",
  "orange_shulker_box",
  "pink_bed",
  "pink_shulker_box",
  "powered_minecart",
  "purple_bed",
  "purple_shulker_box",
  "red_bed",
  "red_shulker_box",
  "redstone_ore",
  "redstone_comparator_off",
  "redstone_comparator_on",
  "repeating_command_block",
  "repeater",
  "powered_repeater",
  "unpowered_repeater",
  "redstone_torch",
  "saddle",
  "shulker_box",
  "sign",
  "sign_post",
  "smithing_table",
  "smoker",
  "spruce_boat",
  "spruce_button",
  "spruce_door",
  "spruce_fence_gate",
  "stonecutter",
  "stone_button",
  "storage_minecart",
  "tnt_minecart",
  "tnt",
  "trap_door",
  "trapped_chest",
  "white_bed",
  "white_shulker_box",
  "wood_button",
  "yellow_bed",
  "yelow_shulker_box"
]

================================================
FILE: lib/lock.js
================================================
const { EventEmitter, on } = require('events')

class Lock {
  constructor () {
    this._locked = false
    this._emitter = new EventEmitter()
  }

  /**
   * Synchronous. Returns true if the lock was acquired. Return false if the lock is already held by something else.
   * @returns {boolean}
   */
  tryAcquire () {
    if (!this._locked) {
      this._locked = true
      return true
    }
    return false
  }

  /**
   * Asynchronous. Resolves when the lock was acquired.
   * @returns {Promise<void>}
   */
  async acquire () {
    if (!this._locked) {
      this._locked = true
      return
    }

    // Cannot use for await without a variable. But the variable is never used. So eslint complains ¯\_(ツ)_/¯
    for await (const _ of on(this._emitter, 'release')) { // eslint-disable-line
      if (!this._locked) {
        this._locked = true
        return
      }
    }
  }

  /**
   * Releases the lock.
   */
  release () {
    this._locked = false
    setImmediate(() => this._emitter.emit('release'))
  }
}

module.exports = Lock


================================================
FILE: lib/move.js
================================================
const { Vec3 } = require('vec3')

class Move extends Vec3 {
  constructor (x, y, z, remainingBlocks, cost, toBreak = [], toPlace = [], parkour = false) {
    super(Math.floor(x), Math.floor(y), Math.floor(z))
    this.remainingBlocks = remainingBlocks
    this.cost = cost
    this.toBreak = toBreak
    this.toPlace = toPlace
    this.parkour = parkour

    this.hash = this.x + ',' + this.y + ',' + this.z
  }
}

module.exports = Move


================================================
FILE: lib/movements.js
================================================
const { Vec3 } = require('vec3')
const nbt = require('prismarine-nbt')
const Move = require('./move')

const cardinalDirections = [
  { x: -1, z: 0 }, // West
  { x: 1, z: 0 }, // East
  { x: 0, z: -1 }, // North
  { x: 0, z: 1 } // South
]
const diagonalDirections = [
  { x: -1, z: -1 },
  { x: -1, z: 1 },
  { x: 1, z: -1 },
  { x: 1, z: 1 }
]

class Movements {
  constructor (bot) {
    const registry = bot.registry
    this.bot = bot

    this.canDig = true
    this.digCost = 1
    this.placeCost = 1
    this.liquidCost = 1
    this.entityCost = 1

    this.dontCreateFlow = true
    this.dontMineUnderFallingBlock = true
    this.allow1by1towers = true
    this.allowFreeMotion = false
    this.allowParkour = true
    this.allowSprinting = true
    this.allowEntityDetection = true

    this.entitiesToAvoid = new Set()
    this.passableEntities = new Set(require('./passableEntities.json'))
    this.interactableBlocks = new Set(require('./interactable.json'))

    this.blocksCantBreak = new Set()
    this.blocksCantBreak.add(registry.blocksByName.chest.id)

    registry.blocksArray.forEach(block => {
      if (block.diggable) return
      this.blocksCantBreak.add(block.id)
    })

    this.blocksToAvoid = new Set()
    this.blocksToAvoid.add(registry.blocksByName.fire.id)
    if (registry.blocksByName.cobweb) this.blocksToAvoid.add(registry.blocksByName.cobweb.id)
    if (registry.blocksByName.web) this.blocksToAvoid.add(registry.blocksByName.web.id)
    this.blocksToAvoid.add(registry.blocksByName.lava.id)

    this.liquids = new Set()
    this.liquids.add(registry.blocksByName.water.id)
    this.liquids.add(registry.blocksByName.lava.id)

    this.gravityBlocks = new Set()
    this.gravityBlocks.add(registry.blocksByName.sand.id)
    this.gravityBlocks.add(registry.blocksByName.gravel.id)

    this.climbables = new Set()
    this.climbables.add(registry.blocksByName.ladder.id)
    // this.climbables.add(registry.blocksByName.vine.id)
    this.emptyBlocks = new Set()

    this.replaceables = new Set()
    this.replaceables.add(registry.blocksByName.air.id)
    if (registry.blocksByName.cave_air) this.replaceables.add(registry.blocksByName.cave_air.id)
    if (registry.blocksByName.void_air) this.replaceables.add(registry.blocksByName.void_air.id)
    this.replaceables.add(registry.blocksByName.water.id)
    this.replaceables.add(registry.blocksByName.lava.id)

    this.scafoldingBlocks = []
    this.scafoldingBlocks.push(registry.itemsByName.dirt.id)
    this.scafoldingBlocks.push(registry.itemsByName.cobblestone.id)

    const Block = require('prismarine-block')(bot.registry)
    this.fences = new Set()
    this.carpets = new Set()
    this.openable = new Set()
    registry.blocksArray.map(x => Block.fromStateId(x.minStateId, 0)).forEach(block => {
      if (block.shapes.length > 0) {
        // Fences or any block taller than 1, they will be considered as non-physical to avoid
        // trying to walk on them
        if (block.shapes[0][4] > 1) this.fences.add(block.type)
        // Carpets or any blocks smaller than 0.1, they will be considered as safe to walk in
        if (block.shapes[0][4] < 0.1) this.carpets.add(block.type)
      } else if (block.shapes.length === 0) {
        this.emptyBlocks.add(block.type)
      }
    })
    registry.blocksArray.forEach(block => {
      if (this.interactableBlocks.has(block.name) && block.name.toLowerCase().includes('gate') && !block.name.toLowerCase().includes('iron')) {
        // console.info(block)
        this.openable.add(block.id)
      }
    })

    this.canOpenDoors = false // Causes issues. Probably due to none paper servers.

    this.exclusionAreasStep = []
    this.exclusionAreasBreak = []
    this.exclusionAreasPlace = []

    this.maxDropDown = 4
    this.infiniteLiquidDropdownDistance = true

    this.entityIntersections = {}
  }

  exclusionPlace (block) {
    if (this.exclusionAreasPlace.length === 0) return 0
    let weight = 0
    for (const a of this.exclusionAreasPlace) {
      weight += a(block)
    }
    return weight
  }

  exclusionStep (block) {
    if (this.exclusionAreasStep.length === 0) return 0
    let weight = 0
    for (const a of this.exclusionAreasStep) {
      weight += a(block)
    }
    return weight
  }

  exclusionBreak (block) {
    if (this.exclusionAreasBreak.length === 0) return 0
    let weight = 0
    for (const a of this.exclusionAreasBreak) {
      weight += a(block)
    }
    return weight
  }

  countScaffoldingItems () {
    let count = 0
    const items = this.bot.inventory.items()
    for (const id of this.scafoldingBlocks) {
      for (const j in items) {
        const item = items[j]
        if (item.type === id) count += item.count
      }
    }
    return count
  }

  getScaffoldingItem () {
    const items = this.bot.inventory.items()
    for (const id of this.scafoldingBlocks) {
      for (const j in items) {
        const item = items[j]
        if (item.type === id) return item
      }
    }
    return null
  }

  clearCollisionIndex () {
    this.entityIntersections = {}
  }

  /**
   * Finds blocks intersected by entity bounding boxes
   * and sets the number of ents intersecting in a dict.
   * Ignores entities that do not affect block placement
   */
  updateCollisionIndex () {
    for (const ent of Object.values(this.bot.entities)) {
      if (ent === this.bot.entity) { continue }

      const avoidedEnt = this.entitiesToAvoid.has(ent.name)
      if (avoidedEnt || !this.passableEntities.has(ent.name)) {
        const entSquareRadius = ent.width / 2.0
        const minY = Math.floor(ent.position.y)
        const maxY = Math.ceil(ent.position.y + ent.height)
        const minX = Math.floor(ent.position.x - entSquareRadius)
        const maxX = Math.ceil(ent.position.x + entSquareRadius)
        const minZ = Math.floor(ent.position.z - entSquareRadius)
        const maxZ = Math.ceil(ent.position.z + entSquareRadius)

        const cost = avoidedEnt ? 100 : 1

        for (let y = minY; y < maxY; y++) {
          for (let x = minX; x < maxX; x++) {
            for (let z = minZ; z < maxZ; z++) {
              this.entityIntersections[`${x},${y},${z}`] = this.entityIntersections[`${x},${y},${z}`] ?? 0
              this.entityIntersections[`${x},${y},${z}`] += cost // More ents = more weight
            }
          }
        }
      }
    }
  }

  /**
   * Gets number of entities who's bounding box intersects the node + offset
   * @param {import('vec3').Vec3} pos node position
   * @param {number} dx X axis offset
   * @param {number} dy Y axis offset
   * @param {number} dz Z axis offset
   * @returns {number} Number of entities intersecting block
   */
  getNumEntitiesAt (pos, dx, dy, dz) {
    if (this.allowEntityDetection === false) return 0
    if (!pos) return 0
    const y = pos.y + dy
    const x = pos.x + dx
    const z = pos.z + dz

    return this.entityIntersections[`${x},${y},${z}`] ?? 0
  }

  getBlock (pos, dx, dy, dz) {
    const b = pos ? this.bot.blockAt(new Vec3(pos.x + dx, pos.y + dy, pos.z + dz), false) : null
    if (!b) {
      return {
        replaceable: false,
        canFall: false,
        safe: false,
        physical: false,
        liquid: false,
        climbable: false,
        height: dy,
        openable: false
      }
    }
    b.climbable = this.climbables.has(b.type)
    b.safe = (b.boundingBox === 'empty' || b.climbable || this.carpets.has(b.type)) && !this.blocksToAvoid.has(b.type)
    b.physical = b.boundingBox === 'block' && !this.fences.has(b.type)
    b.replaceable = this.replaceables.has(b.type) && !b.physical
    b.liquid = this.liquids.has(b.type)
    b.height = pos.y + dy
    b.canFall = this.gravityBlocks.has(b.type)
    b.openable = this.openable.has(b.type)

    for (const shape of b.shapes) {
      b.height = Math.max(b.height, pos.y + dy + shape[4])
    }
    return b
  }

  /**
   * Takes into account if the block is within a break exclusion area.
   * @param {import('prismarine-block').Block} block
   * @returns
   */
  safeToBreak (block) {
    if (!this.canDig) {
      return false
    }

    if (this.dontCreateFlow) {
      // false if next to liquid
      if (this.getBlock(block.position, 0, 1, 0).liquid) return false
      if (this.getBlock(block.position, -1, 0, 0).liquid) return false
      if (this.getBlock(block.position, 1, 0, 0).liquid) return false
      if (this.getBlock(block.position, 0, 0, -1).liquid) return false
      if (this.getBlock(block.position, 0, 0, 1).liquid) return false
    }

    if (this.dontMineUnderFallingBlock) {
      // TODO: Determine if there are other blocks holding the entity up
      if (this.getBlock(block.position, 0, 1, 0).canFall || (this.getNumEntitiesAt(block.position, 0, 1, 0) > 0)) {
        return false
      }
    }

    return block.type && !this.blocksCantBreak.has(block.type) && this.exclusionBreak(block) < 100
  }

  /**
   * Takes into account if the block is within the stepExclusionAreas. And returns 100 if a block to be broken is within break exclusion areas.
   * @param {import('prismarine-block').Block} block block
   * @param {[]} toBreak
   * @returns {number}
   */
  safeOrBreak (block, toBreak) {
    let cost = 0
    cost += this.exclusionStep(block) // Is excluded so can't move or break
    cost += this.getNumEntitiesAt(block.position, 0, 0, 0) * this.entityCost
    if (block.safe) return cost
    if (!this.safeToBreak(block)) return 100 // Can't break, so can't move
    toBreak.push(block.position)

    if (block.physical) cost += this.getNumEntitiesAt(block.position, 0, 1, 0) * this.entityCost // Add entity cost if there is an entity above (a breakable block) that will fall

    const tool = this.bot.pathfinder.bestHarvestTool(block)
    const enchants = (tool && tool.nbt) ? nbt.simplify(tool.nbt).Enchantments : []
    const effects = this.bot.entity.effects
    const digTime = block.digTime(tool ? tool.type : null, false, false, false, enchants, effects)
    const laborCost = (1 + 3 * digTime / 1000) * this.digCost
    cost += laborCost
    return cost
  }

  getMoveJumpUp (node, dir, neighbors) {
    const blockA = this.getBlock(node, 0, 2, 0)
    const blockH = this.getBlock(node, dir.x, 2, dir.z)
    const blockB = this.getBlock(node, dir.x, 1, dir.z)
    const blockC = this.getBlock(node, dir.x, 0, dir.z)

    let cost = 2 // move cost (move+jump)
    const toBreak = []
    const toPlace = []

    if (blockA.physical && (this.getNumEntitiesAt(blockA.position, 0, 1, 0) > 0)) return // Blocks A, B and H are above C, D and the player's space, we need to make sure there are no entities that will fall down onto our building space if we break them
    if (blockH.physical && (this.getNumEntitiesAt(blockH.position, 0, 1, 0) > 0)) return
    if (blockB.physical && !blockH.physical && !blockC.physical && (this.getNumEntitiesAt(blockB.position, 0, 1, 0) > 0)) return // It is fine if an ent falls on B so long as we don't need to replace block C

    if (!blockC.physical) {
      if (node.remainingBlocks === 0) return // not enough blocks to place

      if (this.getNumEntitiesAt(blockC.position, 0, 0, 0) > 0) return // Check for any entities in the way of a block placement

      const blockD = this.getBlock(node, dir.x, -1, dir.z)
      if (!blockD.physical) {
        if (node.remainingBlocks === 1) return // not enough blocks to place

        if (this.getNumEntitiesAt(blockD.position, 0, 0, 0) > 0) return // Check for any entities in the way of a block placement

        if (!blockD.replaceable) {
          if (!this.safeToBreak(blockD)) return
          cost += this.exclusionBreak(blockD)
          toBreak.push(blockD.position)
        }
        cost += this.exclusionPlace(blockD)
        toPlace.push({ x: node.x, y: node.y - 1, z: node.z, dx: dir.x, dy: 0, dz: dir.z, returnPos: new Vec3(node.x, node.y, node.z) })
        cost += this.placeCost // additional cost for placing a block
      }

      if (!blockC.replaceable) {
        if (!this.safeToBreak(blockC)) return
        cost += this.exclusionBreak(blockC)
        toBreak.push(blockC.position)
      }
      cost += this.exclusionPlace(blockC)
      toPlace.push({ x: node.x + dir.x, y: node.y - 1, z: node.z + dir.z, dx: 0, dy: 1, dz: 0 })
      cost += this.placeCost // additional cost for placing a block

      blockC.height += 1
    }

    const block0 = this.getBlock(node, 0, -1, 0)
    if (blockC.height - block0.height > 1.2) return // Too high to jump

    cost += this.safeOrBreak(blockA, toBreak)
    if (cost > 100) return
    cost += this.safeOrBreak(blockH, toBreak)
    if (cost > 100) return
    cost += this.safeOrBreak(blockB, toBreak)
    if (cost > 100) return

    neighbors.push(new Move(blockB.position.x, blockB.position.y, blockB.position.z, node.remainingBlocks - toPlace.length, cost, toBreak, toPlace))
  }

  getMoveForward (node, dir, neighbors) {
    const blockB = this.getBlock(node, dir.x, 1, dir.z)
    const blockC = this.getBlock(node, dir.x, 0, dir.z)
    const blockD = this.getBlock(node, dir.x, -1, dir.z)

    let cost = 1 // move cost
    cost += this.exclusionStep(blockC)

    const toBreak = []
    const toPlace = []

    if (!blockD.physical && !blockC.liquid) {
      if (node.remainingBlocks === 0) return // not enough blocks to place

      if (this.getNumEntitiesAt(blockD.position, 0, 0, 0) > 0) return // D intersects an entity hitbox

      if (!blockD.replaceable) {
        if (!this.safeToBreak(blockD)) return
        cost += this.exclusionBreak(blockD)
        toBreak.push(blockD.position)
      }
      cost += this.exclusionPlace(blockD)
      toPlace.push({ x: node.x, y: node.y - 1, z: node.z, dx: dir.x, dy: 0, dz: dir.z })
      cost += this.placeCost // additional cost for placing a block
    }

    cost += this.safeOrBreak(blockB, toBreak)
    if (cost > 100) return

    // Open fence gates
    if (this.canOpenDoors && blockC.openable && blockC.shapes && blockC.shapes.length !== 0) {
      toPlace.push({ x: node.x + dir.x, y: node.y, z: node.z + dir.z, dx: 0, dy: 0, dz: 0, useOne: true }) // Indicate that a block should be used on this block not placed
    } else {
      cost += this.safeOrBreak(blockC, toBreak)
      if (cost > 100) return
    }

    if (this.getBlock(node, 0, 0, 0).liquid) cost += this.liquidCost

    neighbors.push(new Move(blockC.position.x, blockC.position.y, blockC.position.z, node.remainingBlocks - toPlace.length, cost, toBreak, toPlace))
  }

  getMoveDiagonal (node, dir, neighbors) {
    let cost = Math.SQRT2 // move cost
    const toBreak = []

    const blockC = this.getBlock(node, dir.x, 0, dir.z) // Landing block or standing on block when jumping up by 1
    const y = blockC.physical ? 1 : 0

    const block0 = this.getBlock(node, 0, -1, 0)

    let cost1 = 0
    const toBreak1 = []
    const blockB1 = this.getBlock(node, 0, y + 1, dir.z)
    const blockC1 = this.getBlock(node, 0, y, dir.z)
    const blockD1 = this.getBlock(node, 0, y - 1, dir.z)
    cost1 += this.safeOrBreak(blockB1, toBreak1)
    cost1 += this.safeOrBreak(blockC1, toBreak1)
    if (blockD1.height - block0.height > 1.2) cost1 += this.safeOrBreak(blockD1, toBreak1)

    let cost2 = 0
    const toBreak2 = []
    const blockB2 = this.getBlock(node, dir.x, y + 1, 0)
    const blockC2 = this.getBlock(node, dir.x, y, 0)
    const blockD2 = this.getBlock(node, dir.x, y - 1, 0)
    cost2 += this.safeOrBreak(blockB2, toBreak2)
    cost2 += this.safeOrBreak(blockC2, toBreak2)
    if (blockD2.height - block0.height > 1.2) cost2 += this.safeOrBreak(blockD2, toBreak2)

    if (cost1 < cost2) {
      cost += cost1
      toBreak.push(...toBreak1)
    } else {
      cost += cost2
      toBreak.push(...toBreak2)
    }
    if (cost > 100) return

    cost += this.safeOrBreak(this.getBlock(node, dir.x, y, dir.z), toBreak)
    if (cost > 100) return
    cost += this.safeOrBreak(this.getBlock(node, dir.x, y + 1, dir.z), toBreak)
    if (cost > 100) return

    if (this.getBlock(node, 0, 0, 0).liquid) cost += this.liquidCost

    const blockD = this.getBlock(node, dir.x, -1, dir.z)
    if (y === 1) { // Case jump up by 1
      if (blockC.height - block0.height > 1.2) return // Too high to jump
      cost += this.safeOrBreak(this.getBlock(node, 0, 2, 0), toBreak)
      if (cost > 100) return
      cost += 1
      neighbors.push(new Move(blockC.position.x, blockC.position.y + 1, blockC.position.z, node.remainingBlocks, cost, toBreak))
    } else if (blockD.physical || blockC.liquid) {
      neighbors.push(new Move(blockC.position.x, blockC.position.y, blockC.position.z, node.remainingBlocks, cost, toBreak))
    } else if (this.getBlock(node, dir.x, -2, dir.z).physical || blockD.liquid) {
      if (!blockD.safe) return // don't self-immolate
      cost += this.getNumEntitiesAt(blockC.position, 0, -1, 0) * this.entityCost
      neighbors.push(new Move(blockC.position.x, blockC.position.y - 1, blockC.position.z, node.remainingBlocks, cost, toBreak))
    }
  }

  getLandingBlock (node, dir) {
    let blockLand = this.getBlock(node, dir.x, -2, dir.z)
    while (blockLand.position && blockLand.position.y > this.bot.game.minY) {
      if (blockLand.liquid && blockLand.safe) return blockLand
      if (blockLand.physical) {
        if (node.y - blockLand.position.y <= this.maxDropDown) return this.getBlock(blockLand.position, 0, 1, 0)
        return null
      }
      if (!blockLand.safe) return null
      blockLand = this.getBlock(blockLand.position, 0, -1, 0)
    }
    return null
  }

  getMoveDropDown (node, dir, neighbors) {
    const blockB = this.getBlock(node, dir.x, 1, dir.z)
    const blockC = this.getBlock(node, dir.x, 0, dir.z)
    const blockD = this.getBlock(node, dir.x, -1, dir.z)

    let cost = 1 // move cost
    const toBreak = []
    const toPlace = []

    const blockLand = this.getLandingBlock(node, dir)
    if (!blockLand) return
    if (!this.infiniteLiquidDropdownDistance && ((node.y - blockLand.position.y) > this.maxDropDown)) return // Don't drop down into water

    cost += this.safeOrBreak(blockB, toBreak)
    if (cost > 100) return
    cost += this.safeOrBreak(blockC, toBreak)
    if (cost > 100) return
    cost += this.safeOrBreak(blockD, toBreak)
    if (cost > 100) return

    if (blockC.liquid) return // dont go underwater

    cost += this.getNumEntitiesAt(blockLand.position, 0, 0, 0) * this.entityCost // add cost for entities

    neighbors.push(new Move(blockLand.position.x, blockLand.position.y, blockLand.position.z, node.remainingBlocks - toPlace.length, cost, toBreak, toPlace))
  }

  getMoveDown (node, neighbors) {
    const block0 = this.getBlock(node, 0, -1, 0)

    let cost = 1 // move cost
    const toBreak = []
    const toPlace = []

    const blockLand = this.getLandingBlock(node, { x: 0, z: 0 })
    if (!blockLand) return

    cost += this.safeOrBreak(block0, toBreak)
    if (cost > 100) return

    if (this.getBlock(node, 0, 0, 0).liquid) return // dont go underwater

    cost += this.getNumEntitiesAt(blockLand.position, 0, 0, 0) * this.entityCost // add cost for entities

    neighbors.push(new Move(blockLand.position.x, blockLand.position.y, blockLand.position.z, node.remainingBlocks - toPlace.length, cost, toBreak, toPlace))
  }

  getMoveUp (node, neighbors) {
    const block1 = this.getBlock(node, 0, 0, 0)
    if (block1.liquid) return
    if (this.getNumEntitiesAt(node, 0, 0, 0) > 0) return // an entity (besides the player) is blocking the building area

    const block2 = this.getBlock(node, 0, 2, 0)

    let cost = 1 // move cost
    const toBreak = []
    const toPlace = []
    cost += this.safeOrBreak(block2, toBreak)
    if (cost > 100) return

    if (!block1.climbable) {
      if (!this.allow1by1towers || node.remainingBlocks === 0) return // not enough blocks to place

      if (!block1.replaceable) {
        if (!this.safeToBreak(block1)) return
        toBreak.push(block1.position)
      }

      const block0 = this.getBlock(node, 0, -1, 0)
      if (block0.physical && block0.height - node.y < -0.2) return // cannot jump-place from a half block

      cost += this.exclusionPlace(block1)
      toPlace.push({ x: node.x, y: node.y - 1, z: node.z, dx: 0, dy: 1, dz: 0, jump: true })
      cost += this.placeCost // additional cost for placing a block
    }

    if (cost > 100) return

    neighbors.push(new Move(node.x, node.y + 1, node.z, node.remainingBlocks - toPlace.length, cost, toBreak, toPlace))
  }

  // Jump up, down or forward over a 1 block gap
  getMoveParkourForward (node, dir, neighbors) {
    const block0 = this.getBlock(node, 0, -1, 0)
    const block1 = this.getBlock(node, dir.x, -1, dir.z)
    if ((block1.physical && block1.height >= block0.height) ||
      !this.getBlock(node, dir.x, 0, dir.z).safe ||
      !this.getBlock(node, dir.x, 1, dir.z).safe) return
    if (this.getBlock(node, 0, 0, 0).liquid) return // cant jump from water

    let cost = 1

    // Leaving entities at the ceiling level (along path) out for now because there are few cases where that will be important
    cost += this.getNumEntitiesAt(node, dir.x, 0, dir.z) * this.entityCost

    // If we have a block on the ceiling, we cannot jump but we can still fall
    let ceilingClear = this.getBlock(node, 0, 2, 0).safe && this.getBlock(node, dir.x, 2, dir.z).safe

    // Similarly for the down path
    let floorCleared = !this.getBlock(node, dir.x, -2, dir.z).physical

    const maxD = this.allowSprinting ? 4 : 2

    for (let d = 2; d <= maxD; d++) {
      const dx = dir.x * d
      const dz = dir.z * d
      const blockA = this.getBlock(node, dx, 2, dz)
      const blockB = this.getBlock(node, dx, 1, dz)
      const blockC = this.getBlock(node, dx, 0, dz)
      const blockD = this.getBlock(node, dx, -1, dz)

      if (blockC.safe) cost += this.getNumEntitiesAt(blockC.position, 0, 0, 0) * this.entityCost

      if (ceilingClear && blockB.safe && blockC.safe && blockD.physical) {
        cost += this.exclusionStep(blockB)
        // Forward
        neighbors.push(new Move(blockC.position.x, blockC.position.y, blockC.position.z, node.remainingBlocks, cost, [], [], true))
        break
      } else if (ceilingClear && blockB.safe && blockC.physical) {
        // Up
        if (blockA.safe && d !== 4) { // 4 Blocks forward 1 block up is very difficult and fails often
          cost += this.exclusionStep(blockA)
          if (blockC.height - block0.height > 1.2) break // Too high to jump
          cost += this.getNumEntitiesAt(blockB.position, 0, 0, 0) * this.entityCost
          neighbors.push(new Move(blockB.position.x, blockB.position.y, blockB.position.z, node.remainingBlocks, cost, [], [], true))
          break
        }
      } else if ((ceilingClear || d === 2) && blockB.safe && blockC.safe && blockD.safe && floorCleared) {
        // Down
        const blockE = this.getBlock(node, dx, -2, dz)
        if (blockE.physical) {
          cost += this.exclusionStep(blockD)
          cost += this.getNumEntitiesAt(blockD.position, 0, 0, 0) * this.entityCost
          neighbors.push(new Move(blockD.position.x, blockD.position.y, blockD.position.z, node.remainingBlocks, cost, [], [], true))
        }
        floorCleared = floorCleared && !blockE.physical
      } else if (!blockB.safe || !blockC.safe) {
        break
      }

      ceilingClear = ceilingClear && blockA.safe
    }
  }

  // for each cardinal direction:
  // "." is head. "+" is feet and current location.
  // "#" is initial floor which is always solid. "a"-"u" are blocks to check
  //
  //   --0123-- horizontalOffset
  //  |
  // +2  aho
  // +1  .bip
  //  0  +cjq
  // -1  #dkr
  // -2   els
  // -3   fmt
  // -4   gn
  //  |
  //  dy

  getNeighbors (node) {
    const neighbors = []

    // Simple moves in 4 cardinal points
    for (const i in cardinalDirections) {
      const dir = cardinalDirections[i]
      this.getMoveForward(node, dir, neighbors)
      this.getMoveJumpUp(node, dir, neighbors)
      this.getMoveDropDown(node, dir, neighbors)
      if (this.allowParkour) {
        this.getMoveParkourForward(node, dir, neighbors)
      }
    }

    // Diagonals
    for (const i in diagonalDirections) {
      const dir = diagonalDirections[i]
      this.getMoveDiagonal(node, dir, neighbors)
    }

    this.getMoveDown(node, neighbors)
    this.getMoveUp(node, neighbors)

    return neighbors
  }
}

module.exports = Movements


================================================
FILE: lib/passableEntities.json
================================================
[
  "falling_block",
  "tnt",
  "item",
  "area_effect_cloud",
  "item_frame",
  "leash_knot",
  "painting",
  "arrow",
  "dragon_fireball",
  "fireball",
  "llama_spit",
  "shulker_bullet",
  "small_fireball",
  "snowball",
  "spectral_arrow",
  "egg",
  "ender_pearl",
  "potion",
  "wither_skull",
  "end_crystal",
  "experience_orb",
  "eye_of_ender",
  "firework_rocket",
  "lightning_bolt",
  "experience_bottle",
  "trident",
  "fishing_bobber",
  "evoker_fangs"
]

================================================
FILE: lib/physics.js
================================================
const { PlayerState } = require('prismarine-physics')

class Physics {
  constructor (bot) {
    this.bot = bot
    this.world = { getBlock: (pos) => { return bot.blockAt(pos, false) } }
  }

  /**
   *
   * @param {function} goal A function is the goal has been reached or not
   * @param {function} controller Controller that can change the current control State for the next tick
   * @param {number} ticks Number of ticks to simulate
   * @param {object} state Starting control state to begin the simulation with
   * @returns { import('prismarine-physics').PlayerState } A player state of the final simulation tick
   */
  simulateUntil (goal, controller = () => {}, ticks = 1, state = null) {
    if (!state) {
      const simulationControl = {
        forward: this.bot.controlState.forward,
        back: this.bot.controlState.back,
        left: this.bot.controlState.left,
        right: this.bot.controlState.right,
        jump: this.bot.controlState.jump,
        sprint: this.bot.controlState.sprint,
        sneak: this.bot.controlState.sneak
      }
      state = new PlayerState(this.bot, simulationControl)
    }

    for (let i = 0; i < ticks; i++) {
      controller(state, i)
      this.bot.physics.simulatePlayer(state, this.world)
      if (state.isInLava) return state
      if (goal(state)) return state
    }

    return state
  }

  simulateUntilNextTick () {
    return this.simulateUntil(() => false, () => {}, 1)
  }

  simulateUntilOnGround (ticks = 5) {
    return this.simulateUntil(state => state.onGround, () => {}, ticks)
  }

  canStraightLine (path, sprint = false) {
    const reached = this.getReached(path)
    const state = this.simulateUntil(reached, this.getController(path[0], false, sprint), 200)
    if (reached(state)) return true

    if (sprint) {
      if (this.canSprintJump(path, 0)) return false
    } else {
      if (this.canWalkJump(path, 0)) return false
    }

    for (let i = 1; i < 7; i++) {
      if (sprint) {
        if (this.canSprintJump(path, i)) return true
      } else {
        if (this.canWalkJump(path, i)) return true
      }
    }
    return false
  }

  canStraightLineBetween (n1, n2) {
    const reached = (state) => {
      const delta = n2.minus(state.pos)
      const r2 = 0.15 * 0.15
      return (delta.x * delta.x + delta.z * delta.z) <= r2 && Math.abs(delta.y) < 0.001 && (state.onGround || state.isInWater)
    }
    const simulationControl = {
      forward: this.bot.controlState.forward,
      back: this.bot.controlState.back,
      left: this.bot.controlState.left,
      right: this.bot.controlState.right,
      jump: this.bot.controlState.jump,
      sprint: this.bot.controlState.sprint,
      sneak: this.bot.controlState.sneak
    }
    const state = new PlayerState(this.bot, simulationControl)
    state.pos.update(n1)
    this.simulateUntil(reached, this.getController(n2, false, true), Math.floor(5 * n1.distanceTo(n2)), state)
    return reached(state)
  }

  canSprintJump (path, jumpAfter = 0) {
    const reached = this.getReached(path)
    const state = this.simulateUntil(reached, this.getController(path[0], true, true, jumpAfter), 20)
    return reached(state)
  }

  canWalkJump (path, jumpAfter = 0) {
    const reached = this.getReached(path)
    const state = this.simulateUntil(reached, this.getController(path[0], true, false, jumpAfter), 20)
    return reached(state)
  }

  getReached (path) {
    return (state) => {
      const delta = path[0].minus(state.pos)
      return Math.abs(delta.x) <= 0.35 && Math.abs(delta.z) <= 0.35 && Math.abs(delta.y) < 1
    }
  }

  getController (nextPoint, jump, sprint, jumpAfter = 0) {
    return (state, tick) => {
      const dx = nextPoint.x - state.pos.x
      const dz = nextPoint.z - state.pos.z
      state.yaw = Math.atan2(-dx, -dz)

      state.control.forward = true
      state.control.jump = jump && tick >= jumpAfter
      state.control.sprint = sprint
    }
  }
}

module.exports = Physics


================================================
FILE: lib/shapes.js
================================================
const { Vec3 } = require('vec3')

function getShapeFaceCenters (shapes, direction, half = null) {
  const faces = []
  for (const shape of shapes) {
    const halfsize = new Vec3(shape[3] - shape[0], shape[4] - shape[1], shape[5] - shape[2]).scale(0.5)
    let center = new Vec3(shape[0] + shape[3], shape[1] + shape[4], shape[2] + shape[5]).scale(0.5)
    center = center.offset(halfsize.x * direction.x, halfsize.y * direction.y, halfsize.z * direction.z)

    if (half === 'top' && center.y <= 0.5) {
      if (Math.abs(direction.y) === 0) center.y += halfsize.y - 0.001
      if (center.y <= 0.5) continue
    } else if (half === 'bottom' && center.y >= 0.5) {
      if (Math.abs(direction.y) === 0) center.y -= halfsize.y - 0.001
      if (center.y >= 0.5) continue
    }

    faces.push(center)
  }
  return faces
}

module.exports = { getShapeFaceCenters }


================================================
FILE: package.json
================================================
{
  "name": "mineflayer-pathfinder",
  "version": "2.4.5",
  "description": "",
  "main": "index.js",
  "scripts": {
    "mocha_test": "mocha --reporter spec --exit",
    "mocha_debug": "mocha --inspect-brk --reporter spec --exit",
    "lint": "standard",
    "fix": "standard --fix",
    "test": "npm run lint && npm run mocha_test"
  },
  "author": "Karang",
  "license": "MIT",
  "dependencies": {
    "minecraft-data": "^3.5.1",
    "prismarine-block": "^1.16.3",
    "prismarine-entity": "^2.1.1",
    "prismarine-item": "^1.11.5",
    "prismarine-nbt": "^2.2.1",
    "prismarine-physics": "^1.5.2",
    "vec3": "^0.2.0"
  },
  "devDependencies": {
    "minecraft-wrap": "^1.2.1",
    "mineflayer": "^4.3.0",
    "mineflayer-pathfinder": "file:./",
    "mocha": "^11.0.1",
    "prismarine-schematic": "^1.2.3",
    "standard": "^17.0.0"
  }
}


================================================
FILE: readme.md
================================================
# Mineflayer-pathfinder

[![npm version](https://badge.fury.io/js/mineflayer-pathfinder.svg)](https://badge.fury.io/js/mineflayer-pathfinder) ![npm](https://img.shields.io/npm/dt/mineflayer-pathfinder) [![Try it on gitpod](https://img.shields.io/badge/try-on%20gitpod-brightgreen.svg)](https://gitpod.io/#https://github.com/PrismarineJS/mineflayer-pathfinder) [![Issue Hunt](https://github.com/BoostIO/issuehunt-materials/blob/master/v1/issuehunt-shield-v1.svg)](https://issuehunt.io/r/PrismarineJS/mineflayer-pathfinder)

Pathfinding plugin for the Minecraft Bot API [Mineflayer](https://github.com/PrismarineJS/mineflayer). Create static, dynamic or composite goals to navigate Minecraft terrain fully autonomously.

Mostly stable. Feel free to contribute by making suggestions or posting issues.

## Install

```bash
npm install mineflayer-pathfinder
```

## Tutorial & Explanation

For a basic explanation of how to use mineflayer-pathfinder, you can read [this tutorial](./examples/tutorial/goalsExplained.md).

## Video Tutorials

For a video tutorial explaining the usage of mineflayer-pathfinder, you can watch the following Youtube videos:

[<img src="https://img.youtube.com/vi/UWGSf08wQSc/0.jpg" alt="part 1" width="200">](https://www.youtube.com/watch?v=UWGSf08wQSc)
[<img src="https://img.youtube.com/vi/ssWE0kXDGJE/0.jpg" alt="part 2" width="200">](https://www.youtube.com/watch?v=ssWE0kXDGJE)

## Example

```js
const mineflayer = require('mineflayer')
const pathfinder = require('mineflayer-pathfinder').pathfinder
const Movements = require('mineflayer-pathfinder').Movements
const { GoalNear } = require('mineflayer-pathfinder').goals
const bot = mineflayer.createBot({ username: 'Player' })

bot.loadPlugin(pathfinder)

bot.once('spawn', () => {
  const defaultMove = new Movements(bot)
  
  bot.on('chat', function(username, message) {
  
    if (username === bot.username) return

    const target = bot.players[username] ? bot.players[username].entity : null
    if (message === 'come') {
      if (!target) {
        bot.chat('I don\'t see you !')
        return
      }
      const p = target.position

      bot.pathfinder.setMovements(defaultMove)
      bot.pathfinder.setGoal(new GoalNear(p.x, p.y, p.z, 1))
    } 
  })
})
```

## Features
 * Optimized and modernized A* pathfinding
 * Complexe goals can be specified (inspired by [baritone goals](https://github.com/cabaletta/baritone/blob/master/FEATURES.md#goals) )
 * Customizable movements generator
 * Each movement can have a different cost
 * Can break/place blocks as part of its deplacement
 * Automatically update path when environment change
 * Long distance paths
 * Can swim
 * Can avoid entities
 * Modular and easily extendable with different behavior

## API
Considering there are a lot of deep changes that are being worked on, it could take some time before it's done

Also, **for now**, there is only the `pathfinder` module, `movements` and `goals` still need to be done


# Functions:

### bot.pathfinder.goto(goal)
Returns a Promise with the path result. Resolves when the goal is reached. Rejects on error.
 * `goal` - Goal instance

### bot.pathfinder.bestHarvestTool(block)
Returns the best harvesting tool in the inventory for the specified block.
 * `Returns` - `Item` instance or `null`
 * `block` - Block instance

### bot.pathfinder.getPathTo(movements, goal, timeout)
 * `Returns` - The path
 * `movements` - Movements instance
 * `goal` - Goal instance
 * `timeout` - number (optional, default `bot.pathfinder.thinkTimeout`)

### bot.pathfinder.getPathFromTo* (movements, startPos, goal, options = {})
Returns a Generator. The generator computes the path for as longs as no full path is found or `options.timeout` is reached. 
The generator will block the event loop until a path is found or `options.tickTimeout` (default to 50ms) is reached.
 * `Returns` - A generator instance. See [MDN function*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*).
 * `movements` - Movements instance
 * `startPos` - A Vec3 instance. The starting position to base the path search from. 
 * `goal` - Goal instance
 * `options` - A optional options object contains:
   * `optimizePath` - Boolean Optional. Optimize path for shortcuts like going to the next node in a strait line instead walking only diagonal or along axis.
   * `resetEntityIntersects` - Boolean Optional. Reset the `entityIntersections` index for `movements`. Default: true
   * `timeout` - Number Optional. Total computation timeout.
   * `tickTimeout` - Number Optional. Maximum amount off time before yielding.
   * `searchRadius` - Number Optional. Max distance to search.
   * `startMove` - instance of Move Optional. A optional starting position as a Move. Replaces `startPos` as the starting position.

### bot.pathfinder.setGoal(Goal, dynamic)
 * `goal` - Goal instance
 * `dynamic` - boolean (optional, default false)
 
### bot.pathfinder.setMovements(movements)
Assigns the movements config.
 * `movements` - Movements instance

### bot.pathfinder.stop()
Stops pathfinding as soon as the bot has reached the next node in the path (this prevents the bot from stopping mid-air). Emits `path_stop` when called.
Note: to force stop immediately, use `bot.pathfinder.setGoal(null)`

### bot.pathfinder.isMoving()
A function that checks if the bot is currently moving.
 * `Returns` - boolean

### bot.pathfinder.isMining()
A function that checks if the bot is currently mining blocks.
 * `Returns` - boolean

### bot.pathfinder.isBuilding()
A function that checks if the bot is currently placing blocks.
 * `Returns` - boolean

# Properties:
### bot.pathfinder.thinkTimeout
Think Timeout in milliseconds.
 * `Default` - `5000`

### bot.pathfinder.tickTimeout
How many milliseconds per tick are allocated to thinking.
 * `Default` - `40`

### bot.pathfinder.searchRadius
The search limiting radius, in blocks, if `-1` the search is not limited by distance.
 * `Default` - `-1`

# Movement class
This class configures how pathfinder plans its paths. It configures things like block breaking or different costs for moves. This class can be extended to add or change how pathfinder calculates its moves.

## Usage
Pathfinder instantiates the default movement class by itself if no instance is specified. If you want to change values you should create a new instance of the Movements class, change it's values and set it as pathfinders new movement class. 
### Example:
```js
const { Movements } = require('mineflayer-pathfinder') // Import the Movements class from pathfinder

bot.once('spawn', () => {
  // A new movement instance for specific behavior
  const defaultMove = new Movements(bot)

  defaultMove.allow1by1towers = false // Do not build 1x1 towers when going up
  defaultMove.canDig = false // Disable breaking of blocks when pathing 
  defaultMove.scafoldingBlocks.push(bot.registry.itemsByName['netherrack'].id) // Add nether rack to allowed scaffolding items
  bot.pathfinder.setMovements(defaultMove) // Update the movement instance pathfinder uses

  // Do pathfinder things
  // ...
})
```

## Movements class default properties
Movement class properties and their default values.
### canDig
Boolean to allow breaking blocks.
* Default `true`

### digCost
Additional cost for breaking blocks.
* Default - `1`

### placeCost
Additional cost for placing blocks.
* Default - `1`

### maxDropDown
Max drop down distance. Only considers drops that have blocks to land on.
* Default - `4`

### infiniteLiquidDropdownDistance
Option to ignore maxDropDown distance when the landing position is in water.
* Default - `true`

### liquidCost
Additional cost for interacting with liquids.
* Default - `1`

### entityCost
Additional cost for moving through an entity hitbox (besides passable ones).
* Default - `1`

### dontCreateFlow
Do not break blocks that touch liquid blocks.
* Default - `true`

### dontMineUnderFallingBlock
Do not break blocks that have a gravityBlock above.
* Default - `true`

### allow1by1towers
Allow pillaring up on 1x1 towers.
* Default - `true`

### allowFreeMotion
Allow to walk to the next node/goal in a straight line if terrain allows it.
* Default - `false`

### allowParkour
Allow parkour jumps like jumps over gaps bigger then 1 block.
* Default - `true`

### allowSprinting
Allow sprinting when moving.
* Default - `true`

### allowEntityDetection
Test for entities that may obstruct path or prevent block placement. Grabs updated entities every new path.
* Default - `true`

### entitiesToAvoid
Set of entities (by bot.registry name) to completely avoid when using entity detection.
* instance of `Set`

### passableEntities
Set of entities (by bot.registry name) to ignore when using entity detection.
* instance of `Set`
* Default - See lib/passableEntities.json

### interactableBlocks
Set of blocks (by bot.registry name) that pathfinder should not attempt to place blocks or 'right click' on.
* instance of `Set`
* Default - See lib/interactable.json

### blocksCantBreak
Set of block id's pathfinder cannot break. Includes chests and all unbreakable blocks.
* instance of `Set`

### blocksToAvoid
Set of block id's to avoid.
* instance of `Set`

### liquids
Set of liquid block id's.
* instance of `Set`

### climbables
Set of block id's that are climable. Note: Currently unused as pathfinder cannot use climables.
* instance of `Set`

### replaceables
Set of block id's that can be replaced when placing blocks.
* instance of `Set`

### scafoldingBlocks
Array of item id's that can be used as scaffolding blocks.
* Default - `[<scaffoldingItems>]`

### gravityBlocks
Set of block id's that can fall on bot's head.
* instance of `Set`

### fences
Set of block id's that are fences or blocks that have a collision box taller then 1 block.
* instance of `Set`

### carpets
Set of all carpet block id's or blocks that have a collision box smaller then 0.1. These blocks are considered safe to walk in.
* instance of `Set`

### exclusionAreasStep
An array of functions that define an area or block to be step on excluded. Every function in the array is parsed the Block the bot is planing to step on. Each function should return a positive number (includes 0) that defines extra cost for that specific Block. 0 means no extra cost, 100 means it is impossible for pathfinder to consider this move.
* Array of functions `(block: Block) => number`

### exclusionAreasBreak
An array of functions that define an area or block to be break excluded. Every function in the array is parsed the Block the bot is planing to break. Each function should return a positive number (includes 0) that defines extra cost for that specific Block. 0 means no extra cost, 100 means it is impossible for pathfinder to consider this move.
* Array of functions `(block: Block) => number`

### exclusionAreasPlace
An array of functions that define an area to be block placement excluded. Every function in the array is parsed the current Block the bot is planing to place a block inside (should be air or a replaceable block most of the time). Each function should return a positive number (includes 0) that defines extra cost for that specific Block. 0 means no extra cost, 100 makes it impossible for pathfinder to consider this move.
* Array of functions `(block: Block) => number`

### entityIntersections
A dictionary of the number of entities intersecting each floored block coordinate. Updated automatically for each path, but you may mix in your own entries before calculating a path if desired (generally for testing). To prevent this from being cleared automatically before generating a path,s see the [path gen options](#botpathfindergetpathfromto-movements-startpos-goal-options--). 
* Formatted entityIntersections['x,y,z'] = #ents
* Dictionary of costs `{string: number}`

### canOpenDoors
Enable feature to open Fence Gates. Unreliable and known to be buggy.
* Default - `false`

# Events:

### goal_reached
Called when the goal has been reached. Not called for dynamic goals.

### path_update
Called whenever the path is recalculated. Status may be:
 * `success` a path has been found
 * `partial` a partial path has been found, computations will continue next tick
 * `timeout` timed out
 * `noPath` no path was found

### goal_updated
Called whenever a new goal is assigned to the pathfinder.

### path_reset
Called when the path is reset, with a reason:
 * `goal_updated`
 * `movements_updated`
 * `block_updated`
 * `chunk_loaded`
 * `goal_moved`
 * `dig_error`
 * `no_scaffolding_blocks`
 * `place_error`
 * `stuck`

 ### path_stop
 Called when the pathing has been stopped by `bot.pathfinder.stop()`

# Goals:

### Goal
Abstract Goal class. Do not instantiate this class. Instead extend it to make a new Goal class.

Has abstract methods:
 - `heuristic(node)`
   * `node` - A path node
   * Returns a heuristic number value for a given node. Must be admissible – meaning that it never overestimates the actual cost to get to the goal.
 - `isEnd(node)`
   * `node`
   * Returns a boolean value if the given node is a end node. 

Implements default methods for:
 - `isValid()`
   * Always returns `true`
 - `hasChanged(node)`
   * `node` - A path node
   * Always returns `false`

### GoalBlock(x, y, z)
One specific block that the player should stand inside at foot level
 * `x` - Integer
 * `y` - Integer
 * `z` - Integer

### GoalNear(x, y, z, range)
A block position that the player should get within a certain radius of
 * `x` - Integer
 * `y` - Integer
 * `z` - Integer
 * `range` - Integer
 
### GoalXZ(x, z)
Useful for long-range goals that don't have a specific Y level
 * `x` - Integer
 * `z` - Integer

### GoalNearXZ(x, z, range)
Useful for finding builds that you don't have an exact Y level for, just an approximate X and Z level.
 * `x` - Integer
 * `z` - Integer
 * `range` - Integer

### GoalY(y)
Get to a Y level.
 * `y` - Integer


### GoalGetToBlock(x, y, z)
Don't get into the block, but get directly adjacent to it. Useful for chests.
 * `x` - Integer
 * `y` - Integer
 * `z` - Integer

### GoalCompositeAny(Array\<Goal>?)
A composite of many goals, any one of which satisfies the composite.
For example, a GoalCompositeAny of block goals for every oak log in loaded
chunks would result in it pathing to the easiest oak log to get to.
 * `Array` - Array of goals

### GoalCompositeAll(Array\<Goal>?)
A composite of multiple goals, requiring all of them to be satisfied.
 * `Array` - Array of goals

### GoalInvert(goal)
Inverts the goal.
 * `goal` - Goal to invert

### GoalFollow(entity, range)
Follows an entity.
 * `entity` - Entity instance
 * `range` - Integer

### GoalPlaceBlock(pos, world, options)
Position the bot in order to place a block.
 * `pos` - Vec3 the position of the placed block
 * `world` - the world of the bot (Can be accessed with `bot.world`)
 * `options` - object containing all optionals properties:
   * `range` - maximum distance from the clicked face
   * `faces` - the directions of the faces the player can click
   * `facing` - the direction the player must be facing
   * `facing3D` - boolean, facing is 3D (true) or 2D (false)
   * `half` - `top` or `bottom`, the half that must be clicked

 ### GoalLookAtBlock(pos, world, options = {})
 Path into a position were a blockface of block at pos is visible. Fourth argument is optional and contains extra options.
  * `pos` - Vec3 the block position to look at
  * `world` - the world of the bot (Can be accessed with `bot.world`)
  * `options` - object containing all optionals properties:
    * `reach` - number maximum distance from the clicked face. Default `4.5`
    * `entityHeight` - number Default is `1.6`

 ### GoalBreakBlock(x, y, z, bot, options)
 Deprecated. Wrapper for GoalLookAtBlock. Use GoalLookAtBlock instead.


================================================
FILE: test/internalTest.js
================================================
/* eslint-env mocha */

const mineflayer = require('mineflayer')
const { goals, pathfinder, Movements } = require('mineflayer-pathfinder')
const { Vec3 } = require('vec3')
const mc = require('minecraft-protocol')
const assert = require('assert')
const { v4: uuidv4 } = require('uuid')
const PEntity = require('prismarine-entity')
const { once, on } = require('events')
const { Schematic } = require('prismarine-schematic')
const { promises: fs } = require('fs')
const path = require('path')
const Physics = require('../lib/physics')

const Version = '1.16.5'
const ServerPort = 25567

/**
 * Returns a flat bedrock chunk with a single gold block in it.
 * @param {string} Version version
 * @returns {import('prismarine-chunk').Chunk}
 */
function flatMap (Version) {
  const targetBlock = new Vec3(12, 1, 8) // a gold block away from the spawn position
  const Block = require('prismarine-block')(Version)
  const Chunk = require('prismarine-chunk')(Version)
  const mcData = require('minecraft-data')(Version)
  const chunk = new Chunk()
  chunk.initialize((x, y, z) => {
    if (targetBlock.x === x && targetBlock.y === y && targetBlock.z === z) {
      return new Block(mcData.blocksByName.gold_block.id, 1, 0)
    }
    return y === 0 ? new Block(mcData.blocksByName.bedrock.id, 1, 0) : new Block(mcData.blocksByName.air.id, 1, 0) // Bedrock floor
  })
  return chunk
}

/**
 * Reads the schematic parkour1.schem and returns a chunk containing the schematic content.
 * @param {string} Version version to be used
 * @returns {Promise<import('prismarine-chunk').Chunk>}
 */
async function parkourMap (Version) {
  const pwd = path.join(__dirname, './schematics/parkour1.schem')
  const readSchem = await Schematic.read(await fs.readFile(pwd), '1.18.2')
  const Block = require('prismarine-block')(Version)
  const Chunk = require('prismarine-chunk')(Version)
  const mcData = require('minecraft-data')(Version)
  const chunk = new Chunk()
  chunk.initialize((x, y, z) => {
    const block = readSchem.getBlock(new Vec3(x, y, z))
    if (block.name === 'air') return null
    // Different versions off schematic are not compatible with each other. Assumes block names between versions stay the same.
    const blockVersion = mcData.blocksByName[block.name]
    if (!blockVersion) return null
    return new Block(blockVersion.id, 1, 0)
  })
  return chunk
}

function generateChunkPacket (chunk) {
  const lights = chunk.dumpLight()
  return {
    x: 0,
    z: 0,
    groundUp: true,
    biomes: chunk.dumpBiomes !== undefined ? chunk.dumpBiomes() : undefined,
    heightmaps: {
      type: 'compound',
      name: '',
      value: {
        MOTION_BLOCKING: { type: 'longArray', value: new Array(36).fill([0, 0]) }
      }
    }, // send fake heightmap
    bitMap: chunk.getMask(),
    chunkData: chunk.dump(),
    blockEntities: [],
    trustEdges: false,
    skyLightMask: lights?.skyLightMask,
    blockLightMask: lights?.blockLightMask,
    emptySkyLightMask: lights?.emptySkyLightMask,
    emptyBlockLightMask: lights?.emptyBlockLightMask,
    skyLight: lights?.skyLight,
    blockLight: lights?.blockLight
  }
}

/**
 * Create a new 1.16 server and handle when clients connect.
 * @param {import('minecraft-protocol').Server} server
 * @param {import('vec3').Vec3} spawnPos
 * @param {string} Version
 * @param {boolean} useLoginPacket
 * @returns {Promise<void>}
 */
async function newServer (server, chunk, spawnPos, Version, useLoginPacket) {
  const mcData = require('minecraft-data')(Version)
  server = mc.createServer({
    'online-mode': false,
    version: Version,
    // 25565 - local server, 25566 - proxy server
    port: ServerPort
  })
  server.on('login', (client) => {
    let loginPacket
    if (useLoginPacket) {
      loginPacket = mcData.loginPacket
    } else {
      loginPacket = {
        entityId: 0,
        levelType: 'fogetaboutit',
        gameMode: 0,
        previousGameMode: 255,
        worldNames: ['minecraft:overworld'],
        dimension: 0,
        worldName: 'minecraft:overworld',
        hashedSeed: [0, 0],
        difficulty: 0,
        maxPlayers: 20,
        reducedDebugInfo: 1,
        enableRespawnScreen: true
      }
    }

    client.write('login', loginPacket)

    client.write('map_chunk', generateChunkPacket(chunk))

    client.write('position', {
      x: spawnPos.x,
      y: spawnPos.y,
      z: spawnPos.z,
      yaw: 0,
      pitch: 0,
      flags: 0x00
    })
  })

  await once(server, 'listening')
  return server
}

function add1x2Weight (entityIntersections, posX, posY, posZ, weight = 1) {
  entityIntersections[`${posX},${posY},${posZ}`] = entityIntersections[`${posX},${posY},${posZ}`] ?? 0
  entityIntersections[`${posX},${posY + 1},${posZ}`] = entityIntersections[`${posX},${posY + 1},${posZ}`] ?? 0

  entityIntersections[`${posX},${posY},${posZ}`] += weight
  entityIntersections[`${posX},${posY + 1},${posZ}`] += weight
}

describe('pathfinder Goals', function () {
  const mcData = require('minecraft-data')(Version)

  const targetBlock = new Vec3(12, 1, 8) // a gold block away from the spawn position
  const spawnPos = new Vec3(8.5, 1, 8.5) // Center of the chunk & center of the block

  /** @type { import('mineflayer').Bot & { pathfinder: import('mineflayer-pathfinder').Pathfinder }} */
  let bot
  /** @type { import('minecraft-protocol').Server } */
  let server

  before(async () => {
    const chunk = flatMap(Version)
    server = await newServer(server, chunk, spawnPos, Version, true)
    bot = mineflayer.createBot({
      username: 'player',
      version: Version,
      port: ServerPort
    })
    await once(bot, 'chunkColumnLoad')
  })
  after(() => {
    bot.end()
    bot = null
    server.close()
  })

  describe('Goals', () => {
    beforeEach(() => {
      bot.entity.position = spawnPos.clone()
    })

    it('GoalBlock', () => {
      const goal = new goals.GoalBlock(targetBlock.x, targetBlock.y, targetBlock.z)
      assert.ok(!goal.isEnd(bot.entity.position))
      bot.entity.position = targetBlock.clone()
      assert.ok(goal.isEnd(bot.entity.position))
    })

    it('GoalNear', () => {
      const goal = new goals.GoalNear(targetBlock.x, targetBlock.y, targetBlock.z, 1)
      assert.ok(!goal.isEnd(bot.entity.position))
      bot.entity.position = targetBlock.offset(1, 0, 0)
      assert.ok(goal.isEnd(bot.entity.position))
    })

    it('GoalXZ', () => {
      const goal = new goals.GoalXZ(targetBlock.x, targetBlock.z)
      assert.ok(!goal.isEnd(bot.entity.position))
      bot.entity.position = targetBlock.offset(0, 1, 0)
      assert.ok(goal.isEnd(bot.entity.position))
    })

    it('GoalNearXZ', () => {
      const goal = new goals.GoalNearXZ(targetBlock.x, targetBlock.z, 1)
      assert.ok(!goal.isEnd(bot.entity.position))
      bot.entity.position = targetBlock.offset(1, 0, 0)
      assert.ok(goal.isEnd(bot.entity.position))
    })

    it('GoalY', () => {
      const goal = new goals.GoalY(targetBlock.y + 1)
      assert.ok(!goal.isEnd(bot.entity.position))
      bot.entity.position = targetBlock.offset(0, 1, 0)
      assert.ok(goal.isEnd(bot.entity.position))
    })

    it('GoalGetToBlock', () => {
      const goal = new goals.GoalGetToBlock(targetBlock.x, targetBlock.y, targetBlock.z)
      assert.ok(!goal.isEnd(bot.entity.position))
      bot.entity.position = targetBlock.offset(1, 0, 0)
      assert.ok(goal.isEnd(bot.entity.position))
    })

    it('GoalCompositeAny', () => {
      const targetBlock2 = new Vec3(10, 1, 0)
      const goal1 = new goals.GoalBlock(targetBlock.x, targetBlock.y, targetBlock.z)
      const goal2 = new goals.GoalBlock(targetBlock2.x, targetBlock2.y, targetBlock2.z)
      const goalComposite = new goals.GoalCompositeAny()
      goalComposite.goals = [goal1, goal2]
      assert.ok(!goalComposite.isEnd(bot.entity.position))
      bot.entity.position = targetBlock.clone()
      assert.ok(goalComposite.isEnd(bot.entity.position)) // target block 1
      bot.entity.position = targetBlock2.clone()
      assert.ok(goalComposite.isEnd(bot.entity.position)) // target block 2
    })

    it('GoalCompositeAll', () => {
      const targetBlock = new Vec3(2, 1, 0)
      const block2 = new Vec3(3, 1, 0)
      const goal1 = new goals.GoalBlock(targetBlock.x, targetBlock.y, targetBlock.z)
      const goal2 = new goals.GoalNear(block2.x, block2.y, block2.z, 2)
      const goalComposite = new goals.GoalCompositeAll()
      goalComposite.goals = [goal1, goal2]
      assert.ok(!goalComposite.isEnd(bot.entity.position))
      bot.entity.position = targetBlock.offset(0, 0, 0)
      assert.ok(goalComposite.isEnd(bot.entity.position))
    })

    it('GoalInvert', () => {
      const goalBlock = new goals.GoalBlock(targetBlock.x, targetBlock.y, targetBlock.z)
      const goal = new goals.GoalInvert(goalBlock)
      bot.entity.position = targetBlock.clone()
      assert.ok(!goal.isEnd(bot.entity.position))
      bot.entity.position = new Vec3(0, 1, 0)
      assert.ok(goal.isEnd(bot.entity.position))
    })

    it('GoalPlaceBlock', () => {
      const placeTarget = targetBlock.offset(0, 1, 0)
      const goal = new goals.GoalPlaceBlock(placeTarget, bot.world, {})
      bot.entity.position = targetBlock.offset(-5, 0, 0) // to far away to reach
      assert.ok(!goal.isEnd(bot.entity.position.floored()))
      bot.entity.position = targetBlock.offset(-2, 0, 0)
      assert.ok(goal.isEnd(bot.entity.position.floored()))
    })

    it('GoalLookAtBlock', () => {
      const breakTarget = targetBlock.clone() // should be a gold block or any other block thats dig able
      const goal = new goals.GoalLookAtBlock(breakTarget, bot.world, { reach: 3 })
      assert.ok(!goal.isEnd(bot.entity.position.floored()))
      bot.entity.position = targetBlock.offset(-2, 0, 0) // should now be close enough
      assert.ok(goal.isEnd(bot.entity.position.floored()))
    })
  })

  describe('Goals with entity', () => {
    beforeEach(() => {
      bot.entity.position = spawnPos.clone()
    })
    before((done) => {
      const Entity = PEntity(Version)
      const chicken = new Entity(mcData.entitiesByName.chicken.id)
      const client = Object.values(server.clients)[0]
      client.write('spawn_entity', { // Might only work for 1.16
        entityId: chicken.id,
        objectUUID: uuidv4(),
        type: chicken.type,
        x: targetBlock.x,
        y: targetBlock.y + 1,
        z: targetBlock.z,
        pitch: 0,
        yaw: 0,
        objectData: 0,
        velocityX: 0,
        velocityY: 0,
        velocityZ: 0
      })
      setTimeout(done, 100)
    })

    it('GoalFollow', () => {
      const entity = bot.nearestEntity() || { position: targetBlock.offset(0, 1, 0) }
      const goal = new goals.GoalFollow(entity, 1)
      assert.ok(!goal.isEnd(bot.entity.position))
      bot.entity.position = targetBlock.clone()
      assert.ok(goal.isEnd(bot.entity.position))
    })
  })
})

describe('pathfinder events', function () {
  const mcData = require('minecraft-data')(Version)

  const targetBlock = new Vec3(12, 1, 8) // a gold block away from the spawn position
  const spawnPos = new Vec3(8.5, 1, 8.5) // Center of the chunk & center of the block

  /** @type { import('mineflayer').Bot & { pathfinder: import('mineflayer-pathfinder').Pathfinder }} */
  let bot
  /** @type { import('minecraft-protocol').Server } */
  let server

  before(async () => {
    const chunk = flatMap(Version)
    server = await newServer(server, chunk, spawnPos, Version, true)
    bot = mineflayer.createBot({
      username: 'player',
      version: Version,
      port: ServerPort
    })
    await once(bot, 'chunkColumnLoad')
    bot.loadPlugin(pathfinder)
    bot.pathfinder.setMovements(new Movements(bot, mcData))
  })
  after(() => server.close())

  describe('events', async function () {
    beforeEach(() => {
      bot.entity.position = spawnPos.clone()
    })
    afterEach((done) => {
      bot.pathfinder.setGoal(null)
      setTimeout(done)
      const listeners = ['goal_reached', 'goal_updated', 'path_update', 'path_stop']
      listeners.forEach(l => bot.removeAllListeners(l))
    })

    it('goal_reached', function (done) {
      this.timeout(3000)
      this.slow(1000)
      bot.once('goal_reached', () => done())
      bot.pathfinder.setGoal(new goals.GoalNear(targetBlock.x, targetBlock.y, targetBlock.z, 1))
    })

    it('goal_updated', function (done) {
      this.timeout(100)
      bot.once('goal_updated', () => done())
      bot.pathfinder.setGoal(new goals.GoalNear(targetBlock.x, targetBlock.y, targetBlock.z, 1))
    })

    it('path_update', function (done) {
      this.timeout(3000)
      this.slow(1000)
      bot.pathfinder.setGoal(new goals.GoalNear(targetBlock.x, targetBlock.y, targetBlock.z, 1))
      bot.once('path_update', () => done())
    })

    it('path_stop', function (done) {
      this.timeout(3000)
      this.slow(1000)
      bot.pathfinder.setGoal(new goals.GoalNear(targetBlock.x, targetBlock.y, targetBlock.z, 1))
      bot.once('path_stop', () => done())
      bot.pathfinder.stop()
    })
  })
})

describe('pathfinder util functions', function () {
  const mcData = require('minecraft-data')(Version)
  const Item = require('prismarine-item')(Version)

  const targetBlock = new Vec3(12, 1, 8) // a gold block away from the spawn position
  const spawnPos = new Vec3(8.5, 1, 8.5) // Center of the chunk & center of the block

  const itemsToGive = [new Item(mcData.itemsByName.diamond_pickaxe.id, 1), new Item(mcData.itemsByName.dirt.id, 64)]

  /** @type { import('mineflayer').Bot & { pathfinder: import('mineflayer-pathfinder').Pathfinder }} */
  let bot
  /** @type { import('minecraft-protocol').Server } */
  let server

  before(async () => {
    const chunk = flatMap(Version)
    server = await newServer(server, chunk, spawnPos, Version, true)
    bot = mineflayer.createBot({
      username: 'player',
      version: Version,
      port: ServerPort
    })
    await once(bot, 'chunkColumnLoad')
    itemsToGive.forEach(item => {
      const slot = bot.inventory.firstEmptyHotbarSlot()
      bot.inventory.slots[slot] = item
    })
    bot.loadPlugin(pathfinder)
    bot.pathfinder.setMovements(new Movements(bot, mcData))
  })
  after(() => server.close())

  describe('paththing', function () {
    this.afterEach((done) => {
      bot.pathfinder.setGoal(null)
      bot.entity.position = spawnPos.clone()
      bot.stopDigging()
      setTimeout(() => done())
    })

    it('Goto', async function () {
      this.timeout(3000)
      this.slow(1500)
      await bot.pathfinder.goto(new goals.GoalGetToBlock(targetBlock.x, targetBlock.y, targetBlock.z))
    })

    it('isMoving', function (done) {
      bot.pathfinder.setGoal(new goals.GoalGetToBlock(targetBlock.x, targetBlock.y, targetBlock.z))
      const foo = () => {
        if (bot.pathfinder.isMoving()) {
          bot.removeListener('physicTick', foo)
          done()
        }
      }
      bot.on('physicTick', foo)
    })

    // Note: Ordering seams to matter when running the isBuilding test. If run after isMining isBuilding does not seam to work.
    it('isBuilding', function (done) {
      this.timeout(5000)
      this.slow(1500)

      bot.pathfinder.setGoal(new goals.GoalBlock(targetBlock.x, targetBlock.y + 2, targetBlock.z))
      const foo = () => {
        if (bot.pathfinder.isBuilding()) {
          bot.removeListener('physicTick', foo)
          bot.stopDigging()
          done()
        }
      }
      bot.on('physicTick', foo)
    })

    it('isMining', function (done) {
      this.timeout(5000)
      this.slow(1500)

      bot.pathfinder.setGoal(new goals.GoalBlock(targetBlock.x, targetBlock.y, targetBlock.z))
      const foo = () => {
        if (bot.pathfinder.isMining()) {
          bot.removeListener('physicTick', foo)
          bot.stopDigging()
          done()
        }
      }
      bot.on('physicTick', foo)
    })
  })

  it('bestHarvestTool', function () {
    const block = bot.blockAt(targetBlock)
    const tool = bot.pathfinder.bestHarvestTool(block)
    assert.deepStrictEqual(tool, itemsToGive[0])
  })

  it('getPathTo', function () {
    const path = bot.pathfinder.getPathTo(bot.pathfinder.movements, new goals.GoalGetToBlock(targetBlock.x, targetBlock.y, targetBlock.z))
    // All depends on the actually path that gets generated. If target block is moved some were else these values have to change.
    assert.strictEqual(path.status, 'success')
    assert.ok(path.visitedNodes < 5, `Generated path visited nodes to high (${path.visitedNodes} < 5)`)
    assert.ok(path.generatedNodes < 30, `Generated path nodes to high (${path.generatedNodes} < 30)`)
    assert.ok(path.path.length === 3, `Generated path length wrong (${path.path.length} === 3)`)
    assert.ok(path.time < 50, `Generated path took too long (${path.time} < 50)`)
  })
})

describe('pathfinder Movement', function () {
  const mcData = require('minecraft-data')(Version)
  const Item = require('prismarine-item')(Version)
  const Block = require('prismarine-block')(Version)

  const targetBlock = new Vec3(12, 1, 8) // a gold block away from the spawn position
  const spawnPos = new Vec3(8.5, 1, 8.5) // Center of the chunk & center of the block

  /** @type { import('mineflayer').Bot & { pathfinder: import('mineflayer-pathfinder').Pathfinder }} */
  let bot
  /** @type { import('minecraft-protocol').Server } */
  let server
  /** @type { import('mineflayer-pathfinder').Movements } */
  let defaultMovement

  const itemsToGive = [new Item(mcData.itemsByName.diamond_pickaxe.id, 1), new Item(mcData.itemsByName.dirt.id, 64)]

  before(async () => {
    const chunk = flatMap(Version)
    server = await newServer(server, chunk, spawnPos, Version, true)
    bot = mineflayer.createBot({
      username: 'player',
      version: Version,
      port: ServerPort
    })
    await once(bot, 'chunkColumnLoad')
    itemsToGive.forEach(item => {
      const slot = bot.inventory.firstEmptyHotbarSlot()
      bot.inventory.slots[slot] = item
    })
    defaultMovement = new Movements(bot, mcData)
    bot.loadPlugin(pathfinder)
    bot.pathfinder.setMovements(defaultMovement)
  })
  after(() => server.close())

  it('countScaffoldingItems', function () {
    assert.strictEqual(defaultMovement.countScaffoldingItems(), 64)
  })

  it('getScaffoldingItem', function () {
    assert.strictEqual(defaultMovement.getScaffoldingItem(), itemsToGive[1])
  })

  it('getBlock', function () {
    assert.ok(defaultMovement.getBlock(targetBlock, 0, 0, 0).type === mcData.blocksByName.gold_block.id)
  })

  describe('safeToBreak world editing', function () {
    this.afterAll(async () => {
      defaultMovement.canDig = true
      await bot.world.setBlock(targetBlock.offset(1, 0, 0), new Block(mcData.blocksByName.air.id, 0))
    })

    it('safeToBreak', async function () {
      const block = bot.blockAt(targetBlock)
      assert.ok(defaultMovement.safeToBreak(block))
      defaultMovement.canDig = false
      assert.ok(!defaultMovement.safeToBreak(block))
      defaultMovement.canDig = true
      await bot.world.setBlock(targetBlock.offset(1, 0, 0), new Block(mcData.blocksByName.water.id, 0, 0))
      assert.ok(!defaultMovement.safeToBreak(block))
    })
  })

  it('safeOrBreak', function () {
    const block = defaultMovement.getBlock(targetBlock, 0, 0, 0)
    const toBreak = []
    const extraValue = defaultMovement.safeOrBreak(block, toBreak)
    assert.ok(extraValue < 100, `safeOrBreak to high for block (${extraValue} < 100)`)
    assert.ok(toBreak.length === 1, `safeOrBreak toBreak array wrong length ${toBreak.length} (${toBreak.length} === 1)`)
  })

  it('getMoveJumpUp', function () {
    const block = defaultMovement.getBlock(targetBlock, -1, 0, 0)
    const dir = new Vec3(1, 0, 0)
    const neighbors = []
    defaultMovement.getMoveJumpUp(block.position, dir, neighbors)
    assert.ok(neighbors.length === 1, `getMoveJumpUp neighbors not right length (${neighbors.length} === 1)`)
  })

  it('getMoveForward', function () {
    const dir = new Vec3(1, 0, 0)
    const neighbors = []
    defaultMovement.getMoveForward(targetBlock, dir, neighbors)
    assert.ok(neighbors.length === 1, `getMoveForward neighbors not right length (${neighbors.length} === 1)`)
  })

  it('getMoveDiagonal', function () {
    const dir = new Vec3(1, 0, 0)
    const neighbors = []
    defaultMovement.getMoveDiagonal(targetBlock, dir, neighbors)
    assert.ok(neighbors.length === 1, `getMoveDiagonal neighbors not right length (${neighbors.length} === 1)`)
  })

  it('getLandingBlock', function () {
    const node = targetBlock.offset(-1, 3, 0)
    const dir = new Vec3(1, 0, 0)
    const block = defaultMovement.getLandingBlock(node, dir)
    assert.ok(block != null, 'Landing block is null')
    if (!block) return
    assert.ok(block.type === mcData.blocksByName.air.id, `getLandingBlock not the right block (${block.name} === air)`)
    assert.ok(block.position.offset(0, -1, 0).distanceSquared(targetBlock) === 0, `getLandingBlock not landing (${block.position.offset(0, -1, 0).distanceSquared(targetBlock)}) on target block: ${defaultMovement.getBlock(block.position, 0, -1, 0).name}`)
  })

  it('getMoveDropDown', function () {
    const dir = new Vec3(1, 0, 0)
    const neighbors = []
    defaultMovement.getMoveDropDown(targetBlock.offset(-1, 4, 0), dir, neighbors)
    assert.ok(neighbors.length === 1, `getMoveDropDown neighbors not right length (${neighbors.length} === 1)`)
  })

  it('getMoveDown', function () {
    const neighbors = []
    defaultMovement.getMoveDown(targetBlock.offset(0, 4, 0), neighbors)
    assert.ok(neighbors.length === 1, `getMoveDown neighbors not right length (${neighbors.length} === 1)`)
  })

  it('getMoveUp', function () {
    const neighbors = []
    defaultMovement.getMoveUp(targetBlock.offset(0, 1, 0), neighbors)
    assert.ok(neighbors.length === 1, `getMoveUp neighbors not right length (${neighbors.length} === 1)`)
  })

  it('getNeighbors', function () {
    const neighbors = defaultMovement.getNeighbors(targetBlock.offset(0, 1, 0))
    assert.ok(neighbors.length > 0, 'getNeighbors length 0')
  })
})

describe('Parkour path test', function () {
  const mcData = require('minecraft-data')(Version)

  const spawnPos = new Vec3(8.5, 1, 8.5) // Center of the chunk & center of the block

  /** @type { import('mineflayer').Bot & { pathfinder: import('mineflayer-pathfinder').Pathfinder }} */
  let bot
  /** @type { import('minecraft-protocol').Server } */
  let server
  /** @type { import('mineflayer-pathfinder').Movements } */
  let defaultMovement

  const parkourSpawn1 = new Vec3(0.5, 3, 12.5)
  const parkourSpawn2 = new Vec3(5.5, 3, 12.5)

  before(async () => {
    this.timeout(5000)
    const chunk = await parkourMap(Version)
    server = await newServer(server, chunk, spawnPos, Version, true)
    bot = mineflayer.createBot({
      username: 'player',
      version: Version,
      port: ServerPort
    })
    await once(bot, 'chunkColumnLoad')
    defaultMovement = new Movements(bot, mcData)
    bot.loadPlugin(pathfinder)
    bot.pathfinder.setMovements(defaultMovement)
  })
  after(() => server.close())

  it('getMoveParkourForward-1', function () {
    const dirs = [new Vec3(0, 0, 1), new Vec3(0, 0, -1)]
    for (let i = 0; i < dirs.length; i++) {
      const dir = dirs[i] // only 2 dirs as the schematic parkour1.schem only has 2 other blocks to path to.
      const neighbors = []
      defaultMovement.getMoveParkourForward(parkourSpawn1, dir, neighbors)
      assert.ok(neighbors.length === 1, `getMoveParkourForward jump off gold block neighbors not right length (${neighbors.length} === 1)`)
    }
  })

  it('getMoveParkourForward-2', function () {
    const dirs = [new Vec3(1, 0, 0), new Vec3(0, 0, 1), new Vec3(-1, 0, 0), new Vec3(0, 0, -1)]
    for (let i = 0; i < dirs.length; i++) {
      const dir = dirs[i]
      const neighbors = []
      defaultMovement.getMoveParkourForward(parkourSpawn2, dir, neighbors)
      assert.ok(neighbors.length === 1, `getMoveParkourForward jump off gold block neighbors not right length (${neighbors.length} === 1)`)
    }
  })
})

describe('Physics test', function () {
  const mcData = require('minecraft-data')(Version)

  const spawnPos = new Vec3(8.5, 1, 8.5) // Center of the chunk & center of the block

  /** @type { import('mineflayer').Bot & { pathfinder: import('mineflayer-pathfinder').Pathfinder }} */
  let bot
  /** @type { import('minecraft-protocol').Server } */
  let server
  /** @type { import('mineflayer-pathfinder').Movements } */
  let defaultMovement

  const parkourSpawn1 = new Vec3(0.5, 3, 12.5)
  // const parkourSpawn2 = new Vec3(5.5, 3, 12.5)

  before(async () => {
    this.timeout(5000)
    const chunk = await parkourMap(Version)
    server = await newServer(server, chunk, spawnPos, Version, true)
    bot = mineflayer.createBot({
      username: 'player',
      version: Version,
      port: ServerPort
    })
    await once(bot, 'chunkColumnLoad')
    defaultMovement = new Movements(bot, mcData)
    bot.loadPlugin(pathfinder)
    bot.pathfinder.setMovements(defaultMovement)
  })
  after(() => server.close())

  it('simulateUntil', async function () {
    this.slow(1000)
    this.timeout(2000)
    const ticksToSimulate = 10
    const ticksPressForward = 5

    bot.entity.position = parkourSpawn1.clone()
    bot.entity.velocity = new Vec3(0, 0, 0)

    // Wait for the bot to be on the ground so bot.entity.onGround == true
    bot.clearControlStates()
    await once(bot, 'physicTick')
    await once(bot, 'physicTick')

    const physics = new Physics(bot)

    const simulatedSteps = []
    const realSteps = []

    const controller = (state, counter) => {
      state.control.forward = counter <= ticksPressForward
      state.control.jump = counter <= ticksPressForward
      simulatedSteps.push(state.pos.toString() + ' Input:' + String(counter <= ticksPressForward))
    }
    const state = physics.simulateUntil(() => false, controller, ticksToSimulate)
    simulatedSteps.push(state.pos.toString() + ' Input:false')

    // We have to be carful to not mess up the event scheduling. for await on(bot, 'physicTick') seams to work.
    // A for loop with just await once(bot, 'physicTick') does not always seam to work. What also works is attaching
    // a listener to bot with bot.on('physicTick', listener) but this is a lot nicer.
    let tick = 0
    for await (const _ of on(bot, 'physicTick')) { // eslint-disable-line no-unused-vars
      bot.setControlState('forward', tick <= ticksPressForward)
      bot.setControlState('jump', tick <= ticksPressForward)
      realSteps.push(bot.entity.position.toString() + ' Input:' + String(tick <= ticksPressForward))
      tick++
      if (tick > ticksToSimulate) break
    }

    bot.clearControlStates()
    // console.info(bot.entity.position.toString(), console.info(state.pos.toString()))
    assert.ok(bot.entity.position.distanceSquared(state.pos) < 0.01,
      `Simulated states don't match Bot: ${bot.entity.position.toString()} !== Simulation: ${state.pos.toString()}`
      // + '\nSimulated Steps:\n'
      // + simulatedSteps.join('\n') + '\n'
      // + 'Real steps:\n'
      // + realSteps.join('\n')
    )
  })

  // TODO: write test for simulateUntilNextTick
})

describe('pathfinder entity avoidance test', function () {
  const mcData = require('minecraft-data')(Version)

  const patherOptions = { resetEntityIntersects: false }
  const maxPathTime = 50

  const spawnPos = new Vec3(8.5, 1.0, 8.5) // Center of the chunk & center of the block

  /** @type { import('mineflayer').Bot & { pathfinder: import('mineflayer-pathfinder').Pathfinder }} */
  let bot
  /** @type { import('minecraft-protocol').Server } */
  let server
  /** @type { import('prismarine-chunk').Chunk } */
  let chunk

  before(async () => {
    chunk = await parkourMap(Version)
    server = await newServer(server, chunk, spawnPos, Version, true)
    bot = mineflayer.createBot({
      username: 'player',
      version: Version,
      port: ServerPort
    })
    await once(bot, 'chunkColumnLoad')

    bot.loadPlugin(pathfinder)
    bot.pathfinder.setMovements(new Movements(bot, mcData))
  })

  after(() => {
    bot.end()
    bot = null
    server.close()
  })

  /**
  * Ensure algorithm does not impede performance when handling a large number of entities
  */
  it('entityIndexPerformance', () => {
    const { performance } = require('perf_hooks')

    const targetBlock = new Vec3(11.5, 2.0, 10.5) // a gold block away from the spawn position
    const startPos = new Vec3(11.5, 2.0, 14.5) // Start point for test

    const goal = new goals.GoalGetToBlock(targetBlock.x, targetBlock.y, targetBlock.z)

    for (let i = 1; i <= 10000; i++) {
      const pos = (i % 2) === 0 ? new Vec3(10.5, 2.0, 12.5) : new Vec3(12.5, 2.0, 12.5)
      bot.entities[i] = { name: 'testEntity', position: pos, height: 2.0, width: 1.0 }
    }

    const beforeTime = performance.now()

    const generator = bot.pathfinder.getPathFromTo(bot.pathfinder.movements, startPos, goal)
    const { value: { result } } = generator.next()

    const timeElapsed = performance.now() - beforeTime

    bot.pathfinder.movements.clearCollisionIndex()
    for (let i = 1; i <= 10000; i++) {
      delete bot.entities[i]
    }

    assert.ok(timeElapsed < maxPathTime, `Generated path took too long (${result.time} < ${maxPathTime})`)
  })

  /**
   * Tests if bot will prefer a basic path with less entities
   * The test course is a 3x3x2 with a divider in the center
   * [O] = Open, [W] = Wall, [S] = Start, [E] = End
   *    W E W
   *  W O O O W
   *  W O W O W
   *  W O O O W
   *    W S W
   */
  describe('Weighted Path Avoidance', () => {
    const targetBlock = new Vec3(11.5, 2.0, 10.5) // a gold block away from the spawn position
    const startPos = new Vec3(11.5, 2.0, 14.5) // Start point for test
    const firstLeftNode = new Vec3(10.5, 2.0, 12.5)
    const firstRightNode = new Vec3(12.5, 2.0, 12.5)

    const goal = new goals.GoalGetToBlock(targetBlock.x, targetBlock.y, targetBlock.z)

    beforeEach((done) => {
      bot.pathfinder.movements.clearCollisionIndex()
      setTimeout(done, 100)
    })

    /**
     * By default, algorithm will favor the Left Path
     * [X] = Ent, [O] = Open, [W] = Wall
     *   O O O
     *   O W O
     *   O O O
     */
    it('defaultPath', () => {
      const generator = bot.pathfinder.getPathFromTo(bot.pathfinder.movements, startPos, goal, patherOptions)
      const { value: { result } } = generator.next()
      const path = result.path

      // Look at first and second nodes incase diagonal movements are used
      const leftBranch = (path[0].equals(firstLeftNode) || path[1].equals(firstLeftNode))
      const rightBranch = (path[0].equals(firstRightNode) || path[1].equals(firstRightNode))

      // All depends on the actually path that gets generated. If target block is moved some were else these values have to change.
      assert.strictEqual(result.status, 'success')
      assert.ok(result.time < maxPathTime, `Generated path took too long (${result.time} < ${maxPathTime})`)
      assert.ok(path.length === 3, `Generated path length wrong (${path.length} === 3)`)
      assert.ok(leftBranch === true, `Generated path did not follow Left Branch [Left Branch: ${leftBranch}, Right Branch: ${rightBranch}]`)
    })

    /**
     * Ensure path with weight is avoided
     * [X] = Ent, [O] = Open, [W] = Wall
     *   O O O
     *   O W X
     *   O O O
     */
    it('rightBranchObstructed', () => {
      add1x2Weight(bot.pathfinder.movements.entityIntersections, 12, 2, 12)

      const generator = bot.pathfinder.getPathFromTo(bot.pathfinder.movements, startPos, goal, patherOptions)
      const { value: { result } } = generator.next()
      const path = result.path

      // Look at first and second nodes incase diagonal movements are used
      const leftBranch = (path[0].equals(firstLeftNode) || path[1].equals(firstLeftNode))
      const rightBranch = (path[0].equals(firstRightNode) || path[1].equals(firstRightNode))

      // All depends on the actually path that gets generated. If target block is moved some were else these values have to change.
      assert.strictEqual(result.status, 'success')
      assert.ok(result.time < maxPathTime, `Generated path took too long (${result.time} < ${maxPathTime})`)
      assert.ok(path.length === 3, `Generated path length wrong (${path.length} === 3)`)
      assert.ok(leftBranch === true, `Generated path did not follow Left Branch [Left Branch: ${leftBranch}, Right Branch: ${rightBranch}]`)
    })

    /**
     * Ensure path with more weight is avoided
     * [X] = Ent, [O] = Open, [W] = Wall
     *   O O O
     *   X W X
     *   X O O
     */
    it('leftBranchMoreObstructed', () => {
      add1x2Weight(bot.pathfinder.movements.entityIntersections, 12, 2, 12)
      add1x2Weight(bot.pathfinder.movements.entityIntersections, 10, 2, 12)
      add1x2Weight(bot.pathfinder.movements.entityIntersections, 10, 2, 13)

      const generator = bot.pathfinder.getPathFromTo(bot.pathfinder.movements, startPos, goal, patherOptions)
      const { value: { result } } = generator.next()
      const path = result.path

      // Look at first and second nodes incase diagonal movements are used
      const leftBranch = (path[0].equals(firstLeftNode) || path[1].equals(firstLeftNode))
      const rightBranch = (path[0].equals(firstRightNode) || path[1].equals(firstRightNode))

      // All depends on the actually path that gets generated. If target block is moved some were else these values have to change.
      assert.strictEqual(result.status, 'success')
      assert.ok(result.time < maxPathTime, `Generated path took too long (${result.time} < ${maxPathTime})`)
      assert.ok(path.length === 3, `Generated path length wrong (${path.length} === 3)`)
      assert.ok(rightBranch === true, `Generated path did not follow Right Branch [Left Branch: ${leftBranch}, Right Branch: ${rightBranch}]`)
    })

    /**
     * Ensure blocks adjacent to diagonal nodes are detected
     * [X] = Ent, [O] = Open, [W] = Wall
     *   O O X
     *   X W O
     *   O O X
     */
    it('rightBranchDiagsClear', () => {
      add1x2Weight(bot.pathfinder.movements.entityIntersections, 12, 2, 13)
      add1x2Weight(bot.pathfinder.movements.entityIntersections, 12, 2, 11)
      add1x2Weight(bot.pathfinder.movements.entityIntersections, 10, 2, 12)

      const generator = bot.pathfinder.getPathFromTo(bot.pathfinder.movements, startPos, goal, patherOptions)
      const { value: { result } } = generator.next()
      const path = result.path

      // Look at first and second nodes incase diagonal movements are used
      const leftBranch = (path[0].equals(firstLeftNode) || path[1].equals(firstLeftNode))
      const rightBranch = (path[0].equals(firstRightNode) || path[1].equals(firstRightNode))

      // All depends on the actually path that gets generated. If target block is moved some were else these values have to change.
      assert.strictEqual(result.status, 'success')
      assert.ok(result.time < maxPathTime, `Generated path took too long (${result.time} < ${maxPathTime})`)
      assert.ok(path.length === 3, `Generated path length wrong (${path.length} === 3)`)
      assert.ok(leftBranch === true, `Generated path did not follow Left Branch [Left Branch: ${leftBranch}, Right Branch: ${rightBranch}]`)
    })

    /**
     * Ensure blocks adjacent to diagonal nodes are detected
     * [X] = Ent, [O] = Open, [W] = Wall
     *   X O O
     *   O W X
     *   X O O
     */
    it('leftBranchDiagsClear', () => {
      add1x2Weight(bot.pathfinder.movements.entityIntersections, 12, 2, 12)
      add1x2Weight(bot.pathfinder.movements.entityIntersections, 10, 2, 13)
      add1x2Weight(bot.pathfinder.movements.entityIntersections, 10, 2, 11)

      const generator = bot.pathfinder.getPathFromTo(bot.pathfinder.movements, startPos, goal, patherOptions)
      const { value: { result } } = generator.next()
      const path = result.path

      // Look at first and second nodes incase diagonal movements are used
      const leftBranch = (path[0].equals(firstLeftNode) || path[1].equals(firstLeftNode))
      const rightBranch = (path[0].equals(firstRightNode) || path[1].equals(firstRightNode))

      // All depends on the actually path that gets generated. If target block is moved some were else these values have to change.
      assert.strictEqual(result.status, 'success')
      assert.ok(result.time < maxPathTime, `Generated path took too long (${result.time} < ${maxPathTime})`)
      assert.ok(path.length === 3, `Generated path length wrong (${path.length} === 3)`)
      assert.ok(rightBranch === true, `Generated path did not follow Right Branch [Left Branch: ${leftBranch}, Right Branch: ${rightBranch}]`)
    })
  })

  /**
   * Tests if bot will try to path where they cannot build due to an entity and whether it will
   * try to break a block that would potentially cause an entity to fall.
   * The test course is a 2x2x4 pit where the start is at the bottom and the end is at the top
   * [O] = Open, [W] = Wall, [S] = Start, [E] = End
   *   W W W E
   *   W O O W
   *   W S O W
   *   W W W W
   */
  describe('Construction Path Avoidance', () => {
    const Item = require('prismarine-item')(Version)

    const scaffoldItemId = mcData.itemsByName.dirt.id
    const groundYPos = 2
    const lidYPos = 5
    const forwardPos = { x: 11, z: 6 }
    const leftPos = { x: 10, z: 6 }
    const rightPos = { x: 11, z: 7 }
    const backPos = { x: 10, z: 7 }
    const targetBlock = new Vec3(forwardPos.x + 1.5, lidYPos + 1.0, forwardPos.z - 0.5) // a gold block away from the spawn position. One block diagonal from forward
    const startPos = new Vec3(backPos.x + 0.5, groundYPos, backPos.z + 0.5) // Start point for test
    const firstLeftNode = new Vec3(leftPos.x + 0.5, groundYPos, leftPos.z + 0.5)
    const firstRightNode = new Vec3(rightPos.x + 0.5, groundYPos, rightPos.z + 0.5)
    const firstForwardNode = new Vec3(forwardPos.x + 0.5, groundYPos, forwardPos.z + 0.5)
    const firstBackNode = startPos.clone().plus(new Vec3(-0.5, 1, -0.5)) // Jump up isn't going to half block and targets one block higher

    const blockersToPlace = [forwardPos, leftPos, rightPos, backPos]
    const goal = new goals.GoalGetToBlock(targetBlock.x, targetBlock.y, targetBlock.z)

    /** @type { import('minecraft-protocol').Client } */
    let serverClient
    /** @type { number } */
    let hotbarSlot

    before(() => {
      serverClient = Object.values(server.clients)[0]
      hotbarSlot = bot.inventory.firstEmptyHotbarSlot()
    })

    beforeEach((done) => {
      bot.pathfinder.movements.clearCollisionIndex()
      bot.inventory.slots[hotbarSlot] = new Item(scaffoldItemId, 64)
      setTimeout(done, 100)
    })

    afterEach(async () => {
      blockersToPlace.forEach(hPos => {
        const blockPos = { x: hPos.x, y: lidYPos, z: hPos.z }
        serverClient.write('block_change', { location: blockPos, type: mcData.blocksByName.air.id })
        chunk.setBlockType(new Vec3(blockPos.x, blockPos.y, blockPos.z), mcData.blocksByName.air.id)
      })
      serverClient.write('map_chunk', generateChunkPacket(chunk))
      await once(bot, 'chunkColumnLoad')
    })

    /**
     * By default, algorithm will favor the Backward Path
     * [X] = Ent Below, [+] = Ent Above a Block, [O] = Open, [W] = Wall
     *   W W W W
     *   W O O W
     *   W O O W
     *   W W W W
     */
    it('defaultPath', () => {
      const generator = bot.pathfinder.getPathFromTo(bot.pathfinder.movements, startPos, goal, patherOptions)
      const { value: { 
Download .txt
gitextract_nd4wfh09/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── ci.yml
│       ├── commands.yml
│       └── npm-publish.yml
├── .gitignore
├── .gitpod.yml
├── .npmrc
├── LICENSE
├── examples/
│   ├── bench.js
│   ├── blockInteraction.js
│   ├── callback.js
│   ├── chaining-goals.js
│   ├── example.js
│   ├── exclusionArea.js
│   ├── movements.js
│   ├── multiple.js
│   ├── promise.js
│   └── tutorial/
│       ├── basic.js
│       ├── goalComposite.js
│       └── goalsExplained.md
├── history.md
├── index.d.ts
├── index.js
├── lib/
│   ├── astar.js
│   ├── goals.js
│   ├── goto.js
│   ├── heap.js
│   ├── interactable.json
│   ├── lock.js
│   ├── move.js
│   ├── movements.js
│   ├── passableEntities.json
│   ├── physics.js
│   └── shapes.js
├── package.json
├── readme.md
└── test/
    ├── internalTest.js
    └── schematics/
        └── parkour1.schem
Download .txt
SYMBOL INDEX (165 symbols across 15 files)

FILE: examples/blockInteraction.js
  function directionToVector (line 136) | function directionToVector (dir) {

FILE: examples/callback.js
  function announceArrived (line 33) | function announceArrived () {

FILE: examples/chaining-goals.js
  function walkCheckpoints (line 81) | async function walkCheckpoints () {

FILE: index.d.ts
  type Pathfinder (line 13) | interface Pathfinder {
  class GoalBlock (line 58) | class GoalBlock extends Goal {
  class GoalNear (line 70) | class GoalNear extends Goal {
  class GoalXZ (line 83) | class GoalXZ extends Goal {
  class GoalNearXZ (line 94) | class GoalNearXZ extends Goal {
  class GoalY (line 106) | class GoalY extends Goal {
  class GoalGetToBlock (line 116) | class GoalGetToBlock extends Goal {
  class GoalCompositeAny (line 128) | class GoalCompositeAny<T extends Goal> extends Goal {
  class GoalCompositeAll (line 138) | class GoalCompositeAll<T extends Goal> extends Goal {
  class GoalInvert (line 148) | class GoalInvert extends Goal {
  class GoalFollow (line 158) | class GoalFollow extends Goal {
  class GoalPlaceBlock (line 172) | class GoalPlaceBlock extends Goal {
  class GoalLookAtBlock (line 186) | class GoalLookAtBlock  extends Goal {
  class GoalBreakBlock (line 199) | class GoalBreakBlock extends GoalLookAtBlock {}
  class Movements (line 202) | class Movements {
  type Move (line 319) | interface Move extends XYZCoordinates {
  type Callback (line 328) | type Callback = (error?: Error) => void;
  type PathBase (line 330) | interface PathBase {
  type ComputedPath (line 338) | interface ComputedPath extends PathBase {
  type PartiallyComputedPath (line 342) | interface PartiallyComputedPath extends PathBase {
  type XZCoordinates (line 346) | interface XZCoordinates {
  type XYZCoordinates (line 351) | interface XYZCoordinates extends XZCoordinates {
  type SafeBlock (line 355) | interface SafeBlock extends Block {
  type GoalPlaceBlockOptions (line 365) | interface GoalPlaceBlockOptions {
  type BotEvents (line 374) | interface BotEvents {
  type Bot (line 386) | interface Bot {

FILE: index.js
  function inject (line 15) | function inject (bot) {

FILE: lib/astar.js
  class PathNode (line 5) | class PathNode {
    method constructor (line 6) | constructor () {
    method set (line 14) | set (data, g, h, parent = null) {
  function reconstructPath (line 24) | function reconstructPath (node) {
  class AStar (line 33) | class AStar {
    method constructor (line 34) | constructor (start, movements, goal, timeout, tickTimeout = 40, search...
    method makeResult (line 55) | makeResult (status, node) {
    method compute (line 67) | compute () {

FILE: lib/goals.js
  class Goal (line 5) | class Goal {
    method heuristic (line 7) | heuristic (node) {
    method isEnd (line 12) | isEnd (node) {
    method hasChanged (line 18) | hasChanged () {
    method isValid (line 24) | isValid () {
  class GoalBlock (line 30) | class GoalBlock extends Goal {
    method constructor (line 31) | constructor (x, y, z) {
    method heuristic (line 38) | heuristic (node) {
    method isEnd (line 45) | isEnd (node) {
  class GoalNear (line 51) | class GoalNear extends Goal {
    method constructor (line 52) | constructor (x, y, z, range) {
    method heuristic (line 60) | heuristic (node) {
    method isEnd (line 67) | isEnd (node) {
  class GoalXZ (line 76) | class GoalXZ extends Goal {
    method constructor (line 77) | constructor (x, z) {
    method heuristic (line 83) | heuristic (node) {
    method isEnd (line 89) | isEnd (node) {
  class GoalNearXZ (line 95) | class GoalNearXZ extends Goal {
    method constructor (line 96) | constructor (x, z, range) {
    method heuristic (line 103) | heuristic (node) {
    method isEnd (line 109) | isEnd (node) {
  class GoalY (line 117) | class GoalY extends Goal {
    method constructor (line 118) | constructor (y) {
    method heuristic (line 123) | heuristic (node) {
    method isEnd (line 128) | isEnd (node) {
  class GoalGetToBlock (line 134) | class GoalGetToBlock extends Goal {
    method constructor (line 135) | constructor (x, y, z) {
    method heuristic (line 142) | heuristic (node) {
    method isEnd (line 149) | isEnd (node) {
  class GoalLookAtBlock (line 158) | class GoalLookAtBlock extends Goal {
    method constructor (line 159) | constructor (pos, world, options = {}) {
    method heuristic (line 167) | heuristic (node) {
    method isEnd (line 174) | isEnd (node) {
  class GoalBreakBlock (line 209) | class GoalBreakBlock extends Goal {
    method constructor (line 210) | constructor (x, y, z, bot, options = {}) {
    method isEnd (line 215) | isEnd (node) {
    method heuristic (line 219) | heuristic (node) {
  class GoalCompositeAny (line 227) | class GoalCompositeAny extends Goal {
    method constructor (line 228) | constructor (goals = []) {
    method push (line 233) | push (goal) {
    method heuristic (line 237) | heuristic (node) {
    method isEnd (line 245) | isEnd (node) {
    method hasChanged (line 252) | hasChanged () {
    method isValid (line 259) | isValid () {
  class GoalCompositeAll (line 265) | class GoalCompositeAll extends Goal {
    method constructor (line 266) | constructor (goals = []) {
    method push (line 271) | push (goal) {
    method heuristic (line 275) | heuristic (node) {
    method isEnd (line 283) | isEnd (node) {
    method hasChanged (line 290) | hasChanged () {
    method isValid (line 297) | isValid () {
  class GoalInvert (line 302) | class GoalInvert extends Goal {
    method constructor (line 303) | constructor (goal) {
    method heuristic (line 308) | heuristic (node) {
    method isEnd (line 312) | isEnd (node) {
    method hasChanged (line 316) | hasChanged () {
    method isValid (line 320) | isValid () {
  class GoalFollow (line 325) | class GoalFollow extends Goal {
    method constructor (line 326) | constructor (entity, range) {
    method heuristic (line 335) | heuristic (node) {
    method isEnd (line 342) | isEnd (node) {
    method hasChanged (line 349) | hasChanged () {
    method isValid (line 363) | isValid () {
  function distanceXZ (line 368) | function distanceXZ (dx, dz) {
  class GoalPlaceBlock (line 383) | class GoalPlaceBlock extends Goal {
    method constructor (line 384) | constructor (pos, world, options) {
    method heuristic (line 406) | heuristic (node) {
    method isEnd (line 413) | isEnd (node) {
    method getFaceAndRef (line 419) | getFaceAndRef (headPos) {
    method checkFacing (line 437) | checkFacing (dir) {
    method isStandingIn (line 453) | isStandingIn (node) {
  function vectorToDirection (line 461) | function vectorToDirection (v) {

FILE: lib/goto.js
  function error (line 1) | function error (name, message) {
  function goto (line 16) | function goto (bot, goal) {

FILE: lib/heap.js
  class BinaryHeapOpenSet (line 1) | class BinaryHeapOpenSet {
    method constructor (line 2) | constructor () {
    method size (line 7) | size () {
    method isEmpty (line 11) | isEmpty () {
    method push (line 15) | push (val) {
    method update (line 31) | update (val) {
    method pop (line 43) | pop () {

FILE: lib/lock.js
  class Lock (line 3) | class Lock {
    method constructor (line 4) | constructor () {
    method tryAcquire (line 13) | tryAcquire () {
    method acquire (line 25) | async acquire () {
    method release (line 43) | release () {

FILE: lib/move.js
  class Move (line 3) | class Move extends Vec3 {
    method constructor (line 4) | constructor (x, y, z, remainingBlocks, cost, toBreak = [], toPlace = [...

FILE: lib/movements.js
  class Movements (line 18) | class Movements {
    method constructor (line 19) | constructor (bot) {
    method exclusionPlace (line 113) | exclusionPlace (block) {
    method exclusionStep (line 122) | exclusionStep (block) {
    method exclusionBreak (line 131) | exclusionBreak (block) {
    method countScaffoldingItems (line 140) | countScaffoldingItems () {
    method getScaffoldingItem (line 152) | getScaffoldingItem () {
    method clearCollisionIndex (line 163) | clearCollisionIndex () {
    method updateCollisionIndex (line 172) | updateCollisionIndex () {
    method getNumEntitiesAt (line 208) | getNumEntitiesAt (pos, dx, dy, dz) {
    method getBlock (line 218) | getBlock (pos, dx, dy, dz) {
    method safeToBreak (line 252) | safeToBreak (block) {
    method safeOrBreak (line 282) | safeOrBreak (block, toBreak) {
    method getMoveJumpUp (line 301) | getMoveJumpUp (node, dir, neighbors) {
    method getMoveForward (line 361) | getMoveForward (node, dir, neighbors) {
    method getMoveDiagonal (line 403) | getMoveDiagonal (node, dir, neighbors) {
    method getLandingBlock (line 462) | getLandingBlock (node, dir) {
    method getMoveDropDown (line 476) | getMoveDropDown (node, dir, neighbors) {
    method getMoveDown (line 503) | getMoveDown (node, neighbors) {
    method getMoveUp (line 523) | getMoveUp (node, neighbors) {
    method getMoveParkourForward (line 558) | getMoveParkourForward (node, dir, neighbors) {
    method getNeighbors (line 636) | getNeighbors (node) {

FILE: lib/physics.js
  class Physics (line 3) | class Physics {
    method constructor (line 4) | constructor (bot) {
    method simulateUntil (line 17) | simulateUntil (goal, controller = () => {}, ticks = 1, state = null) {
    method simulateUntilNextTick (line 41) | simulateUntilNextTick () {
    method simulateUntilOnGround (line 45) | simulateUntilOnGround (ticks = 5) {
    method canStraightLine (line 49) | canStraightLine (path, sprint = false) {
    method canStraightLineBetween (line 70) | canStraightLineBetween (n1, n2) {
    method canSprintJump (line 91) | canSprintJump (path, jumpAfter = 0) {
    method canWalkJump (line 97) | canWalkJump (path, jumpAfter = 0) {
    method getReached (line 103) | getReached (path) {
    method getController (line 110) | getController (nextPoint, jump, sprint, jumpAfter = 0) {

FILE: lib/shapes.js
  function getShapeFaceCenters (line 3) | function getShapeFaceCenters (shapes, direction, half = null) {

FILE: test/internalTest.js
  function flatMap (line 24) | function flatMap (Version) {
  function parkourMap (line 44) | async function parkourMap (Version) {
  function generateChunkPacket (line 62) | function generateChunkPacket (chunk) {
  function newServer (line 97) | async function newServer (server, chunk, spawnPos, Version, useLoginPack...
  function add1x2Weight (line 144) | function add1x2Weight (entityIntersections, posX, posY, posZ, weight = 1) {
Condensed preview — 38 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (216K chars).
[
  {
    "path": ".github/dependabot.yml",
    "chars": 125,
    "preview": "version: 2\nupdates:\n- package-ecosystem: npm\n  directory: \"/\"\n  schedule:\n    interval: daily\n  open-pull-requests-limit"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 422,
    "preview": "name: CI\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  build:\n\n    runs-on: ub"
  },
  {
    "path": ".github/workflows/commands.yml",
    "chars": 544,
    "preview": "name: Repo Commands\n\non:\n  issue_comment:        # Handle comment commands\n    types: [created]\n  pull_request:         "
  },
  {
    "path": ".github/workflows/npm-publish.yml",
    "chars": 895,
    "preview": "name: npm-publish\non:\n  push:\n    branches:\n      - master # Change this to your default branch\njobs:\n  npm-publish:\n   "
  },
  {
    "path": ".gitignore",
    "chars": 49,
    "preview": "node_modules\npackage-lock.json\nyarn.lock\n.vscode\n"
  },
  {
    "path": ".gitpod.yml",
    "chars": 30,
    "preview": "tasks:\n- command: npm install\n"
  },
  {
    "path": ".npmrc",
    "chars": 19,
    "preview": "package-lock=false\n"
  },
  {
    "path": "LICENSE",
    "chars": 1063,
    "preview": "MIT License\n\nCopyright (c) 2020 Karang\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
  },
  {
    "path": "examples/bench.js",
    "chars": 1184,
    "preview": "// Simple test to evaluate how much time it takes to find a path of 100 blocks\n\nconst mineflayer = require('mineflayer')"
  },
  {
    "path": "examples/blockInteraction.js",
    "chars": 4958,
    "preview": "const mineflayer = require('mineflayer')\nconst { pathfinder, Movements } = require('mineflayer-pathfinder')\nconst { Goal"
  },
  {
    "path": "examples/callback.js",
    "chars": 1387,
    "preview": "// This example uses promises instead of events like \"goal_reached\" for a cleaner look\n\nconst mineflayer = require('mine"
  },
  {
    "path": "examples/chaining-goals.js",
    "chars": 4768,
    "preview": "/* Pathfinder Chaining Goals example\n\nThis example shows how to chain goals together.\nRun this example with:\n\nnode examp"
  },
  {
    "path": "examples/example.js",
    "chars": 3361,
    "preview": "const mineflayer = require('mineflayer')\nconst { pathfinder, Movements } = require('mineflayer-pathfinder')\nconst { Goal"
  },
  {
    "path": "examples/exclusionArea.js",
    "chars": 4744,
    "preview": "/* Pathfinder Exclusion Area example\n\nThis example shows the use of exclusion areas with the Movement Class.\n\nIn Game Ch"
  },
  {
    "path": "examples/movements.js",
    "chars": 1887,
    "preview": "/*\n * This example demonstrates how easy it is to change the default movement\n *\n * Below are a few options you can edit"
  },
  {
    "path": "examples/multiple.js",
    "chars": 1806,
    "preview": "const mineflayer = require('mineflayer')\nconst { pathfinder, Movements } = require('mineflayer-pathfinder')\nconst { Goal"
  },
  {
    "path": "examples/promise.js",
    "chars": 1234,
    "preview": "// This example uses promises instead of events like \"goal_reached\" for a cleaner look\n\nconst mineflayer = require('mine"
  },
  {
    "path": "examples/tutorial/basic.js",
    "chars": 1332,
    "preview": "/*\r\n * This example shows the usage of the GoalBlock\r\n * goal for mineflayer-pathfinder\r\n *\r\n * See a more detailed expl"
  },
  {
    "path": "examples/tutorial/goalComposite.js",
    "chars": 2030,
    "preview": "/*\r\n * This example shows the usage of the\r\n * GoalCompositeAny and GoalCompositeAll\r\n * goals for mineflayer-pathfinder"
  },
  {
    "path": "examples/tutorial/goalsExplained.md",
    "chars": 9738,
    "preview": "<!-- Explanation of how to use goals in mineflayer-pathfinder. Made by Jovan04 06/07/2023 -->\r\n\r\n# Mineflayer-Pathfinder"
  },
  {
    "path": "history.md",
    "chars": 9966,
    "preview": "# History\n\n# 2.4.5\n* [Fix block update resets for optimized paths (@m000z0rz)](https://github.com/PrismarineJS/mineflaye"
  },
  {
    "path": "index.d.ts",
    "chars": 11524,
    "preview": "import { Bot } from 'mineflayer';\nimport { IndexedData } from 'minecraft-data';\nimport { Item } from 'prismarine-item';\n"
  },
  {
    "path": "index.js",
    "chars": 21819,
    "preview": "const { performance } = require('perf_hooks')\n\nconst AStar = require('./lib/astar')\nconst Move = require('./lib/move')\nc"
  },
  {
    "path": "lib/astar.js",
    "chars": 3713,
    "preview": "const { performance } = require('perf_hooks')\n\nconst Heap = require('./heap.js')\n\nclass PathNode {\n  constructor () {\n  "
  },
  {
    "path": "lib/goals.js",
    "chars": 12659,
    "preview": "const { Vec3 } = require('vec3')\nconst { getShapeFaceCenters } = require('./shapes')\n\n// Goal base class\nclass Goal {\n  "
  },
  {
    "path": "lib/goto.js",
    "chars": 2036,
    "preview": "function error (name, message) {\n  const err = new Error(message)\n  err.name = name\n  return err\n}\n\n/**\n   * Adds a easy"
  },
  {
    "path": "lib/heap.js",
    "chars": 2138,
    "preview": "class BinaryHeapOpenSet {\n  constructor () {\n    // Initialing the array heap and adding a dummy element at index 0\n    "
  },
  {
    "path": "lib/interactable.json",
    "chars": 2458,
    "preview": "[\n  \"acacia_door\",\n  \"acacia_fence_gate\",\n  \"acacia_button\",\n  \"acacia_trapdoor\",\n  \"anvil\",\n  \"armor_stand\",\n  \"barrel\""
  },
  {
    "path": "lib/lock.js",
    "chars": 1046,
    "preview": "const { EventEmitter, on } = require('events')\n\nclass Lock {\n  constructor () {\n    this._locked = false\n    this._emitt"
  },
  {
    "path": "lib/move.js",
    "chars": 437,
    "preview": "const { Vec3 } = require('vec3')\n\nclass Move extends Vec3 {\n  constructor (x, y, z, remainingBlocks, cost, toBreak = [],"
  },
  {
    "path": "lib/movements.js",
    "chars": 24387,
    "preview": "const { Vec3 } = require('vec3')\nconst nbt = require('prismarine-nbt')\nconst Move = require('./move')\n\nconst cardinalDir"
  },
  {
    "path": "lib/passableEntities.json",
    "chars": 471,
    "preview": "[\n  \"falling_block\",\n  \"tnt\",\n  \"item\",\n  \"area_effect_cloud\",\n  \"item_frame\",\n  \"leash_knot\",\n  \"painting\",\n  \"arrow\",\n"
  },
  {
    "path": "lib/physics.js",
    "chars": 3962,
    "preview": "const { PlayerState } = require('prismarine-physics')\n\nclass Physics {\n  constructor (bot) {\n    this.bot = bot\n    this"
  },
  {
    "path": "lib/shapes.js",
    "chars": 864,
    "preview": "const { Vec3 } = require('vec3')\n\nfunction getShapeFaceCenters (shapes, direction, half = null) {\n  const faces = []\n  f"
  },
  {
    "path": "package.json",
    "chars": 848,
    "preview": "{\n  \"name\": \"mineflayer-pathfinder\",\n  \"version\": \"2.4.5\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n   "
  },
  {
    "path": "readme.md",
    "chars": 15778,
    "preview": "# Mineflayer-pathfinder\n\n[![npm version](https://badge.fury.io/js/mineflayer-pathfinder.svg)](https://badge.fury.io/js/m"
  },
  {
    "path": "test/internalTest.js",
    "chars": 50737,
    "preview": "/* eslint-env mocha */\n\nconst mineflayer = require('mineflayer')\nconst { goals, pathfinder, Movements } = require('minef"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the PrismarineJS/mineflayer-pathfinder GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 38 files (201.6 KB), approximately 56.6k tokens, and a symbol index with 165 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!