Giter Club home page Giter Club logo

mineflayer-pathfinder's Introduction

Mineflayer-pathfinder

npm version npm Try it on gitpod Issue Hunt

Pathfinding plugin for the Minecraft Bot API 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

npm install mineflayer-pathfinder

Tutorial & Explanation

For a basic explanation of how to use mineflayer-pathfinder, you can read this tutorial.

Video Tutorials

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

part 1 part 2

Example

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 )
  • 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*.
  • 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:

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.

  • 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.

mineflayer-pathfinder's People

Contributors

alexprogrammerde avatar constantins2001 avatar dement6d avatar dependabot-preview[bot] avatar dependabot[bot] avatar googlesites avatar hax0r31337 avatar icetank avatar jovan-04 avatar karang avatar killbinvlog avatar m000z0rz avatar mat-1 avatar mralexesistegia avatar notsugden avatar olie304 avatar rayhanhamada avatar rom1504 avatar rtm516 avatar rutexd avatar seanmccord avatar sefirosweb avatar slord6 avatar syriusz-2005 avatar thedudefromci avatar trcyx avatar u9g avatar vinciepincie avatar whotho avatar zn10plays avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

mineflayer-pathfinder's Issues

Add abilty to disable destroying blocks

Currently, we can disable placing blocks by setting the scafoldBlocks array to be empty. However, the only alternative for destroying blocks is by adding every block in the game to the blocksCantBreak array. This is a bit cumbersome and it would be nice if there was an easy way to do this, such as movements.canDig = false

You can kind of fake it by setting digCost to be very high, but it cannot be disabled completely.

Fails at parkour

If I wanted to contribute the ability to "parkour" to this module, would you be able to point me in the correct direction to do so? What would it take in order to allow the program to make jumps?

New feature: Water Drop

When jumping from very high, make the bot search for a water bucket in its inventory and place it just as he falls so he prevents getting damage.

Dropping into water/using water bucket to negate fall damage

Currently the bot isn't able to drop into water, much less use a bucket to avoid fall damage. These would be nice features. I've somewhat successfully added an idea to my code but it fails for small bodies of water when falling from very high up as the bot seems to continue moving in the air and thus not fall on the predicted block.
To fix this, I think it would be a good idea to prevent lateral movement when attempting to fall into water.
This is what I have currently: (It's terrible but works as a proof of concept)

  getLandingBlock(node, dir) {
    let blockLand = this.getBlock(node, dir.x, -2, dir.z)
    for (let i = 0; i < 100; i++) {
      if (i < this.maxDropDown - 1) {
        if (blockLand.physical || !blockLand.safe) break
        blockLand = this.getBlock(node, dir.x, -2 - i, dir.z)
      }

      let possibleBlockLand = this.getBlock(node, dir.x, -2 - i, dir.z);
      if (possibleBlockLand.liquid && possibleBlockLand.safe) {
        blockLand = possibleBlockLand;
        break;
      }

    }
    return blockLand
  }

I also had to change the if (!blockLand.physical) return checks in getMoveDropDown and getMoveDown to allow liquids. Are they even necessary if getLandingBlock returns only correct blocks anyway?

I'd make a pull request but this is nowhere near good enough yet.

Callback upon goal reached

I know the mineflayer stuff is more event-based but being able to set a callback would be very useful to more easily string together actions.
It's very easy to add to the inject function. Should I make a pull request?

Buggy movement on snow

This makes a lot of sense. The bot thinks it's stuck in a block, (the snow) so it constantly hops forward instead of walking forward and removes snow layers on ledges before climbing up them.

I would have thought the bot would ignore them because the snow has no collision. However, pathfinder checks the bounding box of the block when handling pathfinding, not the collision shape. So even transparent blocks like snow, torches, and tall grass are seen as obstacles to the bot.

Add climbing vines support

Hi, i started my adventure with mineflayer recently. And i wanted to add pathfinding ability to my bot and i found this repo. I really appreciate your work and i was curious when can we expect ladder and vines support. Using bot is really painfull without it. I know there are plans
for implementing this. I just wanted to know how long does it will take. Best regards.

*.d.ts file for TypeScript

It would be nice if there was a header file defined for support when writing bots with TypeScript.

Gotta say though, very impressive plugin. Keep up the good work!
😁

Finding nearset players

I'm trying to make a bot that attacks nearby players. The problem is that the bot only tracks the first nearest player, and if the player leaves the server or teleports to another world, the bot simply remembers the last point of coordinates. How to make sure that if a player disconnects from the server, the bot selects a new closest player?

if (message == ".farm") {
  clearInterval(afk)
  var target = minecraft.nearestEntity(({
    type
  }) => type === 'player')
  minecraft.pathfinder.setMovements(defaultMove)
  minecraft.pathfinder.setGoal(new GoalFollow(target, 1), true)
  setInterval(() => {
    let distance
    if (target.position) {
      let distance = target.position.distanceTo(minecraft.entity.position)
    }
    if (distance < 3) {
      minecraft._client.write('arm_animation', {
        hand: 0
      });
      minecraft.setQuickBarSlot(1)
      minecraft.attack(target)
    }
  }, 200)
}

Erorr on bot start

Hello, I have this code that throws error when started, but I am not sure if it's my stupidity or Issue in library...:

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

bot.loadPlugin(pathfinder);

bot.once('spawn', () => {
    const mcData = require('minecraft-data')(bot.version);
     bot.on('chat', (username, message) => {
        if (username === bot.username) return
    
        const target = bot.players[username] ? bot.players[username].entity : null //I am using this elsewere

        if (message === 'start') {
            bot.chat("Some text");
            setInterval(() => {
                const playerFilter = (entity) => entity.type == 'player';
                const playerEntity = bot.nearestEntity(playerFilter);
                if (playerEntity == null) {
                    return;
                }
                bot.pathfinder.setMovements(new Movements(bot, mcData));
                bot.pathfinder.setGoal(new GoalNear(playerEntity.position.x, playerEntity.position.y, playerEntity.position.z, 0), true);
            }, 1000)

My problem is that sometime for some weird reason when I start bot he just writes into chat: 'Some text' and then he leaves (error happend). Basically what I am trying to do is: every second follow nearest player.
Error:

E:\OneDrive\#Projekty\Prace notebook - z domu\VelkeProjekty\Minecraft-bots\Killer_Bot\node_modules\mineflayer-pathfinder\lib\movements.js:79
        height: pos.y + dy
                    ^

    at Movements.getNeighbors (E:\OneDrive\#Projekty\Prace notebook - z domu\VelkeProjekty\Minecraft-bots\Killer_Bot\node_modules\mineflayer-pathfinder\lib\movements.js:333:12)
    at AStar.compute (E:\OneDrive\#Projekty\Prace notebook - z domu\VelkeProjekty\Minecraft-bots\Killer_Bot\node_modules\mineflayer-pathfinder\lib\astar.js:76:40)
    at Object.bot.pathfinder.getPathTo (E:\OneDrive\#Projekty\Prace notebook - z domu\VelkeProjekty\Minecraft-bots\Killer_Bot\node_modules\mineflayer-pathfinder\index.js:38:84)
    at Bot.monitorMovement (E:\OneDrive\#Projekty\Prace notebook - z domu\VelkeProjekty\Minecraft-bots\Killer_Bot\node_modules\mineflayer-pathfinder\index.js:205:26)
    at Bot.emit (events.js:326:22)
    at Timeout.doPhysics [as _onTimeout] (E:\OneDrive\#Projekty\Prace notebook - z domu\VelkeProjekty\Minecraft-bots\Killer_Bot\node_modules\mineflayer\lib\plugins\physics.js:62:13)

Thanks for you time :)

Add default movements object

For the most part, when using pathfinder, the default movement configuration is often good enough for most purposes and does not need to be tweaked. It would be a nice qualty of life feature to have movements already defined on startup. Since the plugin injection occurs after the bot has already logged into the server, it is safe to retrieve the mcData object during this phase.

Seemingly random crashes "Cannot read property 'y' of undefined"

My bot sometimes throws the error

[11:30:54] [AUTORECONNECT] Moving to area
D:\prg\FlayBot_v0.8.9\node_modules\mineflayer-pathfinder\lib\movements.js:81
        height: pos.y + dy
                    ^

TypeError: Cannot read property 'y' of undefined
    at Movements.getBlock (D:\prg\FlayBot_v0.8.9\node_modules\mineflayer-pathfinder\lib\movements.js:81:21)
    at Movements.safeToBreak (D:\prg\FlayBot_v0.8.9\node_modules\mineflayer-pathfinder\lib\movements.js:101:16)
    at Movements.safeOrBreak (D:\prg\FlayBot_v0.8.9\node_modules\mineflayer-pathfinder\lib\movements.js:113:15)
    at Movements.getMoveDiagonal (D:\prg\FlayBot_v0.8.9\node_modules\mineflayer-pathfinder\lib\movements.js:204:18)
    at Movements.getNeighbors (D:\prg\FlayBot_v0.8.9\node_modules\mineflayer-pathfinder\lib\movements.js:350:12)
    at AStar.compute (D:\prg\FlayBot_v0.8.9\node_modules\mineflayer-pathfinder\lib\astar.js:76:40)
    at Object.bot.pathfinder.getPathTo (D:\prg\FlayBot_v0.8.9\node_modules\mineflayer-pathfinder\index.js:39:84)
    at Bot.monitorMovement (D:\prg\FlayBot_v0.8.9\node_modules\mineflayer-pathfinder\index.js:210:26)
    at Bot.emit (events.js:326:22)
    at Timeout.doPhysics [as _onTimeout] (D:\prg\FlayBot_v0.8.9\node_modules\mineflayer\lib\plugins\physics.js:62:13)
Drücken Sie eine beliebige Taste . . . 

(Almost) all i have is:

logger.autoreconnect("Moving to area");
var botOptions = {
    host: this.server,
    port: this.port == null ? "25565" : this.port,
    username: this.username,
    password: this.password,
    version: this.version == null ? "1.8.8" : this.version
}

var bot = mineflayer.createBot(botOptions);
bot.loadPlugin(pathfinder);
const mcData = require('minecraft-data')(bot.version);
var defaultMove = new Movements(bot, mcData);
defaultMove.blocksToAvoid.add(mcData.blocksByName.iron_trapdoor.id);
pathfinder(bot)
bot.pathfinder.setMovements(defaultMove);

bot.once('spawn', () => {
    bot.pathfinder.setGoal(new GoalNear(317, 116, 305, 1))
});

Usually, this works, but sometimes, seemingly random, the bot crashes and throws the error shown above.
What am i doing wrong?

Bot thinks it can jump over fences

The bot seems to have a problem jumping over fences, likely because it just sees the fence as a single block instead of 1.5 blocks. It's safe to assume this problem also exists for walls.

Bot gets stuck in water if it tries to place a block under itself while Jumping/Swimming

While in water the bot can get stuck if it tries to place a block.
This can happen when the bot is standing in 1 high water and tries to place a block to get up 1 block. In this case the bot tries to place a block under itself while jumping. While not in water the bot is able to jump high enough to place a block under itself but this is not the case in Water. In Water it is impossible to jump higher then the water Block the player or bot is in. The Bot tries anyway and gets stuck.

Visualisation:
w = Water
o = Water Block the bot is trying to replace with a block while jumping and bot feet position
i = Bot head position
s = Solid block

  i
wwoww
sssss

Marking a block as unbreakable doesn't stop the bot from trying

I had noticed that in some situations, the bot would still attempt to break blocks to get around obstacles despite all block types being in the blocksCantBreak set.

Looking through the code, it appears that the bot doesn't actually mark them as unbreakable, just resilient to break.

if (!this.safeToBreak(block)) return 100 // Can't break, so can't move

This can lead to extremely unexpected results in many conditions.

It's better to not have a path at all than to break blocks I specifically told it not to break.

Expose movements and goal variables

It would be useful to have a getter for the current movements and goal config, alongside the setters.

This is helpful in situations where you want to assign a temporary movement config or goal for a period of time and then revert back to the previous state.

Event listener leak

So, this is actually only after the mineflayer promisification update, so it's not entirely mineflayer-pathfinder's fault, but let me explain:

Mineflayer-pathfinder uses bot.look and bot.lookAt. These are actually both asynchronous functions with a callback, but mineflayer uses them in a synchronous way. This is actually relying on a quirk of mineflayer that it had in the previous version and not proper behavior. Because it also didn't use the force parameter. Let's look at the bot.look function in the previous version:

bot.look = (yaw, pitch, force, cb) => {
    bot.entity.yaw = yaw
    bot.entity.pitch = pitch
    if (force) {
      lastSentYaw = yaw
      lookAtCb = noop
      cb()
    } else {
      lookAtCb = cb || noop
    }
  }

And let's look how mineflayer-pathfinder uses it:

// run toward next point
    bot.look(Math.atan2(-dx, -dz), 0)

    const lx = -Math.sin(bot.entity.yaw)
    const lz = -Math.cos(bot.entity.yaw)

    const frontBackProj = lx * dx + lz * dz
    bot.setControlState('forward', frontBackProj > 0)
    bot.setControlState('back', frontBackProj < 0)

As you can see, there's also no force parameter used. So it just starts the looking process and starts walking. But also, bot.look can be called in very quick succession. For this to make sense, you'd expect at least the force parameter to be set to true, so that it immediately returns and doesn't mess with the lookAtCb (which, by itself, is a weird construction IMO).

Now, in the promisified version, things have changed. The lookAtCb construction is removed, and it's actually an async function that waits for the exit condition. This means that when calling it as often as mineflayer-pathfinder does, without waiting, it will keep adding event listeners instead of overwriting the lookAtCb function. This is the quirk I was talking about. This causes event listener leakage. In short, what I'm trying to say is: the force parameter should, in my opinion, be set to true on both calls to bot.look and bot.lookAt, to fix this issue. Let me know if you disagree with any of my reasoning.

Implement async A*

Right now, the A* implementation is sync, when the computation timeout, we return the path to the best node. While the returned path is generally in the good direction, complexes environments like mazes can be deceiving.

To improve the path, more nodes needs to be explored, so the timeout should be removed. But the computation should not block other plugins like physics that should be updated every tick. Additionally, to keep the responsiveness of the api, a path estimation should be returned as soon as possible so the bot can start walking, while the computation continues. The solution is to allow a fixed amount of time per tick to advance the computation and save the computation context to continue where it left on the next tick.

Things that needs to be considered:

  • Computation should restart on environment / goal change

This issue track what has to be done to achieve that goal:

  • Make A* a class to store the computation context
  • Implement free motion and path compression (this is needed in case the part of the path that was already walked is updated)
  • Path merging logic
  • Update movement monitoring

Walking very slow....

Previously, I used mineflayer-navigate and it moved much faster, as I switched to pathfinder, its walking speed was 4 seconds per 1 block, is there any configuration to improve its walking speed? Nor can it resist the movement of water

i can't run the test.js

node test.js

/root/mineflayer-pathfinder/node_modules/protodef/src/compiler.js:157
for (const [type, [kind, fn]] of Object.entries(types)) {
^

TypeError: Object.entries is not a function
at ReadCompiler.addTypes (/root/mineflayer-pathfinder/node_modules/protodef/src/compiler.js:157:45)
at new ReadCompiler (/root/mineflayer-pathfinder/node_modules/protodef/src/compiler.js:253:10)
at new ProtoDefCompiler (/root/mineflayer-pathfinder/node_modules/protodef/src/compiler.js:12:25)
at createProto (/root/mineflayer-pathfinder/node_modules/prismarine-nbt/nbt.js:9:20)
at Object. (/root/mineflayer-pathfinder/node_modules/prismarine-nbt/nbt.js:15:15)
at Module._compile (module.js:577:32)
at Object.Module._extensions..js (module.js:586:10)
at Module.load (module.js:494:32)
at tryModuleLoad (module.js:453:12)
at Function.Module._load (module.js:445:3)
How to fix it

Add 'goal_changed' event

Given the event nature of the plugin, it can be useful to know when a goal is changed in order to handle situations such as canceling listeners for a previous goal.

For example, if two plugins are running and one of them is following a mob but the other one interrupts the pathfinder to move to another location, it would be useful for the first plugin to know the goal was changed in order to properly clean up.

Extract path object into new class

Currently, the path object is represented as an array of movements within the index.js, tightly bound with the moving process. I would like to split this into a new class object to make it more reusable. For example, generating multiple paths without actually starting the move action (for example, deciding which of several different destinations would be the easiest to get to), or allowing post-processing to be performed on the path before starting it. (An example of this could be to trim redundant waypoints, or shift them off the grid to make them feel more natural)

I can write this as a PR but would like permission to do this before writing anything.

Using doors

Hi,

Thanks for the work pathfinding. I've tested it yesterday and burst out in laughter when I realised that instead of using the door the bot burst through the wall like the cool-aid guy.

Are there plans in the future to allow the bot to pass through open doors or even open/close them as he passes through?

Thanks ;)

Documentation

Having documentation would be very helpful (even more than examples!)

Great work with this lib 😁

Cost for placing blocks

I've noticed that the bot prefers pillaring up a wall instead of using a detour over nearby stairs, which is sometimes preferable.
Is there a way of setting a cost for placing blocks, in the same way as setting cost for breaking blocks?

Sprint usage

An option should allow the bot to use sprint to go to its destination.

Option to disable placing blocks

I'm trying to make a command to make the bot get as close as possible without placing blocks but it ends up placing blocks whenever it can.

Cannot path when on soulsand without a block under it

i tried the same test not on the void and there was no difference

video:
https://streamable.com/qli06k

code:

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

let bot = mineflayer.createBot({
    viewDistance: "short"
});
bot.loadPlugin(pathfinder)

bot.once('spawn', () => {
    let mcData = require('minecraft-data')(bot.version)
    let defaultMove = new Movements(bot, mcData)
    bot.pathfinder.setMovements(defaultMove)
})

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

    if (message === "come")
        come(username)
})

function come(nick) {
    const target = bot.players[nick] ? bot.players[nick].entity : null
    if (!target) {
        return
    }
    const p = target.position
    bot.pathfinder.setGoal(new GoalNear(p.x, p.y, p.z, 2))
    bot.once('goal_reached', (goal) => {
        bot.chat("im here")
    })
}

Feature for replacing the broken block with the same block

Hi at first I wanna thank you for sharing this project, it's cool that you recreated baritone features in mineflayer. You save me a lot of time. I have some suggestions:

  • Block breaking should have different costs based on tools that bot have and enchants on them.
  • For example, if the bot has a shovel and doesn't have a pickaxe, then bot should think that mining stone is more expensive for him.
  • Feature for replacing the broken block with the same block
  • That would give a big potential in the automatic building with bots
  • The cost of placing the block should increase with a decrease in the amount of block in inventory and vice versa.

In a few days, I will start writing some to allow bots to choose the best possible tools for breaking specific blocks. (I'm going to consider also enchants). Also, I'm starting to read your codebase and add some more functionality.

I'm hyped about this project.
Good luck and have a nice day!

TypeError: Cannot read property 'shapes' of null

Error

/mnt/dev/murderer/node_modules/mineflayer-pathfinder/index.js:103
    if (block.shapes.length === 0) return null
              ^

TypeError: Cannot read property 'shapes' of null
    at getPositionOnTopOf (/mnt/dev/murderer/node_modules/mineflayer-pathfinder/index.js:103:15)
    at Bot.monitorMovement (/mnt/dev/murderer/node_modules/mineflayer-pathfinder/index.js:266:14)
    at Bot.emit (events.js:315:20)
    at Timeout.doPhysics [as _onTimeout] (/mnt/dev/murderer/node_modules/mineflayer/lib/plugins/physics.js:62:13)
    at listOnTimeout (internal/timers.js:549:17)
    at processTimers (internal/timers.js:492:7)

About

Hi there, I have an issue with the pathfinder.
I am working on a bot that kills players. The way I went about making this is on spawning I've set an interval every 500ms to setGoal to the player's current position. It worked just fine, but when I kill the bot, it throws an error.

What can I do about it?

Add more tests

There have been a few issues of specific cases that were broken and could now serve as test cases.
mineflayer-pathfinder can be used on a simulated world.
I think it would be great to have some tests.

For example it could make sense to define a simple fake chunk/world section, and to make pathfinder generate a path from A to B, check it manages to find one, that it seems correct (basic consistency test on it), then execute it and check that the state produce is what is expected.

It might not be 100% what would be observed on an actual server, but may already be interesting to test issues.

What do you think ?

Actual move speed per block

Blocks like slime or soulsand can affect the player speed, it should be accounted for in the search.
See prismarine-physics for more infos.

np from getPositionOnTopOf can be null and causes crash

This section of monitorMovement

https://github.com/Karang/mineflayer-pathfinder/blob/0aaee90102c7cbc030aa3a1ec350608eae0b8d53/index.js#L214-L220

can result in np being null, even after the 'if null' check and assignment on line 216.

It seems to be fixed by moving the use of np further down to here:

https://github.com/Karang/mineflayer-pathfinder/blob/0aaee90102c7cbc030aa3a1ec350608eae0b8d53/index.js#L270-L274

presumably because the instances where np is null is only true for the digging or placement sections (I think specifically the issue is with air blocks). Happy to put in a PR but want to check I'm on the right lines before I make any changes

blocksToAvoid doesnt work?

Hello!
So, in my code, i have these lines:
var defaultMove = new Movements(this.botInstance, mcData);
defaultMove.blocksToAvoid.add(mcData.blocksByName.iron_trapdoor.id);

But the bot still just moves right through open trapdoors. (i use SetGoal(x,y,z))
Anyone have an idea, why?
Thanks!
PS: sorry for bad english

Crash when die

Occurs when death:

C:\Workspace\bodyguard\node_modules\mineflayer-pathfinder\lib\movements.js:79
height: pos.y + dy
^

TypeError: Cannot read property 'y' of undefined
at Movements.getBlock (C:\Workspace\bodyguard\node_modules\mineflayer-pathfinder\lib\movements.js:79:21)
at Movements.safeToBreak (C:\Workspace\bodyguard\node_modules\mineflayer-pathfinder\lib\movements.js:95:16)
at Movements.safeOrBreak (C:\Workspace\bodyguard\node_modules\mineflayer-pathfinder\lib\movements.js:107:15)
at Movements.getMoveUp (C:\Workspace\bodyguard\node_modules\mineflayer-pathfinder\lib\movements.js:266:18)
at Movements.getNeighbors (C:\Workspace\bodyguard\node_modules\mineflayer-pathfinder\lib\movements.js:350:12)
at AStar.compute (C:\Workspace\bodyguard\node_modules\mineflayer-pathfinder\lib\astar.js:76:40)
at Object.bot.pathfinder.getPathTo (C:\Workspace\bodyguard\node_modules\mineflayer-pathfinder\index.js:38:84)
at Bot.monitorMovement (C:\Workspace\bodyguard\node_modules\mineflayer-pathfinder\index.js:205:26)
at Bot.emit (node:events:339:22)
at Timeout.doPhysics [as _onTimeout] (C:\Workspace\bodyguard\node_modules\mineflayer\lib\plugins\physics.js:62:13)

Empty bounding boxes

Should the pathfinder break something with an empty bounding box? Such as wheat or a sapling. I know you can add avoidance, but that prevents the bot from standing on the block.

You can add this right after line 224 in index.js:
if ((bot.blockAt(new Vec3(b.x, b.y, b.z), false).boundingBox == "empty")) { digging = false return }

Won't tower to correct Y level / Pathing

When giving the bot a coordinate to get to E.g: 650, 35, 350 (X,Y,Z) It will go to the correct X and Z, then it will tower up 2 blocks or so before stopping (at the incorrect y level). Re running the command the bot will then tower up 1 block at a time for each time the command is run. (Usually worse results when the Y level supplied is much greater than it's current level)

From what I have seen with testing, the bot also does not see towering as a viable pathing strategy. Rather than towering directly up 13 blocks it decided to run 8 or so blocks to higher ground, before towering up 7 and across 8.

Edit:

I forgot to mention the same applies when telling the bot to come / follow. Although I have it set to be at a distance of 1 or 0 each time I test it, the bot either does not take into account the distance of the Y level or fails to create the pathing. If I fly directly above the bot, rather than towering up to me, it will build up a little bit and then just stand still. If I move left or right then it will begin to staircase up.

Edit 2:

The status of the pathfinding is that it's timing out. However I am unsure why, I am still examining the code. I'm sure someone who has actually contributed to this repo would have an idea of why.

Modular movements

I believe it might be easier to implement a modular "plugin-like" system to movements to better configure movements, write cleaner API, and add more flexibility for new movement paths and styles.

For example, if the bot can mine, add the dig-based movements to the movements container. If it can't dig, don't load that module. This will actually result in fewer nodes overall by trimming out actions that can't be preformed.

Some example movement modules:

  • Basic Movement
  • Swimming
  • Digging
  • Placing
  • Large Jumps
  • Ladders/Vines
  • Doorways
  • Elytra (Way, way down the road)
  • Custom Movements defined by the user such as:
    • Opening Chests to get more scaffolding blocks
    • Crafting a new pickaxe if one breaks

Each movement module will be in charge of actually executing the movement as well, which will keep the codebase clean and easy to read. All digging based behavior is kept in the digging module and nowhere else.

In addition, each module contains it's own config for stuff like dig cost as well as the overall config on the actual movements container specifying range and timeout like normal.

Long Distance Travel System

As it currently stands, attempting to find a path to a destination too far away will result in trying to access unloaded chunks and will crash the pathfinder. Even if this crash was patched, it still limits the distance that the bot can travel. In addition, the time it would take to find such a path would be beyond unreasonable for any realtime applications.

My proposal for a long-distance travel system could be to use two sets of grids to run the pathfinder on. The smaller grid is the standard grid that uses regular Minecraft blocks as we have now. The second grid is a larger grid where each node is 16 blocks away from each other, along the XZ plane. When traveling distances greater than 64 blocks, the bot would first try to find a path on these larger grid units. (By default, these all considered completely connected.)

After a path is discovered across the larger grid, the bot will attempt to find a path from each node in the path to the next. If no valid path can be discovered between two large grid units within a 16 block range, the connection is removed from the large grid and the large grid is recalculated. If paths are found, the distance from one node to the next is saved as the distance between both nodes on the larger grid.

This approach also has a few other benefits. Because the solver is running multiple separate pathfinding instances, these can be run async. This means that after the large grid solves at the first small grid solves, the bot can start moving instantly while the rest of the path solves. The bot can start traveling a distance of 10,000+ blocks without any initial delay.

Also, various post-processing effects can be applied to the large grid as well, such as straight pathing.

Lastly, when coming to situations such as the spawn wall of 2b2t (A famous wall around spawn, with a radius of 2km) The bot would be smart enough to know how to tackle going around this. The solver would just tackle finding new paths for the smaller grid instances, so all paths can be correctly solved with <10ms of processing on each segment. The bot would keep trimming connections on the large grid where paths could not be found, meaning the bot would learn to go around the wall after seeing the chunks download. In theory, the bot could literally sprint an entire 10km distance on unknown terrain without pausing or stopping to update more of the path as chunks are downloaded.

enable github action for CI

I advise the recommended github action for CI for node (just go to action tab, you'll see it), it's simple and it works

Goto callback not marked as optional

Using the bot.pathfinder.goto() function, the callback option does not have a default value and will throw an error if the function is used without providing a callback.

How to change options?

Thanks for clicking on this post.
I just got a question: how to edit options so i can prevent the bot from griefing all world?

[Question] How to follow entity indefinitely/until stopped

I'm trying to pick up an item after mining, even if this item drops down to somewhere.
For this I am using the GoalFollow goal, but it doesn't seem to work. The bot stops after reaching the item's initial position and the goal_reached event is fired.
I tried this recursive approach but to no avail:

                           function keepFollowing() {
                                console.log("function keepFollowing called");
                                bot.pathfinder.setGoal(new GoalFollow(itemEntity, 0));
                                bot.once("goal_reached", () => {
                                    console.log("goal_reached fired");
                                    if (!itemGone) {
                                        keepFollowing();
                                    }
                                });
                            }

As you can see in this video, the bot never continues: https://youtu.be/VWl5RxJHCPk

The console output looks like this after the bot has stopped:

function keepFollowing called
goal_reached fired
function keepFollowing called

The bot stops trying to get to the item once it is picked up, by it or by me. This is expected.
Is there a better approach? Did I miss something?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.