├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── .vscode └── settings.json ├── README.md ├── docs ├── API.md └── AdvancedUsage.md ├── examples ├── README.md ├── basic.js ├── basic.ts ├── customClassMaking.js ├── example.js ├── example.ts ├── partialPathTest.js ├── randomTests.js └── webui.js ├── package.json ├── src ├── PathfinderHandling.ts ├── PathingUtil.ts ├── ThePathfinder.ts ├── abstract │ ├── algorithms │ │ ├── astar.ts │ │ └── index.ts │ ├── heap.ts │ ├── index.ts │ └── node.ts ├── customHashmap │ └── Int64Map │ │ ├── .vscode │ │ └── settings.json │ │ ├── src │ │ ├── Int64Map.ts │ │ ├── new │ │ │ ├── Int64Map.ts │ │ │ └── test.ts │ │ └── test.ts │ │ └── tsconfig.json ├── index.ts ├── mineflayer-specific │ ├── algs.ts │ ├── custom.ts │ ├── exceptions.ts │ ├── goals.ts │ ├── index.ts │ ├── move.ts │ ├── movements │ │ ├── baritone │ │ │ ├── baritoneProviders.ts │ │ │ └── movementHelper.ts │ │ ├── controls.ts │ │ ├── costs.ts │ │ ├── index.ts │ │ ├── interactionUtils.ts │ │ ├── movement.ts │ │ ├── movementExecutor.ts │ │ ├── movementExecutors.ts │ │ ├── movementProvider.ts │ │ ├── movementProviders.ts │ │ ├── movementUtils.ts │ │ ├── pp.ts │ │ └── simulators │ │ │ └── jumpSim.ts │ ├── node.ts │ ├── pathProducers │ │ ├── continuousPathProducer.ts │ │ ├── index.ts │ │ └── partialPathProducer.ts │ ├── performer.ts │ ├── post │ │ ├── index.ts │ │ ├── optimizer.ts │ │ ├── optimizers.ts │ │ ├── replacement.ts │ │ └── replacements.ts │ └── world │ │ ├── cacheWorld.ts │ │ ├── index.ts │ │ ├── interactable.ts │ │ ├── utils.ts │ │ └── worldInterface.ts ├── types.ts └── utils.ts ├── tsconfig.json └── yarn.lock /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | Lint: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Use Node.js 18.x 14 | uses: actions/setup-node@v1.4.4 15 | with: 16 | node-version: 18.x 17 | - run: npm i && npx ts-standard -y -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | yarn-* 4 | yarn.lock 5 | package-lock.json 6 | vscode-profile-* 7 | flamegraph.htm -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # specifically shouldn't be published to npm 2 | .gitignore 3 | .github/ 4 | examples/ 5 | yarn* 6 | *.log -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "print.colourScheme": "GitHub" 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

minecraft-pathfinding :tm:

6 |

A pathfinder to get a Minecraft bot from point A to point B with unnecessarily stylish movement

7 | 8 |

9 | 10 | Discord 11 | 12 |

13 | 14 | > [!WARNING] 15 | > This pathfinder is still in **HEAVY** development. It is not meant to be used in production. However, you can use the code in `examples` to understand how the pathfinder works. 16 | 17 |

Why use this pathfinder?

18 | 19 | ----- 20 | 21 | Presently, its execution is better than both Baritone's and mineflayer-pathfinder's. It also follows an entirely different paradigm - each move's execution is split into individual parts, which combined with a modular movement provider/executor/optimizer system, it makes this pathfinder ***extremely*** customizable. So as long as a single movement's execution handles all entry cases (which is possible), it can be immediately integrated into this bot. 22 | 23 | Examples will be added as the project undergoes more development. 24 | 25 |

What is left to be done?

26 | 27 | ----- 28 | 29 | **Many** things. 30 | 31 | - [X] Proper breaking of blocks (Cost + Execution) 32 | - [X] Water movement 33 | - [X] Parkour 34 | - [X] Proper jump sprinting 35 | - [ ] Offloading the world thread 36 | 37 | 38 |

API and Examples

39 | 40 | ----- 41 | 42 | | Link | Description | 43 | | --- | --- | 44 | | [API](https://github.com/GenerelSchwerz/minecraft-pathfinding/blob/main/docs/API.md) | The documentation with the available methods and properties. | 45 | | [Advanced Usage](https://github.com/GenerelSchwerz/minecraft-pathfinding/blob/main/docs/AdvancedUsage.md) | The documentation with the advanced usage of the pathfinder, including the customization of goals and movements. | 46 | | [Examples](https://github.com/GenerelSchwerz/minecraft-pathfinding/tree/main/examples) | The folder with the examples. | 47 | -------------------------------------------------------------------------------- /docs/AdvancedUsage.md: -------------------------------------------------------------------------------- 1 |

Advanced Usage!

2 | 3 |

Table of Contents

4 | 5 | - [Custom Goals](#custom-goals) 6 | - [Custom goals.Goal](#custom-goal) 7 | - [Custom goals.GoalDynamic](#Custom-goaldynamic) 8 | - [Movement Customization](#movement-customization) 9 | - [Custom Movement Providers](#custom-movement-providers) 10 | - [Custom Movement Executors](#custom-movement-executors) 11 | - [Custom Movement Optimizers](#custom-movement-optimizers) 12 | 13 | 14 | 15 |

Custom Goals

16 | 17 | This pathfinder supports any type of goal, provided they extend our base classes `goals.Goal` and `goals.GoalDynamic`. These classes are designed to be extended and provide a simple interface for creating custom goals. 18 | 19 | `goals.Goal` is the simpler goal type. It is static, meaning it cannot update itself based on events. 20 | 21 | `goals.GoalDynamic` is the more complex goal type. It is dynamic, meaning it can update itself based on events. Because of this, both `hasChanged` and `isValid` methods, which implement when *the goal has moved* and *whether the goal is still worth moving towards*, respectively, are required to be implemented. 22 | 23 | Both of these classes are abstract, meaning you cannot create an instance of them directly. Instead, you must create a subclass of them and implement the required methods. 24 | 25 |

Custom Goal

26 | 27 | To create a subclass of `goals.Goal`, you need to implement the `isEnd` and `heuristic` methods. You can also override the `onFinish` method to perform any cleanup or additional actions when the goal is finished. 28 | 29 | 30 |

Example

31 | 32 | 33 | ```ts 34 | 35 | import { goals, MovementExecutor } from '@nxg-org/mineflayer-pathfinder' 36 | const {Goal} = goals 37 | 38 | class MyGoal extends Goal { 39 | isEnd (node: Move): boolean { 40 | // Return true if the goal is finished 41 | } 42 | 43 | heuristic (node: Move): number { 44 | // Return a number representing the cost of the node 45 | } 46 | 47 | async onFinish (node: MovementExecutor): Promise { 48 | // Perform any cleanup or additional actions when the goal is finished 49 | } 50 | } 51 | ``` 52 | 53 |

Custom GoalDynamic

54 | 55 | To create a subclass of `goals.GoalDynamic`, you need to implement all of the required methods for `goals.Goal` and also implement the `hasChanged`, `isValid`, and `update` methods. You will also have to specify the `eventKeys` and `validKeys` values and match them to your provided generic typing. 56 | 57 | 58 |

Example

59 | 60 | ```ts 61 | 62 | import { goals, BotEvents } from '@nxg-org/mineflayer-pathfinder' 63 | const {GoalDynamic} = goals 64 | 65 | class MyGoalDynamic extends GoalDynamic<'physicsTick', 'physicsTick'> { 66 | readonly eventKeys = 'physicsTick' as const // required for typing 67 | readonly validKeys = 'physicsTick' as const // required for typing 68 | 69 | isEnd (node: Move): boolean { 70 | // Return true if the goal is finished 71 | } 72 | 73 | heuristic (node: Move): number { 74 | // Return a number representing the cost of the node 75 | } 76 | 77 | hasChanged (event: 'physicsTick', username: string, message: string): boolean { 78 | // Return true if the event has changed 79 | } 80 | 81 | isValid (event: 'physicsTick', username: string, message: string): boolean { 82 | // Return true if the event is valid 83 | } 84 | 85 | // will be called whenever hasChanged is true. 86 | update (): void { 87 | // Update the goal 88 | } 89 | } 90 | ``` 91 | 92 |

Movement Customization

93 | 94 | 95 |

FAQ

96 | 97 | 98 |
Q: What is a Movement Provider?
99 | 100 | A: A Movement Provider is a class that is used to determine whether or not movement is possible. It is used to calculate the path based on the current movement logic. 101 | 102 |
Q: What is a Movement Executor?
103 | 104 | A: A Movement Executor is a class that is used to execute the path, performing any necessary actions to reach the goal. It is used to move and interact with the world. 105 | 106 |
Q: What is a Movement Optimizer?
107 | 108 | A: A Movement Optimizer is a class that is used to optimize the path, removing unnecessary nodes and making the path more efficient. It is used to optimize the path in a specific way. 109 | 110 |
Q: Can I customize an already existing Movement Provider by extending it?
111 | 112 | A: Yes, but you must de-register the Provider that was extended and register the new Provider with an Executor. 113 | 114 |
Q: Can I customize an already existing Movement Executor by extending it?
115 | 116 | A: Yes, but you must de-register the Executor that was extended and register the new Executor. 117 | 118 |
Q: Can I customize an already existing Movement Optimizer by extending it?
119 | 120 | A: Yes, but you must de-register the Optimizer that was extended and register the new Optimizer. 121 | 122 |
Q: Can I pair a custom Movement Executor with an already registered Provider?
123 | 124 | A: Yes. it will entirely overwrite the previous executor linked to that producer for all *future* paths generated. 125 | 126 |
Q: If I change the link between Producers and Executors, or Producers and Optimizers, will it affect the current path?
127 | 128 | A: No. The path is calculated and optimized based on the current links between Producers and Executors, and Producers and Optimizers. Changing the links will only affect future paths. 129 | 130 |

Custom Movement Providers: A Summary

131 | 132 | This pathfinder supports three levels of customization for movement: Movement Providers, Movement Executors, and Movement Optimizers. Each of these classes are designed to be extended and provide a simple interface for creating custom movement logic. 133 | 134 | To break down how this works, let's trace the code functionality. 135 | 136 | 1. We provide a goal to Pathfinder 137 | - now, pathfinder wants to create a path. 138 | 2. Pathfinder takes the current `MovementProviders` loaded in its settings and begins calculating the path based on them. 139 | - `MovementProviders` are only used at calculation time, not execution time. They are used to determine whether or not movement is possible. 140 | 3. The initial path has been calculated! 141 | 4. The pathfinder now takes the calculated path and *optimizes* it using `MovementOptimizers` 142 | - `MovementOptimizers` are used to optimize the path, removing unnecessary nodes and making the path more efficient. This is the step where straight-lining can be introduced, as normal A* does not provide this functionality well. *Note: see [here](#https://www.ijcai.org/Proceedings/09/Papers/303.pdf) for more information on straight-lining.* 143 | 5. The path has been optimized! 144 | 6. Provide this optimized path to the `goto` function in the pathfinder. 145 | 7. The pathfinder now takes the optimized path and begins executing it using `MovementExecutors`. 146 | - `MovementExecutors` are used to execute the path, performing any necessary actions to reach the goal. This is where the bot actually moves and interacts with the world. 147 | - `MovementExecutors` can provide runtime optimizations to the path itself via skipping nodes, but cannot modify the path itself. 148 | 8. **The path has been executed!** Or has it? 149 | - In the event that some factor (such as failure to execute or knocking off course) has caused the bot to go off course, The pathfinder will recalculate the path and repeat steps 4-7. 150 | - If the bot has gone off course due to an external event (such as a block update), the pathfinder will recalculate the path and repeat steps 4-7. 151 | - If the bot has reached the goal, the pathfinder will finish and the bot will stop moving. 152 | 153 | 154 | Providing customization to each step is important for creating a bot that can handle a wide variety of situations. For example, you may want to create a custom `MovementProvider` that can handle a specific type of block, or a custom `MovementExecutor` that can handle a specific type of movement. You may also want to create a custom `MovementOptimizer` that can optimize the path in a specific way. 155 | 156 | To add custom movement logic, you need to create a subclass of the appropriate class and implement the required methods. You can then provide an instance of your custom class to the pathfinder when creating a path. 157 | 158 |

Inserting custom classes into the pathfinder

159 | 160 |

Best Practice

161 | 162 | When developing custom extensions, it is best to include both the `Executor` and `Optimizer` for the `Provider`. It is not necessary, but recommended. 163 | 164 | 165 |

Movement Providers

166 | 167 | Because Providers cannot do anything on their own, we do not provide a method of adding them to the pathfinder alone. Instead, they are paired with an executor during insertion. 168 | 169 | Inserting a Provider **must** be with its static instance. This is so lookups across the pathfinder can be done with the static instance. 170 | 171 | The movement Executor can be either its static instance or a new instance of the class. We recommend using its **static instance**. 172 | 173 |

Inserting a Custom Movement Executor

174 | 175 | **Important!** Inserting an executor with a provider that has *no* optimizer does *not* break the code. Functionally, the bot will perform the unoptimized path. 176 | 177 | The provider list when calculating a path is *not* linked to the provider list that has executors. This means that if you add an executor to a provider that has no optimizer, the produced path will not be optimized. 178 | 179 | ```ts 180 | import { custom } from '@nxg-org/mineflayer-pathfinder' 181 | const {MovementProvider, MovementExecutor} = custom 182 | 183 | class MyProvider extends MovementProvider { 184 | // ... implementation 185 | } 186 | 187 | class MyExecutor extends MovementExecutor { 188 | // ... implementation 189 | } 190 | 191 | bot.pathfinder.setExecutor(MyProvider, MyExecutor) 192 | 193 | // OR: 194 | 195 | /* MovementOptions, this is not synced with pathfinder's settings */ 196 | const executor = new MyExecutor(bot, world, settings ) 197 | 198 | bot.pathfinder.setExecutor(MyProvider, executor) 199 | ``` 200 | 201 |

Inserting a Custom Movement Optimizer

202 | 203 | **Important!** Adding an optimizer paired with a provider that has *no* executor does *not* break the code. Functionally, nothing will change. 204 | 205 | The provider list when calculating a path is *not* linked to the provider list that has optimizers. This means that if you add an optimizer to a provider that has no executor, the optimizer will not be used. 206 | 207 | This allows providers to be removed from the calculation step without needing to remove the optimizer as well. 208 | 209 | 210 | ```ts 211 | import { custom } from '@nxg-org/mineflayer-pathfinder' 212 | const {MovementProvider, MovementOptimizer} = custom 213 | 214 | class MyProvider extends MovementProvider { 215 | // ... implementation 216 | } 217 | 218 | class MyOptimizer extends MovementOptimizer { 219 | // ... implementation 220 | } 221 | 222 | bot.pathfinder.setOptimizer(MyProvider, MyOptimizer) 223 | 224 | // OR: 225 | 226 | const optimizer = new MyOptimizer(bot, world) 227 | 228 | bot.pathfinder.setOptimizer(MyProvider, optimizer) 229 | 230 | ``` 231 | 232 | 233 | 234 | 235 |

Custom Movement Providers

236 | 237 | To create a subclass of `MovementProvider`, you need to implement the `provideMovements` method. This method is responsible for deciding whether or not movement is possible and, if possible, appending to the provided storage. 238 | 239 |

Example

240 | 241 | ```ts 242 | 243 | import { Move, goals, custom } from '@nxg-org/mineflayer-pathfinder' 244 | const {MovementProvider} = custom 245 | 246 | class MyMovementProvider extends MovementProvider { 247 | movementDirs = [ 248 | new Vec3(1, 0, 0), 249 | new Vec3(-1, 0, 0), 250 | new Vec3(0, 0, 1), 251 | new Vec3(0, 0, -1) 252 | ] // often used in provideMovements to provide all directions. 253 | 254 | provideMovements (start: Move, storage: Move[], goal: goals.Goal, closed: Set): void { 255 | // Decide whether or not movement is possible 256 | // If possible, append to provided storage 257 | } 258 | } 259 | ``` 260 | 261 |

Custom Movement Executors

262 | 263 | To create a subclass of `MovementExecutor`, you need to implement the `performInit`, `performPerTick`, and `align` methods. 264 | 265 |

Example

266 | 267 | ```ts 268 | import { Move, goals, custom } from '@nxg-org/mineflayer-pathfinder' 269 | const {MovementExecutor} = custom; 270 | 271 | class MyMovementExecutor extends MovementExecutor { 272 | 273 | async align (thisMove: Move, tickCount?: number, goal?: goals.Goal, lookTarget?: Vec3): Promise { 274 | // Perform modifications on bot BEFORE attempting the move 275 | // This can be used to align to the center of blocks, etc. 276 | // Align IS allowed to throw exceptions, it will revert to recovery 277 | } 278 | async performInit (thisMove: Move, currentIndex: number, path: Move[]): Promise { 279 | // Perform initial setup upon movement start 280 | } 281 | 282 | async performPerTick (thisMove: Move, tickCount: number, currentIndex: number, path: Move[]): Promise { 283 | // Perform modifications on bot per-tick 284 | // Return whether or not bot has reached the goal 285 | } 286 | 287 | 288 | } 289 | ``` 290 | 291 |

Custom Movement Optimizers

292 | 293 | To create a subclass of `MovementOptimizer`, you need to implement the `identEndOpt` method. 294 | 295 |

Example

296 | 297 | ```ts 298 | import { Move, goals, custom } from '@nxg-org/mineflayer-pathfinder' 299 | const {MovementOptimizer} = custom; 300 | 301 | class MyMovementOptimizer extends MovementOptimizer { 302 | async identEndOpt (currentIndex: number, path: Move[]): Promise { 303 | // Return the index of the last move in the path 304 | } 305 | } 306 | ``` 307 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Minecraft Pathfinding Examples 2 | 3 | `basic.js` shows off the basic functionality of this pathfinder, while `example.js` goes into more depth. -------------------------------------------------------------------------------- /examples/basic.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const { createBot } = require("mineflayer"); 3 | const { createPlugin, goals } = require("../dist"); 4 | const { GoalBlock, GoalLookAt } = goals; 5 | const { Vec3 } = require("vec3"); 6 | const rl = require('readline') 7 | const { default: loader, EntityState } = require("@nxg-org/mineflayer-physics-util"); 8 | 9 | 10 | 11 | const bot = createBot({ 12 | username: "testing1", 13 | auth: "offline", 14 | // host: 'it-mil-1.halex.gg', 15 | // port: 25046 16 | version: '1.21.1', 17 | 18 | // host: "node2.endelon-hosting.de", port: 5000 19 | host: 'localhost', 20 | port: 25565, 21 | // port: 44656 22 | // host: "us1.node.minecraft.sneakyhub.com", 23 | // port: 25607, 24 | }); 25 | const pathfinder = createPlugin(); 26 | 27 | const validTypes = ["block" , "lookat"] 28 | let type = "block" 29 | function getGoal(world, x, y, z) { 30 | const block = bot.blockAt(new Vec3(x, y, z)); 31 | if (block === null) return new GoalBlock(x, y+1, z); 32 | switch (type) { 33 | case "block": 34 | return new GoalBlock(x, y+1, z); 35 | case "lookat": 36 | return GoalLookAt.fromBlock(world, block); 37 | } 38 | 39 | return new GoalBlock(x, y+1, z); 40 | } 41 | 42 | 43 | bot.on("inject_allowed", () => {}); 44 | bot.once('login', () => { 45 | console.info('Bot logged in'); 46 | }); 47 | bot.on('messagestr', (message) => { 48 | console.info('Chat:', message); 49 | }) 50 | bot.on('actionBar', (message) => { 51 | console.info('Action bar:', message); 52 | }) 53 | bot.once("spawn", async () => { 54 | bot.loadPlugin(pathfinder); 55 | bot.loadPlugin(loader); 56 | 57 | bot.physics.autojumpCooldown = 0; 58 | 59 | const rlline = rl.createInterface({ 60 | input: process.stdin, 61 | output: process.stdout 62 | 63 | }) 64 | 65 | 66 | rlline.on('line', (line) => { 67 | if (line === "exit") { 68 | bot.quit() 69 | process.exit() 70 | } 71 | 72 | bot.chat(line) 73 | }) 74 | 75 | 76 | }); 77 | 78 | 79 | async function cmdHandler(username, msg) { 80 | if (username === bot.username) return; 81 | 82 | const [cmd1, ...args] = msg.split(" "); 83 | 84 | const cmd = cmd1.toLowerCase().replace(prefix, ""); 85 | 86 | switch (cmd) { 87 | case "cancel": 88 | case "stop": { 89 | bot.whisper(username, "Canceling path"); 90 | await bot.pathfinder.cancel(); 91 | bot.clearControlStates(); 92 | break; 93 | } 94 | 95 | case "pos": { 96 | bot.whisper(username, `I am at ${bot.entity.position}`); 97 | console.log(`/tp ${bot.username} ${bot.entity.position.x} ${bot.entity.position.y} ${bot.entity.position.z}`); 98 | break; 99 | } 100 | 101 | case "goto": { 102 | const x = Math.floor(Number(args[0])); 103 | const y = Math.floor(Number(args[1])); 104 | const z = Math.floor(Number(args[2])); 105 | if (isNaN(x) || isNaN(y) || isNaN(z)) return bot.whisper(username, "goto failed | invalid args"); 106 | 107 | bot.whisper(username, `going to ${args[0]} ${args[1]} ${args[2]}`); 108 | 109 | await bot.pathfinder.goto(new GoalBlock(x,y,z)); 110 | 111 | break; 112 | } 113 | } 114 | } 115 | 116 | const prefix = "!"; 117 | bot.on("chat", async (username, msg) => { 118 | await cmdHandler(username, msg); 119 | }); 120 | 121 | bot.on("messagestr", async (msg, pos, jsonMsg) => { 122 | const username = bot.nearestEntity((e) => e.type === "player" && e !== bot.entity)?.username ?? "unknown"; 123 | await cmdHandler(username, msg); 124 | }); 125 | 126 | bot.on('error', (err) => { 127 | console.log('Bot error', err) 128 | }) 129 | bot.on('kicked', (reason) => { 130 | console.log('Bot kicked', reason) 131 | }) -------------------------------------------------------------------------------- /examples/basic.ts: -------------------------------------------------------------------------------- 1 | const { createBot } = require("mineflayer"); 2 | import { createPlugin, goals, custom } from "../src"; 3 | const { GoalBlock, GoalLookAt } = goals; 4 | const { MovementExecutor, MovementOptimizer } = custom; 5 | const { Vec3 } = require("vec3"); 6 | const rl = require('readline') 7 | const { default: loader, EntityState } = require("@nxg-org/mineflayer-physics-util"); 8 | 9 | 10 | 11 | 12 | const bot = createBot({ 13 | username: "testing1", 14 | auth: "offline", 15 | // host: 'it-mil-1.halex.gg', 16 | // port: 25046 17 | version: '1.19.4', 18 | 19 | // host: "node2.endelon-hosting.de", port: 5000 20 | host: 'Ic3TankD2HO.aternos.me', 21 | // port: 44656 22 | // host: "us1.node.minecraft.sneakyhub.com", 23 | // port: 25607, 24 | }); 25 | const pathfinder = createPlugin(); 26 | 27 | const validTypes = ["block" , "lookat"] 28 | let type = "block" 29 | function getGoal(world, x, y, z) { 30 | const block = bot.blockAt(new Vec3(x, y, z)); 31 | if (block === null) return new GoalBlock(x, y+1, z); 32 | switch (type) { 33 | case "block": 34 | return new GoalBlock(x, y+1, z); 35 | case "lookat": 36 | return GoalLookAt.fromBlock(world, block); 37 | } 38 | 39 | return new GoalBlock(x, y+1, z); 40 | } 41 | 42 | 43 | bot.on("inject_allowed", () => {}); 44 | bot.once('login', () => { 45 | console.info('Bot logged in'); 46 | }); 47 | bot.on('messagestr', (message) => { 48 | console.info('Chat:', message); 49 | }) 50 | bot.on('actionBar', (message) => { 51 | console.info('Action bar:', message); 52 | }) 53 | bot.once("spawn", async () => { 54 | bot.loadPlugin(pathfinder); 55 | bot.loadPlugin(loader); 56 | 57 | bot.physics.autojumpCooldown = 0; 58 | 59 | const rlline = rl.createInterface({ 60 | input: process.stdin, 61 | output: process.stdout 62 | 63 | }) 64 | 65 | 66 | rlline.on('line', (line) => { 67 | if (line === "exit") { 68 | bot.quit() 69 | process.exit() 70 | } 71 | 72 | bot.chat(line) 73 | }) 74 | 75 | 76 | }); 77 | 78 | 79 | async function cmdHandler(username, msg) { 80 | if (username === bot.username) return; 81 | 82 | const [cmd1, ...args] = msg.split(" "); 83 | 84 | const cmd = cmd1.toLowerCase().replace(prefix, ""); 85 | 86 | switch (cmd) { 87 | case "cancel": 88 | case "stop": { 89 | bot.whisper(username, "Canceling path"); 90 | await bot.pathfinder.cancel(); 91 | bot.clearControlStates(); 92 | break; 93 | } 94 | 95 | case "pos": { 96 | bot.whisper(username, `I am at ${bot.entity.position}`); 97 | console.log(`/tp ${bot.username} ${bot.entity.position.x} ${bot.entity.position.y} ${bot.entity.position.z}`); 98 | break; 99 | } 100 | 101 | case "goto": { 102 | const x = Math.floor(Number(args[0])); 103 | const y = Math.floor(Number(args[1])); 104 | const z = Math.floor(Number(args[2])); 105 | if (isNaN(x) || isNaN(y) || isNaN(z)) return bot.whisper(username, "goto failed | invalid args"); 106 | 107 | bot.whisper(username, `going to ${args[0]} ${args[1]} ${args[2]}`); 108 | 109 | await bot.pathfinder.goto(new GoalBlock(x,y,z)); 110 | 111 | break; 112 | } 113 | } 114 | } 115 | 116 | const prefix = "!"; 117 | bot.on("chat", async (username, msg) => { 118 | await cmdHandler(username, msg); 119 | }); 120 | 121 | bot.on("messagestr", async (msg, pos, jsonMsg) => { 122 | const username = bot.nearestEntity((e) => e.type === "player" && e !== bot.entity)?.username ?? "unknown"; 123 | await cmdHandler(username, msg); 124 | }); 125 | 126 | bot.on('error', (err) => { 127 | console.trace('Bot error', err) 128 | }) 129 | bot.on('kicked', (reason) => { 130 | console.trace('Bot kicked', reason) 131 | }) -------------------------------------------------------------------------------- /examples/customClassMaking.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const { createBot } = require("mineflayer"); 3 | const { createPlugin, goals, custom } = require("../dist"); 4 | const { GoalBlock, GoalLookAt } = goals; 5 | const { MovementProvider, MovementExecutor } = custom; 6 | const { Vec3 } = require("vec3"); 7 | const rl = require("readline"); 8 | const { Move } = require("../dist/mineflayer-specific/move"); 9 | const { CancelError } = require("../dist/mineflayer-specific/exceptions"); 10 | 11 | 12 | const bot = createBot({ 13 | username: "testing1", 14 | auth: "offline", 15 | version: "1.20.1", 16 | host: "node2.meowbot.de", 17 | port: 5000 18 | 19 | }); 20 | 21 | 22 | const pathfinder = createPlugin(); 23 | 24 | 25 | 26 | class MyProvider extends MovementProvider { 27 | offsets = [ 28 | { x: 1, y: 0, z: 0 }, 29 | { x: 0, y: 0, z: 1 }, 30 | { x: -1, y: 0, z: 0 }, 31 | { x: 0, y: 0, z: -1 }, 32 | ]; 33 | 34 | // provideMovements(start: Move, storage: Move[], goal: goals.Goal, closed: Set): void; 35 | 36 | /** 37 | * @param {Move} start 38 | * @param {Move[]} storage 39 | * @param {goals.Goal} goal 40 | * @param {Set} closed 41 | */ 42 | provideMovements(start, storage, goal, closed) { 43 | for (const dir of this.offsets) { 44 | const off = start.cachedVec.offset(dir.x, dir.y, dir.z); // cachedVec is already floored. 45 | if (closed.has(off.toString())) continue; 46 | this.checkAhead(start, dir, storage, goal, closed) 47 | } 48 | 49 | } 50 | 51 | /** 52 | * 53 | * @param {Move} start 54 | * @param {{x: number, y: number, z: number}} dir 55 | * @param {Move[]} storage 56 | * @param {goals.Goal} goal 57 | * @param {Set} closed 58 | */ 59 | checkAhead(start, dir, storage, goal, closed) { 60 | const blockN1 = this.getBlockInfo(start, 0, -1, 0); 61 | if (!blockN1.physical) return // no point in trying walk movement if block below is not physical. 62 | 63 | const targetN1 = this.getBlockInfo(start, dir.x, -1, dir.z); 64 | 65 | if (!targetN1.physical) return // don't walk to block that isn't physical 66 | 67 | const toPlace = []; 68 | const toBreak = []; 69 | 70 | let cost = 1 // move cost 71 | 72 | const target0 = this.getBlockInfo(start, dir.x, 0, dir.z); 73 | const target1 = this.getBlockInfo(start, dir.x, 1, dir.z); 74 | 75 | if ((cost += this.safeOrBreak(target0, toBreak)) > 100) return// auto-assigns break as necessary, returns cost of break 76 | 77 | // ! verbose version of above line: 78 | // if (!target0.safe) { 79 | // if (!target0.physical) return // cannot break block that is not physical and not safe 80 | 81 | // const { BreakHandler } = require("@nxg-org/mineflayer-pathfinder/dist/mineflayer-specific/movements/interactionUtils"); 82 | 83 | // cost += this.breakCost(target0); 84 | // if (cost > 100) return 85 | // toBreak.push(BreakHandler.fromVec(target0.position, 'solid')) 86 | // } 87 | 88 | 89 | if ((cost += this.safeOrBreak(target1, toBreak)) > 100) return // return if cost is too high 90 | 91 | const wantedExitPos = start.cachedVec.offset(dir.x + 0.5, 0, dir.z + 0.5) // center of block 92 | 93 | storage.push(Move.fromPrevious(cost, wantedExitPos, start, this, toPlace, toBreak)) 94 | } 95 | } 96 | 97 | class MyExecutor extends MovementExecutor { 98 | 99 | 100 | /** 101 | * Example code on how to easily provide movements for an executor. 102 | * 103 | * @param {Move} thisMove 104 | * @param {number} currentIndex current index of move in path 105 | * @param {Move[]} path Full path currently known 106 | * 107 | * @returns {void | Promise} 108 | */ 109 | async performInit(thisMove, currentIndex, path) { 110 | 111 | if (this.toBreakLen() > 0) { 112 | for (const breakH of this.toBreak()) { 113 | const info = await breakH.performInfo(this.bot); 114 | if (info.ticks !== Infinity) await this.performInteraction(breakH) 115 | } 116 | } 117 | 118 | this.lookAtPathPos(thisMove.exitPos) // look at Vec3 on yaw axis, looks straight ahead pitch-wise 119 | 120 | this.bot.setControlState('forward', true) 121 | this.bot.setControlState('sprint', true) 122 | 123 | } 124 | 125 | 126 | /** 127 | * @param {Move} thisMove 128 | * @param {number} tickCount 129 | * @param {number} currentIndex 130 | * @param {Move[]} path 131 | * @returns {boolean | number | Promise} 132 | */ 133 | async performPerTick(thisMove, tickCount, currentIndex, path) { 134 | if (tickCount > 40) throw new CancelError('Custom movement executor: took too long!') // cancel error ends current excution. 135 | 136 | if (this.bot.entity.position.y < thisMove.exitPos.y) throw new CancelError('too low!') 137 | 138 | void this.postInitAlignToPath(thisMove) // auto-handle alignment to exit position on this movement. 139 | 140 | // ! verbose usage of above: 141 | // void this.postInitAlignToPath(thisMove, {lookAtYaw: thisMove.exitPos}) // auto-handle alignment to exit position on this movement. 142 | 143 | return this.isComplete(thisMove) // ensure to return true if movement is completed 144 | } 145 | 146 | } 147 | 148 | 149 | 150 | bot.once("spawn", async () => { 151 | bot.loadPlugin(pathfinder); 152 | 153 | // clear all loaded movement providers 154 | bot.pathfinder.dropAllMovements(); 155 | 156 | bot.pathfinder.setExecutor(MyProvider, MyExecutor) 157 | 158 | bot.physics.autojumpCooldown = 0; 159 | 160 | 161 | const rlline = rl.createInterface({ 162 | input: process.stdin, 163 | output: process.stdout, 164 | }); 165 | 166 | rlline.on("line", (line) => { 167 | if (line === "exit") { 168 | bot.quit(); 169 | process.exit(); 170 | } 171 | 172 | bot.chat(line); 173 | }); 174 | 175 | await bot.waitForChunksToLoad(); 176 | bot.chat('rocky1928') 177 | }); 178 | 179 | async function cmdHandler(username, msg) { 180 | if (username === bot.username) return; 181 | 182 | const [cmd1, ...args] = msg.split(" "); 183 | 184 | const cmd = cmd1.toLowerCase().replace(prefix, ""); 185 | 186 | switch (cmd) { 187 | case "cancel": 188 | case "stop": { 189 | bot.whisper(username, "Canceling path"); 190 | await bot.pathfinder.cancel(); 191 | bot.clearControlStates(); 192 | break; 193 | } 194 | 195 | case "pos": { 196 | bot.whisper(username, `I am at ${bot.entity.position}`); 197 | console.log(`/tp ${bot.username} ${bot.entity.position.x} ${bot.entity.position.y} ${bot.entity.position.z}`); 198 | break; 199 | } 200 | 201 | case "goto": { 202 | const x = Math.floor(Number(args[0])); 203 | const y = Math.floor(Number(args[1])); 204 | const z = Math.floor(Number(args[2])); 205 | if (isNaN(x) || isNaN(y) || isNaN(z)) return bot.whisper(username, "goto failed | invalid args"); 206 | 207 | bot.whisper(username, `going to ${args[0]} ${args[1]} ${args[2]}`); 208 | 209 | await bot.pathfinder.goto(new GoalBlock(x, y, z)); 210 | 211 | break; 212 | } 213 | } 214 | } 215 | 216 | const prefix = "!"; 217 | bot.on("chat", async (username, msg) => { 218 | await cmdHandler(username, msg); 219 | }); 220 | 221 | bot.on("messagestr", async (msg, pos, jsonMsg) => { 222 | const username = bot.nearestEntity((e) => e.type === "player" && e !== bot.entity)?.username ?? "unknown"; 223 | await cmdHandler(username, msg); 224 | }); 225 | 226 | bot.on("error", (err) => { 227 | console.log("Bot error", err); 228 | }); 229 | bot.on("kicked", (reason) => { 230 | console.log("Bot kicked", reason); 231 | }); 232 | -------------------------------------------------------------------------------- /examples/example.ts: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | "use strict"; 3 | import {createBot} from 'mineflayer' 4 | import {createPlugin, goals} from '../src' 5 | const { GoalBlock } = goals; 6 | import { Vec3 } from 'vec3'; 7 | 8 | import { default as loader, EntityPhysics, EPhysicsCtx, EntityState, ControlStateHandler } from "@nxg-org/mineflayer-physics-util"; 9 | 10 | const bot = createBot({ username: "testing1", auth: "offline", 11 | // host: "Ic3TankD2HO.aternos.me", 12 | // port: 44656 13 | 14 | host: "localhost", 15 | port: 25565 16 | }); 17 | const pathfinder = createPlugin(); 18 | 19 | bot.on("inject_allowed", () => {}); 20 | 21 | bot.once("spawn", () => { 22 | bot.loadPlugin(pathfinder); 23 | bot.loadPlugin(loader); 24 | // bot.physics.yawSpeed = 5; 25 | 26 | // apply hot-fix to mineflayer's physics engine. 27 | const val = new EntityPhysics(bot.registry); 28 | EntityState.prototype.apply = function (bot) { 29 | this.applyToBot(bot); 30 | }; 31 | 32 | // EntityPhysics.prototype.simulate = function (ctx, world) { 33 | // bot.physics.simulatePlayer(ctx.state, world); 34 | // } 35 | 36 | bot.physics.autojumpCooldown = 0; 37 | // (bot.physics).jumpTicks = 0; 38 | 39 | // bot.jumpTicks = 0; 40 | const oldSim = bot.physics.simulatePlayer; 41 | bot.physics.simulatePlayer = (...args) => { 42 | bot.jumpTicks = 0 43 | const ctx = EPhysicsCtx.FROM_BOT(val, bot) 44 | ctx.state.jumpTicks = 0; // allow immediate jumping 45 | // ctx.state.control.set('sneak', true) 46 | return val.simulate(ctx, bot.world) 47 | return oldSim(...args); 48 | }; 49 | }); 50 | 51 | /** @type { Vec3 | null } */ 52 | let lastStart = null; 53 | 54 | bot.on("chat", async (username, msg) => { 55 | const [cmd, ...args] = msg.split(" "); 56 | const author = bot.nearestEntity((e) => e.username === username); 57 | 58 | 59 | switch (cmd) { 60 | 61 | case "cancel": 62 | case "stop": { 63 | bot.chat('Canceling path') 64 | bot.pathfinder.cancel(); 65 | break; 66 | } 67 | case "lookat": { 68 | bot.lookAt(new Vec3(parseFloat(args[0]), parseFloat(args[1]), parseFloat(args[2]))); 69 | break; 70 | } 71 | 72 | case "pos": { 73 | bot.chat(`I am at ${bot.entity.position}`); 74 | console.log(`/tp ${bot.username} ${bot.entity.position.x} ${bot.entity.position.y} ${bot.entity.position.z}`); 75 | break; 76 | } 77 | 78 | case "path": { 79 | lastStart = bot.entity.position.clone(); 80 | const res = bot.pathfinder.getPathTo(new GoalBlock(Number(args[0]), Number(args[1]), Number(args[2]))); 81 | let test; 82 | while ((test = await res.next()).done === false) { 83 | console.log(test); 84 | } 85 | break; 86 | } 87 | 88 | case "goto": 89 | case "#goto": { 90 | const x = Math.floor(Number(args[0])); 91 | const y = Math.floor(Number(args[1])); 92 | const z = Math.floor(Number(args[2])); 93 | if (isNaN(x) || isNaN(y) || isNaN(z)) return bot.chat("goto failed | invalid args"); 94 | bot.chat(`going to ${args[0]} ${args[1]} ${args[2]}`); 95 | 96 | const startTime = performance.now(); 97 | await bot.pathfinder.goto(new GoalBlock(x, y, z)); 98 | const endTime = performance.now(); 99 | bot.chat( 100 | `took ${(endTime - startTime).toFixed(3)} ms, ${Math.ceil((endTime - startTime) / 50)} ticks, ${( 101 | (endTime - startTime) / 102 | 1000 103 | ).toFixed(3)} seconds` 104 | ); 105 | break; 106 | } 107 | 108 | case "pathtome": { 109 | if (!author) return bot.chat("failed to find player."); 110 | bot.chat("hi"); 111 | const startTime = performance.now(); 112 | const res1 = bot.pathfinder.getPathTo(GoalBlock.fromVec(author.position)); 113 | let test1; 114 | let test2 = []; 115 | while ((test1 = await res1.next()).done === false) { 116 | test2.concat(test1.value.result.path); 117 | } 118 | const endTime = performance.now(); 119 | bot.chat( 120 | `took ${(endTime - startTime).toFixed(3)} ms, ${Math.ceil((endTime - startTime) / 50)} ticks, ${( 121 | (endTime - startTime) / 122 | 1000 123 | ).toFixed(3)} seconds` 124 | ); 125 | bot.chat(bot.pathfinder.world.getCacheSize()); 126 | console.log(test2.length); 127 | break; 128 | } 129 | 130 | case "repath": { 131 | if (!lastStart) { 132 | bot.chat("no last start"); 133 | return; 134 | } 135 | const res = bot.pathfinder.getPathFromTo(lastStart, bot.entity.velocity, GoalBlock.fromVec(author.position)); 136 | let test; 137 | while ((test = await res.next()).done === false) { 138 | console.log(test); 139 | } 140 | break; 141 | } 142 | 143 | case "cachesize": { 144 | bot.chat(bot.pathfinder.getCacheSize()); 145 | break; 146 | } 147 | 148 | case "togglecache": 149 | case "cache": { 150 | bot.pathfinder.setCacheEnabled(!bot.pathfinder.isCacheEnabled()); 151 | bot.chat(`pathfinder cache is now ${bot.pathfinder.isCacheEnabled() ? "enabled" : "disabled"}`); 152 | break; 153 | } 154 | 155 | case "therepos": { 156 | if (!author) return bot.chat("failed to find player"); 157 | const authorPos = author.position.clone(); 158 | const rayBlock = rayTraceEntitySight({ entity: author }); 159 | if (!rayBlock) return bot.chat("No block in sight"); 160 | else return bot.chat(`Block in sight: ${rayBlock.position.x} ${rayBlock.position.y} ${rayBlock.position.z}`); 161 | } 162 | case "there": { 163 | if (!author) return bot.chat("failed to find player"); 164 | const authorPos = author.position.clone(); 165 | const rayBlock = rayTraceEntitySight({ entity: author }); 166 | if (!rayBlock) return bot.chat("No block in sigth"); 167 | lastStart = rayBlock.position.clone().offset(0.5, 1, 0.5); 168 | const startTime = performance.now(); 169 | await bot.pathfinder.goto(GoalBlock.fromVec(lastStart)); 170 | const endTime = performance.now(); 171 | bot.chat( 172 | `took ${(endTime - startTime).toFixed(3)} ms, ${Math.ceil((endTime - startTime) / 50)} ticks, ${( 173 | (endTime - startTime) / 174 | 1000 175 | ).toFixed(3)} seconds` 176 | ); 177 | break; 178 | } 179 | 180 | case "test": { 181 | if (!author) return bot.chat("failed to find player."); 182 | lastStart = author.position.clone(); 183 | bot.chat("hi"); 184 | const startTime = performance.now(); 185 | await bot.pathfinder.goto(GoalBlock.fromVec(author.position)); 186 | const endTime = performance.now(); 187 | bot.chat( 188 | `took ${(endTime - startTime).toFixed(3)} ms, ${Math.ceil((endTime - startTime) / 50)} ticks, ${( 189 | (endTime - startTime) / 190 | 1000 191 | ).toFixed(3)} seconds` 192 | ); 193 | break; 194 | } 195 | 196 | case "placeblock": { 197 | await bot.equip(bot.registry.itemsByName.dirt.id, "hand"); 198 | await bot.placeBlock(bot.blockAtCursor(5), new Vec3(parseInt(args[0]), parseInt(args[1]), parseInt(args[2]))); 199 | break; 200 | } 201 | 202 | case "jump": { 203 | bot.physics.jump(); 204 | break; 205 | } 206 | 207 | case "stop": { 208 | bot.pathfinder.stop(); 209 | break; 210 | } 211 | } 212 | }); 213 | 214 | bot._client.on('animation', (data) => { 215 | if (data.animation !== 0) return 216 | const entity = bot.entities[data.entityId] 217 | if (!entity || entity.type !== 'player') return 218 | if (!entity.heldItem || entity.heldItem.name !== 'stick') return 219 | const block = rayTraceEntitySight({ entity: entity }); 220 | console.log('saw block', block?.position) 221 | if (!block) return 222 | bot.pathfinder.goto(GoalBlock.fromVec(block.position.offset(0.5, 1, 0.5))); 223 | }) 224 | 225 | /** 226 | * @param { { entity: import('mineflayer').Entity } } options 227 | */ 228 | function rayTraceEntitySight(options) { 229 | if (bot.world?.raycast) { 230 | const { height, position, yaw, pitch } = options.entity; 231 | const x = -Math.sin(yaw) * Math.cos(pitch); 232 | const y = Math.sin(pitch); 233 | const z = -Math.cos(yaw) * Math.cos(pitch); 234 | const rayBlock = bot.world.raycast(position.offset(0, height, 0), new Vec3(x, y, z), 120); 235 | if (rayBlock) { 236 | return rayBlock; 237 | } 238 | } 239 | return null; 240 | } 241 | 242 | bot.on("kicked", console.log); 243 | -------------------------------------------------------------------------------- /examples/partialPathTest.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const { createBot } = require("mineflayer"); 3 | const { createPlugin, goals } = require("../dist"); 4 | const { GoalBlock, GoalLookAt, GoalPlaceBlock } = goals; 5 | const { Vec3 } = require("vec3"); 6 | const rl = require('readline') 7 | const { default: loader, EntityState, EPhysicsCtx, EntityPhysics } = require("@nxg-org/mineflayer-physics-util"); 8 | const { GoalMineBlock } = require("../dist/mineflayer-specific/goals"); 9 | const { ThePathfinder } = require("../dist/ThePathfinder"); 10 | const { performance } = require("perf_hooks"); 11 | 12 | const bot = createBot({ 13 | username: "testing1", 14 | auth: "offline", 15 | 16 | host: "node2.meowbot.de", port: 5000 17 | // host: 'Ic3TankD2HO.aternos.me', 18 | // version: '1.19.4' 19 | }); 20 | const pathfinder = createPlugin(); 21 | 22 | const targetGoal = new GoalBlock(103, 64, -201) 23 | 24 | function chatEverywhere(message) { 25 | bot.chat(message); 26 | console.log(message); 27 | } 28 | 29 | async function debugPath(goal, partialPathProducer) { 30 | bot.pathfinder.world.setEnabled(true) 31 | bot.pathfinder.setOptions({ partialPathProducer }) 32 | console.info('Target:', new Vec3(goal.x, goal.y, goal.z).toString()) 33 | const generator = bot.pathfinder.getPathFromTo(bot.entity.position, new Vec3(0, 0, 0), goal) 34 | 35 | const start = performance.now() 36 | let lastResult = undefined 37 | let nodeOverlap = 0 38 | 39 | let foo 40 | while ((!foo?.done || lastResult?.status !== 'success') && (foo = await generator.next())) { 41 | if (foo.value?.result) { 42 | const nodeSet = new Set() 43 | foo.value.result.path.forEach(node => { 44 | if (nodeSet.has(node.toString())) { 45 | nodeOverlap++ 46 | } else { 47 | nodeSet.add(node.toString()) 48 | } 49 | }) 50 | 51 | lastResult = foo.value.result 52 | } 53 | } 54 | 55 | const end = performance.now() 56 | 57 | const isDone = lastResult?.status === 'success' 58 | const visitedNodes = lastResult?.visitedNodes ?? 0 59 | const cost = lastResult?.cost ?? 0 60 | const lastMove = lastResult?.path?.[lastResult.path.length - 1] 61 | const lastPos = lastMove ? new Vec3(lastMove.x, lastMove.y, lastMove.z) : undefined 62 | if (isDone) { 63 | chatEverywhere(`✔️ Calc ${partialPathProducer ? 'partial' : 'full'}, Overlap: ${nodeOverlap} Visited: ${visitedNodes} (${(visitedNodes / ((end - start) / 1000)).toFixed(1)} n/s)`) 64 | chatEverywhere(` Time: ${end - start}ms`) 65 | chatEverywhere(` Cost: ${cost}, Length: ${lastResult.path.length}`) 66 | if (lastPos) { 67 | console.info(' Last move distance to goal', lastPos.distanceTo(new Vec3(goal.x, goal.y, goal.z))) 68 | } 69 | } else { 70 | chatEverywhere(`Calc done: ❌, Overlap: ${nodeOverlap} Visited: ${visitedNodes} (${(visitedNodes / ((end - start) / 1000)).toFixed(1)} n/s)`) 71 | } 72 | } 73 | 74 | bot.once("spawn", async () => { 75 | bot.loadPlugin(pathfinder); 76 | bot.loadPlugin(loader); 77 | EntityState.prototype.apply = function (bot) { 78 | this.applyToBot(bot); 79 | }; 80 | 81 | bot.on('resetPath', (reason)=>console.log('reset path!', reason)) 82 | 83 | bot.physics.autojumpCooldown = 0; 84 | 85 | const val = new EntityPhysics(bot.registry) 86 | const oldSim = bot.physics.simulatePlayer; 87 | bot.physics.simulatePlayer = (...args) => { 88 | bot.jumpTicks = 0 89 | const ctx = EPhysicsCtx.FROM_BOT(val, bot) 90 | ctx.state.jumpTicks = 0; // allow immediate jumping 91 | // ctx.state.control.set('sneak', true) 92 | return val.simulate(ctx, bot.world).applyToBot(bot); 93 | return oldSim(...args); 94 | }; 95 | bot.pathfinder.setMoveOptions({ 96 | canDig: false, 97 | canPlace: false, 98 | }) 99 | 100 | bot.chat('rocky1928') 101 | setTimeout(async () => { 102 | await new Promise((resolve) => setTimeout(resolve, 1000)) 103 | await bot.waitForChunksToLoad(); 104 | await debugPath(targetGoal, false) 105 | await debugPath(targetGoal, true) 106 | }, 1000) 107 | 108 | 109 | }); 110 | 111 | bot.on('chat', (username, message) => { 112 | if (username === bot.username) return; 113 | const args = message.split(" "); 114 | switch (args[0].toLowerCase()) { 115 | case "cachesize": { 116 | bot.whisper(username, bot.pathfinder.getCacheSize()); 117 | break; 118 | } 119 | 120 | case "togglecache": 121 | case "cache": { 122 | bot.pathfinder.setCacheEnabled(!bot.pathfinder.isCacheEnabled()); 123 | bot.whisper(username, `pathfinder cache is now ${bot.pathfinder.isCacheEnabled() ? "enabled" : "disabled"}`); 124 | break; 125 | } 126 | } 127 | }) 128 | 129 | bot._client.on("animation", (data) => { 130 | if (data.animation !== 0) return; 131 | const entity = bot.entities[data.entityId]; 132 | if (!entity || entity.type !== "player") return; 133 | if (!entity.heldItem || entity.heldItem.name !== "stick") return; 134 | const block = rayTraceEntitySight({ entity }); 135 | if (!block) return; 136 | const goal = new goals.GoalBlock(block.position.x, block.position.y + 1, block.position.z) 137 | debugPath(goal, true).then(() => { 138 | debugPath(goal, false) 139 | }).catch(console.error) 140 | }); 141 | 142 | /** 143 | * @param { { entity: import('mineflayer').Entity } } options 144 | */ 145 | function rayTraceEntitySight(options) { 146 | if (bot.world?.raycast) { 147 | const { height, position, yaw, pitch } = options.entity; 148 | const x = -Math.sin(yaw) * Math.cos(pitch); 149 | const y = Math.sin(pitch); 150 | const z = -Math.cos(yaw) * Math.cos(pitch); 151 | const rayBlock = bot.world.raycast(position.offset(0, height, 0), new Vec3(x, y, z), 4098); 152 | if (rayBlock) { 153 | return rayBlock; 154 | } 155 | } 156 | return null; 157 | } 158 | 159 | bot.on("kicked", console.log); 160 | -------------------------------------------------------------------------------- /examples/randomTests.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const { createBot } = require("mineflayer"); 3 | const { createPlugin, goals } = require("../dist"); 4 | const { GoalBlock, GoalLookAt } = goals; 5 | const { Vec3 } = require("vec3"); 6 | const rl = require('readline') 7 | const { default: loader, EntityState } = require("@nxg-org/mineflayer-physics-util"); 8 | 9 | 10 | // const ZERO = (0 * Math.PI) / 12 11 | // const PI_OVER_TWELVE = (1 * Math.PI) / 12 12 | const TWO_PI_OVER_TWELVE = (2 * Math.PI) / 12 13 | // const THREE_PI_OVER_TWELVE = (3 * Math.PI) / 12 14 | const FOUR_PI_OVER_TWELVE = (4 * Math.PI) / 12 15 | // const FIVE_PI_OVER_TWELVE = (5 * Math.PI) / 12 16 | // const SIX_PI_OVER_TWELVE = (6 * Math.PI) / 12 17 | // const SEVEN_PI_OVER_TWELVE = (7 * Math.PI) / 12 18 | const EIGHT_PI_OVER_TWELVE = (8 * Math.PI) / 12 19 | // const NINE_PI_OVER_TWELVE = (9 * Math.PI) / 12 20 | const TEN_PI_OVER_TWELVE = (10 * Math.PI) / 12 21 | // const ELEVEN_PI_OVER_TWELVE = (11 * Math.PI) / 12 22 | // const TWELVE_PI_OVER_TWELVE = (12 * Math.PI) / 12 23 | // const THIRTEEN_PI_OVER_TWELVE = (13 * Math.PI) / 12 24 | const FOURTEEN_PI_OVER_TWELVE = (14 * Math.PI) / 12 25 | // const FIFTEEN_PI_OVER_TWELVE = (15 * Math.PI) / 12 26 | const SIXTEEN_PI_OVER_TWELVE = (16 * Math.PI) / 12 27 | // const SEVENTEEN_PI_OVER_TWELVE = (17 * Math.PI) / 12 28 | // const EIGHTEEN_PI_OVER_TWELVE = (18 * Math.PI) / 12 29 | // const NINETEEN_PI_OVER_TWELVE = (19 * Math.PI) / 12 30 | const TWENTY_PI_OVER_TWELVE = (20 * Math.PI) / 12 31 | // const TWENTY_ONE_PI_OVER_TWELVE = (21 * Math.PI) / 12 32 | const TWENTY_TWO_PI_OVER_TWELVE = (22 * Math.PI) / 12 33 | // const TWENTY_THREE_PI_OVER_TWELVE = (23 * Math.PI) / 12 34 | // const TWENTY_FOUR_PI_OVER_TWELVE = (24 * Math.PI) / 12 35 | const TWO_PI = 2 * Math.PI 36 | 37 | function wrapRadians (radians) { 38 | const tmp = radians % (Math.PI * 2) 39 | // console.log('radians', radians, 'tmp', tmp, tmp < 0 ? tmp + Math.PI : tmp - Math.PI); 40 | return tmp < 0 ? tmp + (Math.PI * 2) : tmp 41 | // return tmp < 0 ? tmp + Math.PI : tmp > 0 ? tmp - Math.PI : tmp; 42 | } 43 | 44 | // currentPoint: Vec3 45 | function findDiff (position, velocity, yaw, pitch, nextPoint, onGround) { 46 | const xzVel = velocity.offset(0, -velocity.y, 0) 47 | // const dir1 = getViewDir({ yaw, pitch }) 48 | 49 | const amt = xzVel.norm() 50 | 51 | // if we're traveling fast enough, account ahead of time for the velocity. 52 | // 0.15 is about full speed for sprinting. Anything above that and we're jumping. 53 | // another method of doing this is vel.y > 0 ? 2 : 1 54 | // const offset = bot.entity.position.plus(bot.entity.velocity.scaled(amt > 0.15 ? 2 : 1)); 55 | let scale = onGround ? 0 : 1 56 | if (amt > 0.17) scale = 2 57 | if (position.distanceTo(nextPoint) < 0.3) scale = 0 58 | if (amt < 0.02) scale = 0 59 | const offset = position.plus(velocity.scaled(scale)) 60 | const lookDiff = wrapRadians(wrapRadians(yaw)) 61 | if (xzVel.norm() < 0.03) { 62 | // console.log("no vel, so different calc.", currentPoint, nextPoint, position); 63 | // return 0; 64 | 65 | const dir = nextPoint.minus(offset) 66 | const dx = dir.x 67 | const dz = dir.z 68 | 69 | // const dir1 = nextPoint.minus(bot.entity.position) 70 | // const dx1 = dir1.x 71 | // const dz1 = dir1.z 72 | 73 | const wantedYaw = wrapRadians(Math.atan2(-dx, -dz)) 74 | // const moveYaw = wrapRadians(Math.atan2(-dx1, -dz1)) 75 | 76 | const diff = wrapRadians(wantedYaw - lookDiff) 77 | // console.log('diff', diff) 78 | // // diff = wrapRadians(diff - lookDiff) 79 | 80 | // console.log('wantedYaw', wantedYaw) 81 | // console.log('moveYaw', moveYaw) 82 | // console.log('look diff', lookDiff) 83 | 84 | // console.log('entity yaw', bot.entity.yaw, lookDiff) 85 | // console.log('return', diff) 86 | // console.log("ratio", diff / Math.PI * 12, '\n\n') 87 | return diff 88 | } 89 | 90 | // const dx = nextPoint.x - currentPoint.x; 91 | // const dz = nextPoint.z - currentPoint.z; 92 | 93 | const dir = nextPoint.minus(offset) 94 | const dx = dir.x 95 | const dz = dir.z 96 | 97 | // const dir1 = bot.entity.velocity; 98 | // const dx1 = dir1.x 99 | // const dz1 = dir1.z 100 | 101 | const wantedYaw = wrapRadians(Math.atan2(-dx, -dz)) 102 | 103 | // console.log(nextPoint, currentPoint, dx, dz, dx1, dz1) 104 | 105 | // const moveYaw = wrapRadians(Math.atan2(-dx1, -dz1)); 106 | // const moveYaw = wrapRadians(Math.atan2(-dx1, -dz1)) 107 | 108 | const diff = wrapRadians(wantedYaw - lookDiff) 109 | // console.log('diff', diff) 110 | // // diff = wrapRadians(diff - lookDiff) 111 | 112 | // console.log('diff', diff) 113 | // diff = wrapRadians(diff + lookDiff) 114 | 115 | // console.log('wantedYaw', wantedYaw) 116 | // console.log('moveYaw', moveYaw) 117 | // console.log('look diff', lookDiff) 118 | // console.log('entity yaw', bot.entity.yaw, lookDiff) 119 | // console.log('return', diff) 120 | // console.log("ratio", diff / Math.PI * 12, '\n\n') 121 | return diff 122 | } 123 | 124 | /** 125 | * control strafing left-to-right dependent on offset to current goal. 126 | * @param nextPoint 127 | * @returns 128 | */ 129 | // currentPoint, 130 | function botStrafeMovement (bot, nextPoint) { 131 | const diff = findDiff(bot.entity.position, bot.entity.velocity, bot.entity.yaw, bot.entity.pitch, nextPoint, bot.entity.onGround) 132 | 133 | if (bot.entity.position.distanceTo(nextPoint) < 0.1) { 134 | // console.log('stopping since near goal') 135 | bot.setControlState('left', false) 136 | bot.setControlState('right', false) 137 | } 138 | 139 | if (FOURTEEN_PI_OVER_TWELVE < diff && diff < TWENTY_TWO_PI_OVER_TWELVE) { 140 | // console.log('going left') 141 | bot.setControlState('left', false) // are these reversed? tf 142 | bot.setControlState('right', true) 143 | } else if (TWO_PI_OVER_TWELVE < diff && diff < TEN_PI_OVER_TWELVE) { 144 | // console.log('going right') 145 | bot.setControlState('left', true) 146 | bot.setControlState('right', false) 147 | } else { 148 | // console.log('going neither strafe') 149 | bot.setControlState('left', false) 150 | bot.setControlState('right', false) 151 | } 152 | } 153 | 154 | /** 155 | * 156 | * @param bot 157 | * @param goal 158 | * @param sprint 159 | * @returns 160 | */ 161 | // currentPoint, 162 | function botSmartMovement (bot, nextPoint, sprint) { 163 | const diff = findDiff(bot.entity.position, bot.entity.velocity, bot.entity.yaw, bot.entity.pitch, nextPoint, bot.entity.onGround) 164 | 165 | if (bot.entity.position.distanceTo(nextPoint) < 0.1) { 166 | // console.log('stopping since near goal') 167 | bot.setControlState('forward', false) 168 | bot.setControlState('back', false) 169 | return 170 | } 171 | 172 | if (EIGHT_PI_OVER_TWELVE < diff && diff < SIXTEEN_PI_OVER_TWELVE) { 173 | // console.log('going back') 174 | bot.setControlState('forward', false) 175 | bot.setControlState('sprint', false) 176 | bot.setControlState('back', true) 177 | 178 | // console.log("back"); 179 | } else if (TWENTY_PI_OVER_TWELVE < diff || diff < FOUR_PI_OVER_TWELVE) { 180 | // console.log('going forward') 181 | bot.setControlState('forward', true) 182 | bot.setControlState('sprint', sprint) 183 | bot.setControlState('back', false) 184 | } else { 185 | // console.log('going neither') 186 | bot.setControlState('forward', false) 187 | bot.setControlState('sprint', false) 188 | bot.setControlState('back', false) 189 | } 190 | } 191 | 192 | 193 | 194 | const bot = createBot({ 195 | username: "testing1", 196 | auth: "offline", 197 | host: "node2.meowbot.de", port: 5000 198 | }); 199 | const pathfinder = createPlugin(); 200 | 201 | const validTypes = ["block" , "lookat"] 202 | let type = "block" 203 | function getGoal(world, x, y, z) { 204 | const block = bot.blockAt(new Vec3(x, y, z)); 205 | if (block === null) return new GoalBlock(x, y+1, z); 206 | switch (type) { 207 | case "block": 208 | return new GoalBlock(x, y+1, z); 209 | case "lookat": 210 | return GoalLookAt.fromBlock(world, block); 211 | } 212 | 213 | return new GoalBlock(x, y+1, z); 214 | } 215 | 216 | 217 | bot.on("inject_allowed", () => {}); 218 | bot.once('login', () => { 219 | console.info('Bot logged in'); 220 | }); 221 | 222 | 223 | bot.once("spawn", async () => { 224 | bot.loadPlugin(pathfinder); 225 | bot.loadPlugin(loader); 226 | 227 | bot.physics.autojumpCooldown = 0; 228 | 229 | const rlline = rl.createInterface({ 230 | input: process.stdin, 231 | output: process.stdout 232 | 233 | }) 234 | 235 | 236 | rlline.on('line', (line) => { 237 | if (line === "exit") { 238 | bot.quit() 239 | process.exit() 240 | } 241 | 242 | bot.chat(line) 243 | }) 244 | 245 | 246 | 247 | await bot.waitForTicks(20) 248 | bot.chat("rocky1928") 249 | 250 | }); 251 | 252 | 253 | async function cmdHandler(username, msg) { 254 | if (username === bot.username) return; 255 | 256 | const [cmd1, ...args] = msg.split(" "); 257 | 258 | const cmd = cmd1.toLowerCase().replace(prefix, ""); 259 | 260 | 261 | const author = bot.players[username]; 262 | switch (cmd) { 263 | 264 | case "test": { 265 | 266 | let oppDir = bot.entity.position.minus(author.entity.position); 267 | 268 | // rotate oppDir 90 degrees 269 | const temp = oppDir.x; 270 | oppDir.x = -oppDir.z; 271 | oppDir.z = temp; 272 | 273 | oppDir = oppDir.scaled(10).plus(author.entity.position); 274 | 275 | bot.lookAt(oppDir, true); 276 | 277 | while (bot.entity.position.distanceTo(author.entity.position) > 0.3) { 278 | botSmartMovement(bot, author.entity.position, true); 279 | botStrafeMovement(bot, author.entity.position); 280 | await bot.waitForTicks(1) 281 | } 282 | 283 | bot.clearControlStates(); 284 | 285 | console.log(author); 286 | break 287 | } 288 | 289 | case "cancel": 290 | case "stop": { 291 | bot.whisper(username, "Canceling path"); 292 | await bot.pathfinder.cancel(); 293 | bot.clearControlStates(); 294 | break; 295 | } 296 | 297 | case "pos": { 298 | bot.whisper(username, `I am at ${bot.entity.position}`); 299 | console.log(`/tp ${bot.username} ${bot.entity.position.x} ${bot.entity.position.y} ${bot.entity.position.z}`); 300 | break; 301 | } 302 | 303 | case "goto": { 304 | const x = Math.floor(Number(args[0])); 305 | const y = Math.floor(Number(args[1])); 306 | const z = Math.floor(Number(args[2])); 307 | if (isNaN(x) || isNaN(y) || isNaN(z)) return bot.whisper(username, "goto failed | invalid args"); 308 | 309 | bot.whisper(username, `going to ${args[0]} ${args[1]} ${args[2]}`); 310 | 311 | await bot.pathfinder.goto(new GoalBlock(x,y,z)); 312 | 313 | break; 314 | } 315 | } 316 | } 317 | 318 | const prefix = "!"; 319 | bot.on("chat", async (username, msg) => { 320 | await cmdHandler(username, msg); 321 | }); 322 | 323 | bot.on("messagestr", async (msg, pos, jsonMsg) => { 324 | const username = bot.nearestEntity((e) => e.type === "player" && e !== bot.entity)?.username ?? "unknown"; 325 | await cmdHandler(username, msg); 326 | }); 327 | 328 | bot.on('error', (err) => { 329 | console.log('Bot error', err) 330 | }) 331 | bot.on('kicked', (reason) => { 332 | console.log('Bot kicked', reason) 333 | }) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nxg-org/mineflayer-pathfinder", 3 | "version": "0.0.24", 4 | "description": "Pathfinder using A* for mineflayer", 5 | "main": "dist/index.js", 6 | "repository": "https://github.com/Minecraft-Pathfinding/minecraft-pathfinding.git", 7 | "author": "GenerelSchwerz ", 8 | "license": "MIT", 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "scripts": { 13 | "postinstall": "npm run build", 14 | "prepublishOnly": "npm run lint && npm run build", 15 | "build": "tsc -p ./tsconfig.json", 16 | "pub": "npm run lint && npm run build && npm publish --access public", 17 | "lint": "ts-standard -y --fix" 18 | }, 19 | "dependencies": { 20 | "@nxg-org/mineflayer-physics-util": "1.5.8", 21 | "@nxg-org/mineflayer-util-plugin": "^1.8.3", 22 | "lru-cache": "^10.1.0" 23 | }, 24 | "devDependencies": { 25 | "@nxg-org/mineflayer-pathfinder": "file:.", 26 | "cors": "^2.8.5", 27 | "express": "^4.18.2", 28 | "mineflayer": "^4.10.1", 29 | "prismarine-viewer": "^1.28.0", 30 | "ts-standard": "^12.0.2", 31 | "typescript": "^5.3.3", 32 | "vec3": "^0.1.8" 33 | }, 34 | "ts-standard": { 35 | "ignore": [ 36 | "examples" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/PathingUtil.ts: -------------------------------------------------------------------------------- 1 | import { Bot } from 'mineflayer' 2 | import { Block } from './types' 3 | import type { Item } from 'prismarine-item' 4 | import * as nbt from 'prismarine-nbt' 5 | 6 | export class PathingUtil { 7 | private items: Item[] = [] 8 | private tools: Item[] = [] 9 | 10 | private memoedDigSpeed: { [key: string]: number } = {} 11 | private memoedBestTool: { [key: string]: Item | null } = {} 12 | 13 | constructor (private readonly bot: Bot) { 14 | this.refresh() 15 | } 16 | 17 | public refresh (): void { 18 | this.items = this.bot.inventory.items() 19 | this.tools = this.items.filter( 20 | (item) => item.name.includes('pickaxe') || item.name.includes('axe') || item.name.includes('shovel') || item.name.includes('hoe') || item.name.includes('shears') 21 | ) 22 | this.memoedDigSpeed = {} 23 | this.memoedBestTool = {} 24 | } 25 | 26 | /** 27 | * TODO: Handle underwater digging. 28 | */ 29 | public bestHarvestingTool (block: Block): Item | null { 30 | if (block === null) return null 31 | if (this.memoedBestTool[block.type] != null) return this.memoedBestTool[block.type] 32 | 33 | const availableTools = this.tools 34 | const effects = this.bot.entity.effects 35 | 36 | const creative = this.bot.game.gameMode === 'creative' 37 | 38 | let fastest = Number.MAX_VALUE 39 | let bestTool = null as unknown as Item 40 | 41 | // if (creative === false) 42 | for (const tool of availableTools) { 43 | const enchants = tool.nbt != null ? nbt.simplify(tool.nbt).Enchantments : [] 44 | const digTime = block.digTime(tool.type, creative, false, false, enchants, effects) 45 | if (digTime < fastest) { 46 | fastest = digTime 47 | bestTool = tool 48 | } 49 | } 50 | 51 | // // default to no tools used in creative. 52 | if (fastest === Number.MAX_VALUE) { 53 | fastest = block.digTime(null, creative, false, false, [], effects) 54 | } 55 | 56 | this.memoedBestTool[block.type] = bestTool 57 | this.memoedDigSpeed[block.type] = fastest 58 | return bestTool 59 | } 60 | 61 | public digCost (block: Block): number { 62 | if (this.memoedDigSpeed[block.type] != null) return this.memoedDigSpeed[block.type] 63 | this.bestHarvestingTool(block) // run it to fill everything in. 64 | 65 | return this.memoedDigSpeed[block.type] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/abstract/algorithms/astar.ts: -------------------------------------------------------------------------------- 1 | import { reconstructPath } from '.' 2 | import { Goal, MovementProvider, Path, Algorithm } from '../' 3 | import { PathStatus } from '../../types' 4 | import { BinaryHeapOpenSet as Heap } from '../heap' 5 | // import {MinHeap as Heap} from 'heap-typed' 6 | import { CPathNode, PathData, PathNode } from '../node' 7 | 8 | export class AStar implements Algorithm { 9 | startTime: number 10 | goal: Goal 11 | timeout: number 12 | tickTimeout: number 13 | differential: number 14 | movementProvider: MovementProvider 15 | 16 | closedDataSet: Set 17 | openHeap: Heap> // Heap<> 18 | openDataMap: Map> 19 | 20 | bestNode: PathNode 21 | maxCost: number 22 | 23 | checkInterval = 0// 1 << 5 - 1 24 | nodeConsiderCount = 0 25 | 26 | constructor ( 27 | start: Data, 28 | movements: MovementProvider, 29 | goal: Goal, 30 | timeout: number, 31 | tickTimeout = 40, 32 | searchRadius = -1, 33 | differential = 0 34 | ) { 35 | this.startTime = performance.now() 36 | 37 | this.movementProvider = movements 38 | this.goal = goal 39 | this.timeout = timeout 40 | this.tickTimeout = tickTimeout 41 | this.differential = differential 42 | 43 | this.closedDataSet = new Set() 44 | this.openHeap = new Heap() // new Heap(undefined, {comparator: (a, b)=> a.f - b.f}) 45 | this.openDataMap = new Map() 46 | 47 | const startNode = new PathNode().update(0, goal.heuristic(start), start) 48 | 49 | // dumb type check, thanks ts-standard. 50 | if (startNode.data == null) throw new Error('Start node data is null!') 51 | 52 | // this.openHeap.add(startNode) 53 | this.openHeap.push(startNode) 54 | this.openDataMap.set(startNode.data.hash, startNode) 55 | this.bestNode = startNode 56 | 57 | this.maxCost = searchRadius < 0 ? -1 : startNode.h + searchRadius 58 | } 59 | 60 | protected addToClosedDataSet (node: PathNode): void { 61 | // data is not null here. Adding a check is slow. 62 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 63 | this.closedDataSet.add(node.data!.hash) 64 | } 65 | 66 | protected heuristic (node: Data): number { 67 | return this.goal.heuristic(node) 68 | } 69 | 70 | // for debugging. 71 | private lastAmt: number = 0 72 | makeResult (status: PathStatus, node: PathNode): Path> { 73 | // console.log( 74 | // status, 75 | // // this.goal, 76 | // performance.now() - this.startTime, 77 | // node.g, 78 | // this.closedDataSet.size, 79 | // this.closedDataSet.size + this.openHeap.size(), 80 | // reconstructPath(node).length, 81 | // `${this.closedDataSet.size - this.lastAmt} nodes visited in this tick.`, 82 | // // reconstructPath(node) 83 | 84 | // // used heap memory 85 | // Math.round((process.memoryUsage().heapUsed / 1024 / 1024) * 10) / 10, 86 | // 'MB' 87 | // ) 88 | 89 | this.lastAmt = this.closedDataSet.size 90 | 91 | return { 92 | status, 93 | cost: node.g, 94 | calcTime: performance.now() - this.startTime, 95 | visitedNodes: this.closedDataSet.size, 96 | generatedNodes: this.closedDataSet.size + this.openHeap.size(), 97 | movementProvider: this.movementProvider, 98 | path: reconstructPath(node), 99 | context: this 100 | } 101 | } 102 | 103 | compute (): Path> { 104 | const computeStartTime = performance.now() 105 | 106 | if (!this.movementProvider.sanitize()) { 107 | throw new Error('Movement Provider was not properly configured!') 108 | } 109 | 110 | while (!this.openHeap.isEmpty()) { 111 | if ((++this.nodeConsiderCount & this.checkInterval) === 0) { 112 | const time = performance.now() 113 | if (time - computeStartTime > this.tickTimeout) { 114 | // compute time per tick 115 | return this.makeResult('partial', this.bestNode) 116 | } 117 | if (this.timeout >= 0 && time - this.startTime > this.timeout) { 118 | // total compute time 119 | return this.makeResult('timeout', this.bestNode) 120 | } 121 | } 122 | // const node = this.openHeap.poll()! 123 | const node = this.openHeap.pop() 124 | 125 | // again, cannot afford another if-statement. 126 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 127 | if (this.goal.isEnd(node.data!)) { 128 | return this.makeResult('success', node) 129 | } 130 | // not done yet 131 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 132 | this.openDataMap.delete(node.data!.hash) 133 | 134 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 135 | const neighbors = this.movementProvider.getNeighbors(node.data!, this.closedDataSet) 136 | for (const neighborData of neighbors) { 137 | if (this.closedDataSet.has(neighborData.hash)) { 138 | continue // skip closed neighbors 139 | } 140 | const gFromThisNode = node.g + neighborData.cost 141 | const pastNeighborNode = this.openDataMap.get(neighborData.hash) 142 | 143 | const heuristic = this.heuristic(neighborData) 144 | if (this.maxCost > 0 && gFromThisNode + heuristic > this.maxCost) continue 145 | 146 | if (pastNeighborNode === undefined) { 147 | // add neighbor to the open set 148 | const neighbor = new CPathNode(gFromThisNode, heuristic, neighborData, node) 149 | 150 | if (neighbor.h < this.bestNode.h) this.bestNode = neighbor 151 | // properties will be set later 152 | this.openDataMap.set(neighborData.hash, neighbor) 153 | this.openHeap.push(neighbor) 154 | } else if (gFromThisNode - pastNeighborNode.g < this.differential) { 155 | pastNeighborNode.update(gFromThisNode, heuristic, neighborData, node) 156 | this.openHeap.update(pastNeighborNode) 157 | if (pastNeighborNode.h < this.bestNode.h) this.bestNode = pastNeighborNode 158 | } 159 | 160 | // allow specific implementations to access visited and closed data. 161 | this.addToClosedDataSet(node) 162 | 163 | // found a new or better route. 164 | // update this neighbor with this node as its new parent 165 | 166 | // console.log(neighborNode.data!.x, neighborNode.data!.y, neighborNode.data!.z, neighborNode.g, neighborNode.h) 167 | 168 | // if (update) { 169 | // // this.openHeap. 170 | // // // this.openHeap. 171 | // this.openHeap.update(neighborNode) 172 | // } else { 173 | // // this.openHeap.add(neighborNode) 174 | // this.openHeap.push(neighborNode) 175 | // } 176 | } 177 | } 178 | // all the neighbors of every accessible node have been exhausted 179 | return this.makeResult('noPath', this.bestNode) 180 | } 181 | } 182 | 183 | export class AStarBackOff extends AStar { 184 | bestNode0: PathNode = this.bestNode 185 | bestNode1: PathNode = this.bestNode 186 | bestNode2: PathNode = this.bestNode 187 | bestNode3: PathNode = this.bestNode 188 | bestNode4: PathNode = this.bestNode 189 | bestNode5: PathNode = this.bestNode 190 | bestNode6: PathNode = this.bestNode 191 | 192 | bn0: number = this.bestNode.h 193 | bn1: number = this.bestNode.h 194 | bn2: number = this.bestNode.h 195 | bn3: number = this.bestNode.h 196 | bn4: number = this.bestNode.h 197 | bn5: number = this.bestNode.h 198 | bn6: number = this.bestNode.h 199 | 200 | // these are stolen from baritone. Thanks guys. 201 | x0 = 1 / 1.5 202 | x1 = 1 / 2 203 | x2 = 1 / 2.5 204 | x3 = 1 / 3 205 | x4 = 1 / 4 206 | x5 = 1 / 5 207 | x6 = 1 / 10 208 | 209 | checkInterval = 0 // 1 << 5 - 1 210 | nodeConsiderCount = 0 211 | moveConsiderCount = 0 212 | 213 | assignBestNodes (check: PathNode): void { 214 | if (check.h < this.bestNode.h) { 215 | // mf-pathfinder way 216 | this.bestNode = check 217 | } 218 | 219 | if (check.h + check.g * this.x0 < this.bn0) { 220 | this.bestNode0 = check 221 | this.bn0 = check.h + check.g * this.x0 - this.differential 222 | } 223 | 224 | if (check.h + check.g * this.x1 < this.bn1) { 225 | this.bestNode1 = check 226 | this.bn1 = check.h + check.g * this.x1 - this.differential 227 | } 228 | 229 | if (check.h + check.g * this.x2 < this.bn2) { 230 | this.bestNode2 = check 231 | this.bn2 = check.h + check.g * this.x2 - this.differential 232 | } 233 | 234 | if (check.h + check.g * this.x3 < this.bn3) { 235 | this.bestNode3 = check 236 | this.bn3 = check.h + check.g * this.x3 - this.differential 237 | } 238 | 239 | if (check.h + check.g * this.x4 < this.bn4) { 240 | this.bestNode4 = check 241 | this.bn4 = check.h + check.g * this.x4 - this.differential 242 | } 243 | 244 | if (check.h + check.g * this.x5 < this.bn5) { 245 | this.bestNode5 = check 246 | this.bn5 = check.h + check.g * this.x5 - this.differential 247 | } 248 | 249 | if (check.h + check.g * this.x6 < this.bn6) { 250 | this.bestNode6 = check 251 | this.bn6 = check.h + check.g * this.x6 - this.differential 252 | } 253 | } 254 | 255 | getActualBestNode (): PathNode { 256 | // console.log('check bn6') 257 | if (this.bestNode6.h > 5) return this.bestNode6 258 | // console.log('check bn5') 259 | if (this.bestNode5.h > 5) return this.bestNode5 260 | // console.log('check bn4') 261 | if (this.bestNode4.h > 5) return this.bestNode4 262 | // console.log('check bn3') 263 | if (this.bestNode3.h > 5) return this.bestNode3 264 | // console.log('check bn2') 265 | if (this.bestNode2.h > 5) return this.bestNode2 266 | // console.log('check bn1') 267 | if (this.bestNode1.h > 5) return this.bestNode1 268 | // console.log('check bn0') 269 | if (this.bestNode0.h > 5) return this.bestNode0 270 | 271 | return this.bestNode 272 | } 273 | 274 | compute (): Path> { 275 | const computeStartTime = performance.now() 276 | 277 | if (!this.movementProvider.sanitize()) { 278 | throw new Error('Movement Provider was not properly configured!') 279 | } 280 | 281 | while (!this.openHeap.isEmpty()) { 282 | if ((++this.nodeConsiderCount & this.checkInterval) === 0) { 283 | const time = performance.now() 284 | if (time - computeStartTime > this.tickTimeout) { 285 | // compute time per tick 286 | return this.makeResult('partial', this.getActualBestNode()) 287 | } 288 | if (this.timeout >= 0 && time - this.startTime > this.timeout) { 289 | // total compute time 290 | return this.makeResult('timeout', this.getActualBestNode()) 291 | } 292 | } 293 | // const node = this.openHeap.poll()! 294 | const node = this.openHeap.pop() 295 | 296 | // again, cannot afford another if-statement. 297 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 298 | if (this.goal.isEnd(node.data!)) { 299 | return this.makeResult('success', node) 300 | } 301 | // not done yet 302 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 303 | this.openDataMap.delete(node.data!.hash) 304 | 305 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 306 | const neighbors = this.movementProvider.getNeighbors(node.data!, this.closedDataSet) 307 | for (const neighborData of neighbors) { 308 | this.moveConsiderCount++ 309 | if (this.closedDataSet.has(neighborData.hash)) { 310 | continue // skip closed neighbors 311 | } 312 | const gFromThisNode = node.g + neighborData.cost 313 | const pastNeighborNode = this.openDataMap.get(neighborData.hash) 314 | 315 | const heuristic = this.heuristic(neighborData) 316 | if (this.maxCost > 0 && gFromThisNode + heuristic > this.maxCost) continue 317 | 318 | if (pastNeighborNode === undefined) { 319 | // add neighbor to the open set 320 | const neighbor = new CPathNode(gFromThisNode, heuristic, neighborData, node) 321 | this.openDataMap.set(neighborData.hash, neighbor) 322 | this.openHeap.push(neighbor) 323 | this.assignBestNodes(neighbor) 324 | } else if (gFromThisNode - pastNeighborNode.g < this.differential) { 325 | pastNeighborNode.update(gFromThisNode, heuristic, neighborData, node) 326 | this.openHeap.update(pastNeighborNode) 327 | this.assignBestNodes(pastNeighborNode) 328 | } 329 | 330 | // allow specific implementations to access visited and closed data. 331 | this.addToClosedDataSet(node) 332 | 333 | // found a new or better route. 334 | // update this neighbor with this node as its new parent 335 | 336 | // console.log(neighborNode.data!.x, neighborNode.data!.y, neighborNode.data!.z, neighborNode.g, neighborNode.h) 337 | 338 | // if (update) { 339 | // // this.openHeap. 340 | // // // this.openHeap. 341 | // this.openHeap.update(neighborNode) 342 | // } else { 343 | // // this.openHeap.add(neighborNode) 344 | // this.openHeap.push(neighborNode) 345 | // } 346 | } 347 | } 348 | // all the neighbors of every accessible node have been exhausted 349 | return this.makeResult('noPath', this.getActualBestNode()) 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /src/abstract/algorithms/index.ts: -------------------------------------------------------------------------------- 1 | import { PathData, PathNode } from '../node' 2 | 3 | export function reconstructPath (node: PathNode): Data[] { 4 | const path: Data[] = [] 5 | while (node.parent != null) { 6 | if (node.data == null) throw new Error('Node data is null!') // should never occur. 7 | path.push(node.data) 8 | node = node.parent 9 | } 10 | return path.reverse() 11 | } 12 | -------------------------------------------------------------------------------- /src/abstract/heap.ts: -------------------------------------------------------------------------------- 1 | import { PathData, PathNode } from './node' 2 | 3 | export class BinaryHeapOpenSet> { 4 | // Initialing the array heap and adding a dummy element at index 0 5 | heap: N[] = [null] as any 6 | 7 | size (): number { 8 | return this.heap.length - 1 9 | } 10 | 11 | isEmpty (): boolean { 12 | return this.heap.length === 1 13 | } 14 | 15 | push (val: N): void { 16 | // Inserting the new node at the end of the heap array 17 | this.heap.push(val) 18 | 19 | // Finding the correct position for the new node 20 | let current = this.heap.length - 1 21 | let parent = current >>> 1 22 | 23 | // Traversing up the parent node until the current node is greater than the parent 24 | while (current > 1 && this.heap[parent].f > this.heap[current].f) { 25 | [this.heap[parent], this.heap[current]] = [this.heap[current], this.heap[parent]] 26 | current = parent 27 | parent = current >>> 1 28 | } 29 | } 30 | 31 | update (val: N): void { 32 | let current = this.heap.indexOf(val) 33 | let parent = current >>> 1 34 | 35 | // Traversing up the parent node until the current node is greater than the parent 36 | while (current > 1 && this.heap[parent].f > this.heap[current].f) { 37 | [this.heap[parent], this.heap[current]] = [this.heap[current], this.heap[parent]] 38 | current = parent 39 | parent = current >>> 1 40 | } 41 | } 42 | 43 | pop (): N { 44 | // Smallest element is at the index 1 in the heap array 45 | const smallest = this.heap[1] 46 | 47 | this.heap[1] = this.heap[this.heap.length - 1] 48 | this.heap.splice(this.heap.length - 1) 49 | 50 | const size = this.heap.length - 1 51 | 52 | if (size < 2) return smallest 53 | 54 | const val = this.heap[1] 55 | let index = 1 56 | let smallerChild = 2 57 | const cost = val.f 58 | do { 59 | let smallerChildNode = this.heap[smallerChild] 60 | if (smallerChild < size - 1) { 61 | const rightChildNode = this.heap[smallerChild + 1] 62 | if (smallerChildNode.f > rightChildNode.f) { 63 | smallerChild++ 64 | smallerChildNode = rightChildNode 65 | } 66 | } 67 | if (cost <= smallerChildNode.f) { 68 | break 69 | } 70 | this.heap[index] = smallerChildNode 71 | this.heap[smallerChild] = val 72 | index = smallerChild 73 | 74 | smallerChild *= 2 75 | } while (smallerChild <= size) 76 | 77 | return smallest 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/abstract/index.ts: -------------------------------------------------------------------------------- 1 | import { PathStatus } from '../types' 2 | import { PathData, PathNode } from './node' 3 | 4 | export interface Goal { 5 | isEnd: (node: Data) => boolean 6 | heuristic: (node: Data) => number 7 | } 8 | 9 | export interface Algorithm { 10 | movementProvider: MovementProvider 11 | compute: () => Path> | null 12 | makeResult: (status: PathStatus, node: PathNode) => Path> 13 | } 14 | 15 | export interface Path> { 16 | status: PathStatus 17 | cost: number 18 | calcTime: number 19 | visitedNodes: number 20 | generatedNodes: number 21 | movementProvider: MovementProvider 22 | path: Data[] 23 | context: Alg 24 | } 25 | 26 | export interface MovementProvider { 27 | sanitize: () => boolean 28 | getNeighbors: (org: Data, set: Set) => Data[] 29 | } 30 | 31 | export { PathNode } from './node' 32 | -------------------------------------------------------------------------------- /src/abstract/node.ts: -------------------------------------------------------------------------------- 1 | export interface PathData { 2 | hash: string 3 | cost: number 4 | } 5 | 6 | export class PathNode { 7 | data: Data | null = null 8 | parent: PathNode | null = null 9 | g = 0 10 | h = 0 11 | 12 | get f (): number { 13 | return this.g + this.h 14 | } 15 | 16 | update ( 17 | g: number, 18 | h: number, 19 | data: Data | null = null, 20 | parent: PathNode | null = null 21 | ): this { 22 | this.g = g 23 | this.h = h 24 | this.data = data 25 | this.parent = parent 26 | return this 27 | } 28 | } 29 | 30 | export class CPathNode implements PathNode { 31 | constructor (g: number, h: number, data: Data | null = null, parent: PathNode | null = null) { 32 | this.g = g 33 | this.h = h 34 | this.f = g + h 35 | this.data = data 36 | this.parent = parent 37 | } 38 | 39 | data: Data | null 40 | parent: PathNode | null 41 | g: number 42 | h: number 43 | f: number 44 | 45 | update (g: number, h: number, data: Data | null, parent: PathNode | null): this { 46 | this.g = g 47 | this.h = h 48 | this.f = g + h 49 | this.data = data 50 | this.parent = parent 51 | return this 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/customHashmap/Int64Map/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "print.colourScheme": "GitHub" 3 | } -------------------------------------------------------------------------------- /src/customHashmap/Int64Map/src/Int64Map.ts: -------------------------------------------------------------------------------- 1 | 2 | type primitive = boolean | number | string | bigint | symbol | object | null 3 | 4 | interface Node { 5 | intLow: number 6 | intHigh: number 7 | value: primitive 8 | next?: Node 9 | } 10 | 11 | interface Bucket { 12 | head?: Node 13 | } 14 | 15 | const DEFAULT_SIZE = 1024 16 | const LOAD_FACTOR = 0.5 17 | 18 | console.log('running with load factor', LOAD_FACTOR) 19 | 20 | class Int64Map { 21 | constructor (initialSize = DEFAULT_SIZE) { 22 | this.values = new Array(initialSize) 23 | for (let i = 0; i < initialSize; i++) { 24 | this.values[i] = { } 25 | } 26 | this.INTIAL_SIZE = initialSize 27 | this.size = initialSize 28 | } 29 | 30 | private readonly values: Bucket[] 31 | 32 | private readonly INTIAL_SIZE: number = DEFAULT_SIZE 33 | 34 | private size = 0 35 | 36 | get __size (): number { 37 | return this.size 38 | } 39 | 40 | private length = 0 41 | 42 | get __length (): number { 43 | return this.length 44 | } 45 | 46 | get (intLow: number, intHigh: number): primitive { 47 | const index = intLow & (this.size - 1) 48 | const bucket = this.values[index] 49 | let node = bucket.head 50 | while (node != null) { 51 | if (node.intHigh === intHigh) { 52 | return node.value 53 | } 54 | node = node.next 55 | } 56 | return null 57 | } 58 | 59 | set (intLow: number, intHigh: number, value: primitive): primitive { 60 | if (this.length > this.size * LOAD_FACTOR) { 61 | this.grow() 62 | } 63 | const index = intLow & (this.size - 1) 64 | const bucket = this.values[index] 65 | let node = bucket.head 66 | while (node != null) { 67 | if (node.intHigh === intHigh) { 68 | node.value = value 69 | return false 70 | } 71 | node = node.next 72 | } 73 | 74 | node = { 75 | intLow, 76 | intHigh, 77 | value, 78 | next: bucket.head 79 | } 80 | bucket.head = node 81 | // bucket.head = new Node(intLow, intHigh, value, bucket.head)//node 82 | this.length++ 83 | return true 84 | } 85 | 86 | delete (intLow: number, intHigh: number): boolean { 87 | if (this.size > this.INTIAL_SIZE && this.size >= this.length * 4) { 88 | this.shrink() 89 | } 90 | const index = intLow & (this.size - 1) 91 | const bucket = this.values[index] 92 | let node = bucket.head 93 | if (node != null) { 94 | if (node.intHigh === intHigh) { 95 | bucket.head = node.next 96 | this.length-- 97 | return true 98 | } 99 | let prev = node 100 | node = node.next 101 | while (node != null) { 102 | if (node.intHigh === intHigh) { 103 | prev.next = node.next 104 | this.length-- 105 | return true 106 | } 107 | prev = node 108 | node = node.next 109 | } 110 | } 111 | return false 112 | } 113 | 114 | private clear (): void { 115 | for (let i = 0; i < this.size; i++) { 116 | delete this.values[i].head 117 | } 118 | this.length = 0 119 | } 120 | 121 | private grow (): void { 122 | // const start = performance.now() 123 | const oldSize = this.size 124 | const newSize = oldSize * 2 125 | this.size = newSize 126 | this.values.length = newSize 127 | 128 | for (let i = oldSize; i < newSize; i++) { 129 | this.values[i] = { } 130 | } 131 | 132 | for (let i = 0; i < oldSize; i++) { 133 | const oldBucket = this.values[i] 134 | let node = oldBucket.head 135 | delete oldBucket.head 136 | // oldBucket.head = null 137 | while (node != null) { 138 | const { next } = node 139 | const newIndex = node.intLow & (newSize - 1) 140 | if (newIndex === i) { 141 | node.next = oldBucket.head 142 | oldBucket.head = node 143 | } else { 144 | const bucket = this.values[newIndex] 145 | node.next = bucket.head 146 | bucket.head = node 147 | } 148 | node = next 149 | } 150 | } 151 | 152 | // const time = performance.now() - start 153 | // console.log(`Took \x1B[32m${time.toFixed(0)}\x1B[0m ms to resize hashmap to \x1B[32m${newSize}\x1B[0m.`) 154 | } 155 | 156 | private shrink (): void { 157 | // console.log('Shrink not implemented yet. (Not tested!)') 158 | // TODO 159 | const oldSize = this.size 160 | const newSize = oldSize / 2 161 | 162 | for (let i = oldSize; i >= newSize; i--) { 163 | let node = this.values[i].head 164 | while (node != null) { 165 | const next = node.next 166 | const newIndex = node.intLow % newSize 167 | const bucket = this.values[newIndex] 168 | node.next = bucket.head 169 | bucket.head = node 170 | node = next 171 | } 172 | 173 | // eslint-disable-next-line @typescript-eslint/no-dynamic-delete 174 | delete this.values[i] 175 | } 176 | this.values.length = newSize 177 | this.size = newSize 178 | } 179 | } 180 | 181 | export { Int64Map } 182 | -------------------------------------------------------------------------------- /src/customHashmap/Int64Map/src/new/Int64Map.ts: -------------------------------------------------------------------------------- 1 | 2 | type primitive = boolean | number | string | bigint | symbol | object | null 3 | 4 | interface Node { 5 | intLow: number 6 | intHigh: number 7 | value: primitive 8 | next: Node | undefined 9 | } 10 | 11 | const DEFAULT_SIZE = 1024 12 | const LOAD_FACTOR = 0.5 13 | 14 | class Int64Map { 15 | constructor (initialSize = DEFAULT_SIZE) { 16 | this.values = new Array(initialSize) 17 | this.INTIAL_SIZE = initialSize 18 | this.size = initialSize 19 | } 20 | 21 | private readonly values: Array 22 | 23 | private readonly INTIAL_SIZE: number = DEFAULT_SIZE 24 | 25 | private size = 0 26 | 27 | get __size (): number { 28 | return this.size 29 | } 30 | 31 | private length = 0 32 | 33 | get __length (): number { 34 | return this.length 35 | } 36 | 37 | get (intLow: number, intHigh: number): primitive | undefined { 38 | const index = intLow & (this.size - 1) 39 | let node = this.values[index] 40 | while (node != null) { 41 | if (node.intHigh === intHigh) { 42 | return node.value 43 | } 44 | node = node.next 45 | } 46 | return undefined 47 | } 48 | 49 | set (intLow: number, intHigh: number, value: primitive): boolean { 50 | if (this.length > this.size * LOAD_FACTOR) { 51 | this.grow() 52 | } 53 | const index = intLow & (this.size - 1) 54 | // const bucket = this.values[index] 55 | let node = this.values[index] 56 | while (node != null) { 57 | if (node.intHigh === intHigh) { 58 | node.value = value 59 | return false 60 | } 61 | node = node.next 62 | } 63 | node = { 64 | intLow, 65 | intHigh, 66 | value, 67 | next: this.values[index] 68 | } 69 | this.values[index] = node 70 | this.length++ 71 | return true 72 | } 73 | 74 | delete (intLow: number, intHigh: number): boolean { 75 | if (this.size > this.INTIAL_SIZE && this.size >= this.length * 4) { 76 | this.shrink() 77 | } 78 | const index = intLow & (this.size - 1) 79 | // const bucket = this.values[index] 80 | let node = this.values[index] 81 | if (node != null) { 82 | if (node.intHigh === intHigh) { 83 | this.values[index] = node.next 84 | this.length-- 85 | return true 86 | } 87 | let prev = node 88 | node = node.next 89 | while (node != null) { 90 | if (node.intHigh === intHigh) { 91 | prev.next = node.next 92 | this.length-- 93 | return true 94 | } 95 | prev = node 96 | node = node.next 97 | } 98 | } 99 | return false 100 | } 101 | 102 | private grow (): void { 103 | // const start = performance.now() 104 | const oldSize = this.size 105 | const newSize = oldSize * 2 106 | this.size = newSize 107 | this.values.length = newSize 108 | for (let i = 0; i < oldSize; i++) { 109 | // const oldBucket = this.values[i] 110 | let node = this.values[i] 111 | this.values[i] = undefined 112 | while (node != null) { 113 | const { next } = node 114 | const newIndex = node.intLow & (newSize - 1) 115 | if (newIndex === i) { 116 | node.next = this.values[i] 117 | this.values[i] = node 118 | } else { 119 | // const bucket = this.values[newIndex] 120 | node.next = this.values[newIndex] 121 | this.values[newIndex] = node 122 | } 123 | node = next 124 | } 125 | } 126 | // const time = performance.now() - start 127 | // console.log(`Took \x1B[32m${time.toFixed(0)}\x1B[0m ms to resize hashmap to \x1B[32m${newSize}\x1B[0m.`) 128 | } 129 | 130 | private shrink (): void { 131 | // console.log('Shrink not implemented yet. (Not tested!)') 132 | // TODO 133 | const oldSize = this.size 134 | const newSize = oldSize / 2 135 | 136 | for (let i = oldSize; i >= newSize; i--) { 137 | let node = this.values[i] 138 | while (node != null) { 139 | const next = node.next 140 | const newIndex = node.intLow % newSize 141 | node.next = this.values[newIndex] 142 | this.values[newIndex] = node 143 | node = next 144 | } 145 | 146 | // eslint-disable-next-line @typescript-eslint/no-dynamic-delete 147 | delete this.values[i] 148 | } 149 | this.values.length = newSize 150 | this.size = newSize 151 | } 152 | } 153 | 154 | export { Int64Map } 155 | -------------------------------------------------------------------------------- /src/customHashmap/Int64Map/src/new/test.ts: -------------------------------------------------------------------------------- 1 | import { Int64Map } from './Int64Map' 2 | 3 | const hashMap = new Int64Map() 4 | 5 | function randInt32 (): number { 6 | return Math.floor(Math.random() * 0xffffffff) 7 | } 8 | 9 | const size = 1024 * 1024 * 8 10 | const ints = new Array<[number, number]>(size) 11 | for (let i = 0; i < ints.length; i++) { 12 | ints[i] = [randInt32(), randInt32()] 13 | } 14 | 15 | const ts1 = performance.now() 16 | for (let i = 0; i < size; i++) { 17 | const int = ints[i % ints.length] 18 | hashMap.set(int[0], int[1], i) 19 | } 20 | const ts2 = performance.now() 21 | console.log(ts2 - ts1) 22 | for (let i = 0; i < size; i++) { 23 | const int = ints[i % ints.length] 24 | const v1 = hashMap.get(int[0], int[1]) 25 | if (v1 !== i) { 26 | // console.log('Hashmap failed', v1, i) 27 | } 28 | } 29 | 30 | const collsions = 0 31 | console.log(performance.now() - ts2) 32 | console.log(`Total collisions: ${collsions}`) 33 | -------------------------------------------------------------------------------- /src/customHashmap/Int64Map/src/test.ts: -------------------------------------------------------------------------------- 1 | import { Int64Map } from './Int64Map' 2 | 3 | const hashMap = new Int64Map() 4 | 5 | function randInt32 (): number { 6 | return Math.floor(Math.random() * 0xffffffff) 7 | } 8 | 9 | const size = 1024 * 1024 * 16 10 | const ints = new Array(size) 11 | for (let i = 0; i < ints.length; i++) { 12 | ints[i] = [randInt32(), randInt32()] 13 | } 14 | 15 | const ts1 = performance.now() 16 | for (let i = 0; i < size; i++) { 17 | const int = ints[i % ints.length] 18 | hashMap.set(int[0], int[1], i) 19 | } 20 | const ts2 = performance.now() 21 | console.log(ts2 - ts1) 22 | for (let i = 0; i < size; i++) { 23 | const int = ints[i % ints.length] 24 | const v1 = hashMap.get(int[0], int[1]) 25 | if (v1 !== i) { 26 | // console.log('Hashmap failed', v1, i) 27 | } 28 | } 29 | 30 | const collsions = 0 31 | console.log(performance.now() - ts2) 32 | console.log(`Total collisions: ${collsions}`) 33 | console.log(hashMap.__size, hashMap.__length) 34 | -------------------------------------------------------------------------------- /src/customHashmap/Int64Map/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | // "resolveJsonModule": true, /* Enable importing .json files. */ 43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 45 | 46 | /* JavaScript Support */ 47 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | 51 | /* Emit */ 52 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 56 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 58 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 59 | // "removeComments": true, /* Disable emitting comments. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 62 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 68 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 74 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 75 | 76 | /* Interop Constraints */ 77 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 78 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 83 | 84 | /* Type Checking */ 85 | "strict": true, /* Enable all strict type-checking options. */ 86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 91 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 92 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 93 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 94 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 95 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 96 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 97 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 98 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 99 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 100 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 101 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 102 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 103 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 104 | 105 | /* Completeness */ 106 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 107 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 108 | }, 109 | "include": [ 110 | "src/**/*.ts" 111 | ], 112 | } 113 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Bot } from 'mineflayer' 2 | import { BlockInfo } from './mineflayer-specific/world/cacheWorld' 3 | import { PathfinderOptions, ThePathfinder } from './ThePathfinder' 4 | import { Vec3 } from 'vec3' 5 | 6 | import utilPlugin from '@nxg-org/mineflayer-util-plugin' 7 | import physicsUtil, { initSetup } from '@nxg-org/mineflayer-physics-util' 8 | 9 | import { Block, PlaceBlockOptions, ResetReason } from './types' 10 | import { PathingUtil } from './PathingUtil' 11 | 12 | import * as goals from './mineflayer-specific/goals' 13 | import { Path } from './mineflayer-specific/algs' 14 | import { MovementOptions, MovementSetup } from './mineflayer-specific/movements' 15 | import { OptimizationSetup } from './mineflayer-specific/post' 16 | 17 | export function createPlugin (opts?: { 18 | movements?: MovementSetup 19 | optimizers?: OptimizationSetup 20 | settings?: PathfinderOptions 21 | moveSettings?: MovementOptions 22 | }) { 23 | return function (bot: Bot) { 24 | BlockInfo.init(bot.registry) // set up block info 25 | if (!bot.hasPlugin(utilPlugin)) bot.loadPlugin(utilPlugin) 26 | if (!bot.hasPlugin(physicsUtil)) bot.loadPlugin(physicsUtil) 27 | initSetup(bot.registry) 28 | bot.pathfinder = new ThePathfinder(bot, opts) 29 | bot.pathingUtil = new PathingUtil(bot) 30 | } 31 | } 32 | 33 | declare module 'mineflayer' { 34 | interface Bot { 35 | pathfinder: ThePathfinder 36 | pathingUtil: PathingUtil 37 | 38 | _placeBlockWithOptions: (referenceBlock: Block, faceVector: Vec3, options?: PlaceBlockOptions) => Promise 39 | } 40 | 41 | interface BotEvents { 42 | pathGenerated: (path: Path) => void 43 | resetPath: (reason: ResetReason) => void 44 | enteredRecovery: (errorCount: number) => void 45 | exitedRecovery: (errorCount: number) => void 46 | goalSet: (goal: goals.Goal) => void 47 | goalFinished: (goal: goals.Goal) => void 48 | goalAborted: (goal: goals.Goal) => void 49 | } 50 | } 51 | 52 | export * as goals from './mineflayer-specific/goals' 53 | export * as custom from './mineflayer-specific/custom' 54 | -------------------------------------------------------------------------------- /src/mineflayer-specific/algs.ts: -------------------------------------------------------------------------------- 1 | import { Goal, MovementProvider, Path as APath } from '../abstract' 2 | import { AStarBackOff as AAStarBackOff } from '../abstract/algorithms/astar' 3 | import { CPathNode } from '../abstract/node' 4 | import { PathStatus } from '../types' 5 | import { Move } from './move' 6 | import { PathNode } from './node' 7 | 8 | export interface Path extends APath {} 9 | export interface OptPath extends Path { 10 | optPath: Move[] 11 | } 12 | 13 | export interface PathProducer { 14 | // constructor(start: Data, goal: goals.Goal, settings: Settings): PathProducer 15 | 16 | getCurrentPath: () => Move[] 17 | getAstarContext: () => AStar | undefined 18 | advance: () => { result: Path, astarContext: AStar } 19 | } 20 | 21 | export class AStar extends AAStarBackOff { 22 | visitedChunks: Set 23 | 24 | mostRecentNode: PathNode = this.bestNode // also known as start 25 | constructor ( 26 | start: Move, 27 | movements: MovementProvider, 28 | goal: Goal, 29 | timeout: number, 30 | tickTimeout = 40, 31 | searchRadius = -1, 32 | differential = 0 33 | ) { 34 | super(start, movements, goal, timeout, tickTimeout, searchRadius, differential) 35 | this.visitedChunks = new Set() 36 | } 37 | 38 | protected addToClosedDataSet (node: PathNode): void { 39 | // Checking with if statement is slow. 40 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 41 | this.closedDataSet.add(node.data!.hash) 42 | 43 | // Checking with if statement is slow. 44 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 45 | this.visitedChunks.add(`${node.data!.x >> 4},${node.data!.z >> 4}`) 46 | 47 | this.mostRecentNode = node 48 | } 49 | 50 | public override compute (): Path { 51 | // slightly slower, but yknow compliant with typescript bitching. 52 | return { 53 | ...super.compute(), 54 | context: this 55 | } 56 | } 57 | } 58 | 59 | export class AStarNeighbor extends AStar { 60 | makeResult (status: PathStatus, node: PathNode): Path { 61 | return { 62 | ...super.makeResult(status, node), 63 | context: this 64 | } 65 | } 66 | 67 | compute (): Path { 68 | const computeStartTime = performance.now() 69 | 70 | if (!this.movementProvider.sanitize()) { 71 | throw new Error('Movement Provider was not properly configured!') 72 | } 73 | 74 | while (!this.openHeap.isEmpty()) { 75 | if ((++this.nodeConsiderCount & this.checkInterval) === 0) { 76 | const time = performance.now() 77 | if (time - computeStartTime > this.tickTimeout) { 78 | // compute time per tick 79 | return this.makeResult('partial', this.getActualBestNode()) 80 | } 81 | if (this.timeout >= 0 && time - this.startTime > this.timeout) { 82 | // total compute time 83 | return this.makeResult('timeout', this.getActualBestNode()) 84 | } 85 | } 86 | // const node = this.openHeap.poll()! 87 | const node = this.openHeap.pop() 88 | 89 | // again, cannot afford another if-statement. 90 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 91 | if (this.goal.isEnd(node.data!)) { 92 | return this.makeResult('success', node) 93 | } 94 | // not done yet 95 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 96 | this.openDataMap.delete(node.data!.hash) 97 | 98 | this.test(node, 1) 99 | // this.test(this.bestNode, 1) 100 | 101 | // if (this.callAmt++ % 50 === 0) { 102 | // this.test(this.bestNode, 3) 103 | // } 104 | } 105 | // all the neighbors of every accessible node have been exhausted 106 | return this.makeResult('noPath', this.getActualBestNode()) 107 | } 108 | 109 | private test (node: PathNode, maxDepth: number, depth = 0, seen = new Set()): void { 110 | if (depth > maxDepth) return 111 | 112 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 113 | if (this.closedDataSet.has(node.data!.hash)) { 114 | return 115 | } 116 | 117 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 118 | if (seen.has(node.data!.hash)) { 119 | // console.log('seen!') 120 | return 121 | } 122 | 123 | const test = node 124 | let bestLocal = test 125 | // if (node.f < this.bestNode.f - 50) return; 126 | 127 | // allow specific implementations to access visited and closed data. 128 | this.addToClosedDataSet(node) 129 | 130 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 131 | const neighbors = this.movementProvider.getNeighbors(node.data!, this.closedDataSet) 132 | for (const neighborData of neighbors) { 133 | if (this.closedDataSet.has(neighborData.hash)) { 134 | continue // skip closed neighbors 135 | } 136 | 137 | // if (seen.has(neighborData.hash)) { 138 | // // console.log('seen!') 139 | // continue; 140 | // } 141 | 142 | // seen.add(neighborData.hash) 143 | 144 | // console.trace('called with:', node.data?.hash, neighborData.hash, seen.size, depth) 145 | 146 | const gFromThisNode = node.g + neighborData.cost 147 | const pastNeighbor = this.openDataMap.get(neighborData.hash) 148 | 149 | const heuristic = this.heuristic(neighborData) 150 | if (this.maxCost > 0 && gFromThisNode + heuristic > this.maxCost) continue 151 | 152 | if (pastNeighbor === undefined) { 153 | // add neighbor to the open set 154 | const neighbor = new CPathNode(gFromThisNode, heuristic, neighborData, node) 155 | this.assignBestNodes(neighbor) 156 | // if (neighbor.h < this.bestNode.h) this.bestNode = neighbor 157 | if (neighbor.h < test.h) { 158 | bestLocal = neighbor 159 | // bestLocal = neighbor; 160 | // oldBestCost = pastNeighborNode.f; 161 | // this.test(neighbor, maxDepth, depth + 1, seen) 162 | } 163 | 164 | this.openDataMap.set(neighborData.hash, neighbor) 165 | this.openHeap.push(neighbor) 166 | } else if (gFromThisNode - pastNeighbor.g < this.differential) { 167 | pastNeighbor.update(gFromThisNode, heuristic, neighborData, node) 168 | this.assignBestNodes(pastNeighbor) 169 | this.openHeap.update(pastNeighbor) 170 | // if (pastNeighbor.h < this.bestNode.h) this.bestNode = pastNeighbor 171 | if (pastNeighbor.h < test.h) { 172 | bestLocal = pastNeighbor 173 | // this.test(pastNeighbor, maxDepth, depth + 1, seen) 174 | // bestLocal = pastNeighborNode; 175 | } 176 | } 177 | } 178 | if (bestLocal !== test) { 179 | this.test(bestLocal, maxDepth, depth + 1, seen) 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/mineflayer-specific/custom.ts: -------------------------------------------------------------------------------- 1 | export { MovementExecutor, BuildableMoveExecutor, BuildableMoveProvider, MovementProvider } from './movements' 2 | export { MovementOptimizer } from './post' 3 | -------------------------------------------------------------------------------- /src/mineflayer-specific/exceptions.ts: -------------------------------------------------------------------------------- 1 | import { ResetReason } from '../types' 2 | 3 | export class CancelError extends Error { 4 | constructor (...args: any[]) { 5 | // console.log('CancelError', args) 6 | super('Movement canceled: ' + args.join(' ')) 7 | } 8 | } 9 | 10 | export class AbortError extends Error { 11 | constructor (...args: any[]) { 12 | // console.log('AbortError', args) 13 | super('Movement aborted: ' + args.join(' ')) 14 | } 15 | } 16 | 17 | export class ResetError extends Error { 18 | constructor (public readonly reason: ResetReason, ...args: any[]) { 19 | // console.log('ResetError', reason, args) 20 | super('Movement timed out: ' + args.join(' ')) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/mineflayer-specific/index.ts: -------------------------------------------------------------------------------- 1 | export { MovementHandler } from './movements' 2 | -------------------------------------------------------------------------------- /src/mineflayer-specific/move.ts: -------------------------------------------------------------------------------- 1 | import { Vec3 } from 'vec3' 2 | import { MovementProvider } from './movements' 3 | import { PathData } from '../abstract/node' 4 | import { EntityState } from '@nxg-org/mineflayer-physics-util' 5 | import { BreakHandler, PlaceHandler } from './movements/interactionUtils' 6 | const emptyVec = new Vec3(0, 0, 0) 7 | 8 | // const TOPLACE: PlaceHandler[] = [] 9 | // const TOBREAK: BreakHandler[] = [] 10 | 11 | export class Move implements PathData { 12 | hash: string 13 | 14 | targetPos: Vec3 15 | 16 | public readonly cachedVec: Vec3 17 | // remainingBlocks: number = 0 // TODO: implement this 18 | 19 | toPlace: PlaceHandler[] 20 | toBreak: BreakHandler[] 21 | 22 | constructor ( 23 | public readonly x: number, 24 | public readonly y: number, 25 | public readonly z: number, 26 | toPlace: PlaceHandler[], 27 | toBreak: BreakHandler[], 28 | public readonly remainingBlocks: number, 29 | public readonly cost: number, 30 | public readonly moveType: MovementProvider, 31 | public readonly entryPos: Vec3, 32 | public readonly entryVel: Vec3, 33 | public readonly exitPos: Vec3, 34 | public readonly exitVel: Vec3, 35 | // public readonly interactMap: Map, 36 | public readonly parent?: Move 37 | ) { 38 | this.x = Math.floor(x) 39 | this.y = Math.floor(y) 40 | this.z = Math.floor(z) 41 | this.hash = `${this.x},${this.y},${this.z}` // this.x + ',' + this.y + ',' + this.z 42 | this.targetPos = this.exitPos 43 | 44 | this.cachedVec = new Vec3(this.x, this.y, this.z) 45 | Object.freeze(this.cachedVec) 46 | 47 | // this.toPlace = TOPLACE 48 | // this.toBreak = TOBREAK 49 | this.toPlace = toPlace 50 | this.toBreak = toBreak 51 | // this.x = x; 52 | // this.y = y; 53 | // this.z = z; 54 | // this.hash = this.x.toFixed(1) + "," + this.y.toFixed(1) + "," + this.z.toFixed(1); 55 | } 56 | 57 | static startMove (type: MovementProvider, pos: Vec3, vel: Vec3, remainingBlocks: number): Move { 58 | return new Move(pos.x, pos.y, pos.z, [], [], remainingBlocks, 0, type, pos, vel, pos, vel) 59 | // new Map()); 60 | } 61 | 62 | static fromPreviousState ( 63 | cost: number, 64 | state: EntityState, 65 | prevMove: Move, 66 | type: MovementProvider, 67 | toPlace: PlaceHandler[] = [], 68 | toBreak: BreakHandler[] = [] 69 | ): Move { 70 | // const p = new Map(prevMove.interactMap); 71 | // for (const breakH of toBreak) { 72 | // p.set(`(${breakH.x}, ${breakH.y}, ${breakH.z})`, breakH); 73 | // } 74 | // for (const place of toPlace) { 75 | // p.set(`(${place.x}, ${place.y}, ${place.z})`, place); 76 | // } 77 | return new Move( 78 | state.pos.x, 79 | state.pos.y, 80 | state.pos.z, 81 | toPlace, 82 | toBreak, 83 | prevMove.remainingBlocks - 0, //toPlace.length, 84 | cost, 85 | type, 86 | prevMove.exitPos, 87 | prevMove.exitVel, 88 | state.pos.clone(), 89 | state.vel.clone(), 90 | // prevMove.interactMap, 91 | prevMove 92 | // p 93 | ) 94 | } 95 | 96 | static fromPrevious ( 97 | cost: number, 98 | pos: Vec3, 99 | prevMove: Move, 100 | type: MovementProvider, 101 | toPlace: PlaceHandler[] = [], 102 | toBreak: BreakHandler[] = [] 103 | ): Move { 104 | // const p = new Map(prevMove.interactMap); 105 | // for (const place of toPlace) { 106 | // p.set(`(${place.x}, ${place.y}, ${place.z})`, place); 107 | // } 108 | // for (const breakH of toBreak) { 109 | // p.set(`(${breakH.x}, ${breakH.y}, ${breakH.z})`, breakH); 110 | // } 111 | return new Move( 112 | pos.x, 113 | pos.y, 114 | pos.z, 115 | toPlace, 116 | toBreak, 117 | prevMove.remainingBlocks - 0,//toPlace.length, 118 | cost, 119 | type, 120 | prevMove.exitPos, 121 | prevMove.exitVel, 122 | pos, 123 | emptyVec, 124 | // prevMove.interactMap, 125 | prevMove 126 | // p 127 | ) 128 | } 129 | 130 | public clone (): Move { 131 | return { ...this } // lazy. 132 | } 133 | 134 | public get vec (): Vec3 { 135 | return this.cachedVec 136 | // return new Vec3(this.x, this.y, this.z) 137 | } 138 | 139 | public toVecCenter (): Vec3 { 140 | return new Vec3(this.x + 0.5, this.y, this.z + 0.5) 141 | } 142 | 143 | public exitRounded (digits: number): Vec3 { 144 | const mult = Math.pow(10, digits) 145 | return new Vec3( 146 | Math.round(this.exitPos.x * mult) / mult, 147 | Math.round(this.exitPos.y * mult) / mult, 148 | Math.round(this.exitPos.z * mult) / mult 149 | ) 150 | } 151 | 152 | toString (): String { 153 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions 154 | return `Move { ${this.moveType.constructor.name} | ${this.x}, ${this.y}, ${this.z} | ${this.entryPos} | ${this.exitPos} }` 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/mineflayer-specific/movements/baritone/baritoneProviders.ts: -------------------------------------------------------------------------------- 1 | import { Vec3 } from 'vec3' 2 | import { Move } from '../../move' 3 | import { MovementProvider } from '../movementProvider' 4 | import { Goal } from '../../goals' 5 | import { canUseFrostWalker, canWalkOn, canWalkThrough, findPlaceOpts, getMiningDurationTicks, isBottomSlab } from './movementHelper' 6 | import { BreakHandler, PlaceHandler } from '../interactionUtils' 7 | import { CENTER_AFTER_FALL_COST, COST_INF, FALL_N_BLOCKS_COST, JUMP_ONE_BLOCK_COST, WALK_OFF_BLOCK_COST, WALK_ONE_BLOCK_COST, WALK_ONE_OVER_SOUL_SAND_COST } from '../costs' 8 | import { BlockInfo } from '../../world/cacheWorld' 9 | 10 | export class IdleMovement extends MovementProvider { 11 | movementDirs: Vec3[] = [] 12 | provideMovements (start: Move, storage: Move[]): void {} 13 | async performInit (thisMove: Move, currentIndex: number, path: Move[]): Promise {} 14 | async performPerTick (thisMove: Move, tickCount: number, currentIndex: number, path: Move[]): Promise { 15 | return true 16 | } 17 | } 18 | 19 | // base all code in this file off of: 20 | // https://github.com/cabaletta/baritone/blob/1.19.4/src/main/java/baritone/pathing/movement/movements/MovementAscend.java 21 | 22 | export class MovementAscend extends MovementProvider { 23 | movementDirs = [new Vec3(0, 1, 0)] 24 | provideMovements (start: Move, storage: Move[], goal: Goal, closed: Set): void { 25 | for (const dir of this.movementDirs) { 26 | const off = start.cachedVec.plus(dir) 27 | if (closed.has(off.toString())) return 28 | this.provideAscend(start, dir, storage, closed) 29 | } 30 | } 31 | 32 | provideAscend (node: Move, dir: Vec3, storage: Move[], closed: Set): void { 33 | const blPlace = this.getBlockInfo(node, dir.x, 0, dir.y) 34 | 35 | if (blPlace.isInvalid) return 36 | 37 | // potentially get rid of these, as we don't actually need them for the time being. 38 | const toPlace: PlaceHandler[] = [] 39 | const toBreak: BreakHandler[] = [] 40 | 41 | let cost = 0 42 | if (!blPlace.physical) { 43 | if (!blPlace.replaceable) { 44 | if ((cost += this.safeOrBreak(blPlace, toBreak)) >= COST_INF) return 45 | } 46 | if ((cost += this.safeOrPlace(blPlace, toPlace)) >= COST_INF) return 47 | if (findPlaceOpts(this, node, blPlace.position) == null) return 48 | } 49 | 50 | const srcUp1 = this.getBlockInfo(node, 0, 1, 0) 51 | const srcUp2 = this.getBlockInfo(node, 0, 2, 0) 52 | const srcUp3 = this.getBlockInfo(node, 0, 3, 0) 53 | // translate below to typescript 54 | // if (context.get(x, y + 3, z).getBlock() instanceof FallingBlock && (MovementHelper.canWalkThrough(context, x, y + 1, z) || !(srcUp2.getBlock() instanceof FallingBlock))) {//it would fall on us and possibly suffocate us 55 | 56 | if (srcUp3.canFall && (canWalkThrough(srcUp1) || !srcUp2.canFall)) { 57 | return 58 | } 59 | 60 | const srcDown1 = this.getBlockInfo(node, 0, -1, 0) 61 | if (srcDown1.climbable) return 62 | 63 | const jumpFromBottomSlab = isBottomSlab(srcDown1) 64 | const jumpToBottomSlab = isBottomSlab(blPlace) 65 | 66 | if (jumpFromBottomSlab && !jumpToBottomSlab) { 67 | return 68 | } 69 | 70 | let walk = 0 71 | if (jumpToBottomSlab) { 72 | if (jumpFromBottomSlab) { 73 | walk = Math.max(JUMP_ONE_BLOCK_COST, WALK_ONE_BLOCK_COST) // we hit space immediately on entering this action 74 | walk += this.settings.jumpCost 75 | } else { 76 | walk = WALK_ONE_BLOCK_COST 77 | } 78 | } else { 79 | // for speed 80 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 81 | if (blPlace.block!.type === BlockInfo.soulsandId) { 82 | walk = WALK_ONE_OVER_SOUL_SAND_COST 83 | } else { 84 | walk = Math.max(JUMP_ONE_BLOCK_COST, WALK_ONE_BLOCK_COST) 85 | } 86 | walk += this.settings.jumpCost 87 | } 88 | 89 | if ((cost += walk) >= COST_INF) return 90 | if ((cost += getMiningDurationTicks(this, srcUp2)) >= COST_INF) return 91 | 92 | const target1 = this.getBlockInfo(node, dir.x, 1, dir.z) 93 | if ((cost += getMiningDurationTicks(this, target1)) >= COST_INF) return 94 | 95 | const target2 = this.getBlockInfo(node, dir.x, 2, dir.z) 96 | if ((cost += getMiningDurationTicks(this, target2)) >= COST_INF) return 97 | 98 | cost += 1 99 | } 100 | } 101 | 102 | export class MovementDescend extends MovementProvider { 103 | movementDirs = [new Vec3(0, -1, 0)] 104 | provideMovements (start: Move, storage: Move[], goal: Goal, closed: Set): void { 105 | for (const dir of this.movementDirs) { 106 | this.provideDescend(start, dir, storage, closed) 107 | } 108 | } 109 | 110 | provideDescend (node: Move, dir: Vec3, storage: Move[], closed: Set): void { 111 | const srcN1 = this.getBlockInfo(node, dir.x, -1, dir.z) 112 | if (srcN1.climbable) return 113 | 114 | const srcN2 = this.getBlockInfo(node, dir.x, -2, dir.z) 115 | if (!canWalkOn(srcN2)) { 116 | return // for now, do not calculate this movement as it will be handled by movementFall. 117 | // this.dynamicFallCosts(srcN2, cost); 118 | } 119 | 120 | if (canUseFrostWalker(this, srcN2)) { 121 | return 122 | } 123 | 124 | let cost = 0 125 | 126 | const destN1 = this.getBlockInfo(node, dir.x, -1, dir.z) 127 | if ((cost += getMiningDurationTicks(this, destN1)) >= COST_INF) return 128 | 129 | const dest = this.getBlockInfo(node, dir.x, 0, dir.z) 130 | if ((cost += getMiningDurationTicks(this, dest)) >= COST_INF) return 131 | 132 | const dest1 = this.getBlockInfo(node, dir.x, 1, dir.z) 133 | if ((cost += getMiningDurationTicks(this, dest1, true)) >= COST_INF) return 134 | 135 | let walk = WALK_OFF_BLOCK_COST 136 | 137 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 138 | if (srcN1.block!.type === BlockInfo.soulsandId) { 139 | walk = WALK_ONE_OVER_SOUL_SAND_COST / WALK_ONE_BLOCK_COST 140 | } 141 | 142 | cost += walk 143 | cost += Math.max(FALL_N_BLOCKS_COST[1], CENTER_AFTER_FALL_COST) 144 | } 145 | 146 | // TODO: implement mutables 147 | dynamicFallCosts (info: BlockInfo, cost: number): void { 148 | 149 | } 150 | } 151 | 152 | export class MovementDiagonal extends MovementProvider { 153 | movementDirs = MovementProvider.diagonalDirs 154 | provideMovements (start: Move, storage: Move[], goal: Goal, closed: Set): void { 155 | 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/mineflayer-specific/movements/baritone/movementHelper.ts: -------------------------------------------------------------------------------- 1 | import { Vec3 } from 'vec3' 2 | import { BlockInfo } from '../../world/cacheWorld' 3 | import { Movement } from '../movement' 4 | import { Vec3Properties } from '../../../types' 5 | 6 | export function canWalkOn (info: BlockInfo): boolean { 7 | if (info.block == null) return false 8 | return info.block.boundingBox === 'block' || info.safe 9 | } 10 | 11 | export function canWalkThrough (info: BlockInfo): boolean { 12 | if (info.block == null) return false 13 | return info.block.boundingBox === 'empty' || info.safe 14 | } 15 | 16 | const ALL_DIRS_BUT_UP = [ 17 | new Vec3(1, 0, 0), 18 | new Vec3(-1, 0, 0), 19 | new Vec3(0, 0, 1), 20 | new Vec3(0, 0, -1), 21 | new Vec3(0, -1, 0) 22 | ] 23 | export function findPlaceOpts (move: Movement, orgPos: Vec3Properties, pos: Vec3Properties): BlockInfo | null { 24 | for (const dir of ALL_DIRS_BUT_UP) { 25 | const nX = pos.x + dir.x 26 | const nZ = pos.z + dir.z 27 | if (nX === orgPos.x && nZ === orgPos.z) continue 28 | const info = move.getBlockInfo(pos, dir.x, dir.y, dir.z) 29 | if (canPlaceAgainst(info)) return info 30 | } 31 | 32 | return null 33 | } 34 | 35 | export function canPlaceAgainst (info: BlockInfo): boolean { 36 | return info.physical 37 | } 38 | 39 | export function isBottomSlab (info: BlockInfo): boolean { 40 | return info.dY === 0.5 41 | } 42 | 43 | export function getMiningDurationTicks (move: Movement, info: BlockInfo, includeFalling = false): number { 44 | if (!includeFalling) return move.breakCost(info) 45 | 46 | const above = move.getBlockInfo(info.position, 0, 1, 0) 47 | return move.breakCost(info) + getMiningDurationTicks(move, above, true) // recurse upwards. potentially slow. 48 | } 49 | 50 | export function getMiningDurationTicksCoords (move: Movement, pos: Vec3, includeFalling = false): number { 51 | return getMiningDurationTicks(move, move.getBlockInfoRaw(pos), includeFalling) 52 | } 53 | 54 | export function canUseFrostWalker (move: Movement, info: BlockInfo): boolean { 55 | return info.liquid && false // TODO: frostwalker. 56 | } 57 | -------------------------------------------------------------------------------- /src/mineflayer-specific/movements/controls.ts: -------------------------------------------------------------------------------- 1 | import { EntityState } from '@nxg-org/mineflayer-physics-util' 2 | import { Bot } from 'mineflayer' 3 | import { Vec3 } from 'vec3' 4 | 5 | // const ZERO = (0 * Math.PI) / 12 6 | // const PI_OVER_TWELVE = (1 * Math.PI) / 12 7 | const TWO_PI_OVER_TWELVE = (2 * Math.PI) / 12 8 | // const THREE_PI_OVER_TWELVE = (3 * Math.PI) / 12 9 | const FOUR_PI_OVER_TWELVE = (4 * Math.PI) / 12 10 | // const FIVE_PI_OVER_TWELVE = (5 * Math.PI) / 12 11 | // const SIX_PI_OVER_TWELVE = (6 * Math.PI) / 12 12 | // const SEVEN_PI_OVER_TWELVE = (7 * Math.PI) / 12 13 | const EIGHT_PI_OVER_TWELVE = (8 * Math.PI) / 12 14 | // const NINE_PI_OVER_TWELVE = (9 * Math.PI) / 12 15 | const TEN_PI_OVER_TWELVE = (10 * Math.PI) / 12 16 | // const ELEVEN_PI_OVER_TWELVE = (11 * Math.PI) / 12 17 | // const TWELVE_PI_OVER_TWELVE = (12 * Math.PI) / 12 18 | // const THIRTEEN_PI_OVER_TWELVE = (13 * Math.PI) / 12 19 | const FOURTEEN_PI_OVER_TWELVE = (14 * Math.PI) / 12 20 | // const FIFTEEN_PI_OVER_TWELVE = (15 * Math.PI) / 12 21 | const SIXTEEN_PI_OVER_TWELVE = (16 * Math.PI) / 12 22 | // const SEVENTEEN_PI_OVER_TWELVE = (17 * Math.PI) / 12 23 | // const EIGHTEEN_PI_OVER_TWELVE = (18 * Math.PI) / 12 24 | // const NINETEEN_PI_OVER_TWELVE = (19 * Math.PI) / 12 25 | const TWENTY_PI_OVER_TWELVE = (20 * Math.PI) / 12 26 | // const TWENTY_ONE_PI_OVER_TWELVE = (21 * Math.PI) / 12 27 | const TWENTY_TWO_PI_OVER_TWELVE = (22 * Math.PI) / 12 28 | // const TWENTY_THREE_PI_OVER_TWELVE = (23 * Math.PI) / 12 29 | // const TWENTY_FOUR_PI_OVER_TWELVE = (24 * Math.PI) / 12 30 | const TWO_PI = 2 * Math.PI 31 | 32 | // TODO: move to utils 33 | export function wrapDegrees (degrees: number): number { 34 | const tmp = degrees % 360 35 | return tmp < 0 ? tmp + 360 : tmp 36 | } 37 | 38 | export function wrapRadians (radians: number): number { 39 | const tmp = radians % TWO_PI 40 | // console.log('radians', radians, 'tmp', tmp, tmp < 0 ? tmp + Math.PI : tmp - Math.PI); 41 | return tmp < 0 ? tmp + TWO_PI : tmp 42 | // return tmp < 0 ? tmp + Math.PI : tmp > 0 ? tmp - Math.PI : tmp; 43 | } 44 | 45 | // currentPoint: Vec3 46 | function findDiff (position: Vec3, velocity: Vec3, yaw: number, pitch: number, nextPoint: Vec3, onGround: boolean): number { 47 | const xzVel = velocity.offset(0, -velocity.y, 0) 48 | // const dir1 = getViewDir({ yaw, pitch }) 49 | 50 | const amt = xzVel.norm() 51 | 52 | // if we're traveling fast enough, account ahead of time for the velocity. 53 | // 0.15 is about full speed for sprinting. Anything above that and we're jumping. 54 | // another method of doing this is vel.y > 0 ? 2 : 1 55 | // const offset = bot.entity.position.plus(bot.entity.velocity.scaled(amt > 0.15 ? 2 : 1)); 56 | let scale = onGround ? 0 : 1 57 | if (amt > 0.17) scale = 2 58 | if (position.distanceTo(nextPoint) < 0.3) scale = 0 59 | if (amt < 0.02) scale = 0 60 | const offset = position.plus(velocity.scaled(scale)) 61 | const lookDiff = wrapRadians(wrapRadians(yaw)) 62 | if (xzVel.norm() < 0.03) { 63 | // console.log("no vel, so different calc.", currentPoint, nextPoint, position); 64 | // return 0; 65 | 66 | const dir = nextPoint.minus(offset) 67 | const dx = dir.x 68 | const dz = dir.z 69 | 70 | // const dir1 = nextPoint.minus(bot.entity.position) 71 | // const dx1 = dir1.x 72 | // const dz1 = dir1.z 73 | 74 | const wantedYaw = wrapRadians(Math.atan2(-dx, -dz)) 75 | // const moveYaw = wrapRadians(Math.atan2(-dx1, -dz1)) 76 | 77 | const diff = wrapRadians(wantedYaw - lookDiff) 78 | // console.log('diff', diff) 79 | // // diff = wrapRadians(diff - lookDiff) 80 | 81 | // console.log('wantedYaw', wantedYaw) 82 | // console.log('moveYaw', moveYaw) 83 | // console.log('look diff', lookDiff) 84 | 85 | // console.log('entity yaw', bot.entity.yaw, lookDiff) 86 | // console.log('return', diff) 87 | // console.log("ratio", diff / Math.PI * 12, '\n\n') 88 | return diff 89 | } 90 | 91 | // const dx = nextPoint.x - currentPoint.x; 92 | // const dz = nextPoint.z - currentPoint.z; 93 | 94 | const dir = nextPoint.minus(offset) 95 | const dx = dir.x 96 | const dz = dir.z 97 | 98 | // const dir1 = bot.entity.velocity; 99 | // const dx1 = dir1.x 100 | // const dz1 = dir1.z 101 | 102 | const wantedYaw = wrapRadians(Math.atan2(-dx, -dz)) 103 | 104 | // console.log(nextPoint, currentPoint, dx, dz, dx1, dz1) 105 | 106 | // const moveYaw = wrapRadians(Math.atan2(-dx1, -dz1)); 107 | // const moveYaw = wrapRadians(Math.atan2(-dx1, -dz1)) 108 | 109 | const diff = wrapRadians(wantedYaw - lookDiff) 110 | // console.log('diff', diff) 111 | // // diff = wrapRadians(diff - lookDiff) 112 | 113 | // console.log('diff', diff) 114 | // diff = wrapRadians(diff + lookDiff) 115 | 116 | // console.log('wantedYaw', wantedYaw) 117 | // console.log('moveYaw', moveYaw) 118 | // console.log('look diff', lookDiff) 119 | // console.log('entity yaw', bot.entity.yaw, lookDiff) 120 | // console.log('return', diff) 121 | // console.log("ratio", diff / Math.PI * 12, '\n\n') 122 | return diff 123 | } 124 | 125 | /** 126 | * control strafing left-to-right dependent on offset to current goal. 127 | * @param nextPoint 128 | * @returns 129 | */ 130 | // currentPoint: Vec3 131 | export function strafeMovement (ctx: EntityState, nextPoint: Vec3): void { 132 | // const diff = findDiff(ctx.pos, ctx.vel, ctx.yaw, ctx.pitch, nextPoint, ctx.onGround) 133 | 134 | // ctx.pos.distanceTo(nextPoint) < 0.3 135 | // if (true) { 136 | // console.log('stopping since near goal') 137 | ctx.control.set('left', false) 138 | ctx.control.set('right', false) 139 | 140 | // } 141 | 142 | // const lookDiff = wrapRadians(wrapRadians(ctx.yaw)) 143 | 144 | // if (PI_OVER_TWELVE < diff && diff < ELEVEN_PI_OVER_TWELVE) { 145 | // // console.log('going left') 146 | // ctx.control.set('left', false) // are these reversed? tf 147 | // ctx.control.set('right', true) 148 | // } else if (THIRTEEN_PI_OVER_TWELVE < diff && diff < TWENTY_THREE_PI_OVER_TWELVE) { 149 | // // console.log('going right') 150 | // ctx.control.set('left', true) 151 | // ctx.control.set('right', false) 152 | // } else { 153 | // // console.log('going neither strafe') 154 | // ctx.control.set('left', false) 155 | // ctx.control.set('right', false) 156 | // } 157 | } 158 | 159 | /** 160 | * control strafing left-to-right dependent on offset to current goal. 161 | * @param nextPoint 162 | * @returns 163 | */ 164 | // currentPoint, 165 | export function botStrafeMovement (bot: Bot, nextPoint: Vec3): void { 166 | const diff = findDiff(bot.entity.position, bot.entity.velocity, bot.entity.yaw, bot.entity.pitch, nextPoint, bot.entity.onGround) 167 | 168 | if (bot.entity.position.distanceTo(nextPoint) < 0.1) { 169 | // console.log('stopping since near goal') 170 | bot.setControlState('left', false) 171 | bot.setControlState('right', false) 172 | } 173 | 174 | if (FOURTEEN_PI_OVER_TWELVE < diff && diff < TWENTY_TWO_PI_OVER_TWELVE) { 175 | // console.log('going left') 176 | bot.setControlState('left', false) // are these reversed? tf 177 | bot.setControlState('right', true) 178 | } else if (TWO_PI_OVER_TWELVE < diff && diff < TEN_PI_OVER_TWELVE) { 179 | // console.log('going right') 180 | bot.setControlState('left', true) 181 | bot.setControlState('right', false) 182 | } else { 183 | // console.log('going neither strafe') 184 | bot.setControlState('left', false) 185 | bot.setControlState('right', false) 186 | } 187 | } 188 | 189 | /** 190 | * Dependent on offset to current goal, control forward/backward movement. 191 | * Used in tandem with strafe aim. 192 | * @param goal 193 | * @param sprint 194 | * @returns 195 | */ 196 | // currentPoint, 197 | export function smartMovement (ctx: EntityState, nextPoint: Vec3, sprint = true): void { 198 | // console.log('hey!') 199 | const diff = findDiff(ctx.pos, ctx.vel, ctx.yaw, ctx.pitch, nextPoint, ctx.onGround) 200 | 201 | if (ctx.pos.distanceTo(nextPoint) < 0.1) { 202 | // console.log('stopping since near goal') 203 | ctx.control.set('forward', false) 204 | ctx.control.set('back', false) 205 | return 206 | } 207 | 208 | // const lookDiff = wrapRadians(wrapRadians(ctx.yaw)) 209 | 210 | // diff = wrapRadians(diff + lookDiff) 211 | 212 | // console.log('forward/back diff', diff, diff / Math.PI * 12) 213 | 214 | if (EIGHT_PI_OVER_TWELVE < diff && diff < SIXTEEN_PI_OVER_TWELVE) { 215 | // console.log('going back') 216 | ctx.control.set('forward', false) 217 | ctx.control.set('sprint', false) 218 | ctx.control.set('back', true) 219 | 220 | // console.log("back"); 221 | } else if (TWENTY_PI_OVER_TWELVE < diff || diff < FOUR_PI_OVER_TWELVE) { 222 | // console.log('going forward') 223 | ctx.control.set('forward', true) 224 | ctx.control.set('sprint', sprint) 225 | ctx.control.set('back', false) 226 | } else { 227 | // console.log('going neither') 228 | ctx.control.set('forward', false) 229 | ctx.control.set('sprint', false) 230 | ctx.control.set('back', false) 231 | } 232 | } 233 | 234 | /** 235 | * 236 | * @param bot 237 | * @param goal 238 | * @param sprint 239 | * @returns 240 | */ 241 | // currentPoint, 242 | export function botSmartMovement (bot: Bot, nextPoint: Vec3, sprint: boolean): void { 243 | const diff = findDiff(bot.entity.position, bot.entity.velocity, bot.entity.yaw, bot.entity.pitch, nextPoint, bot.entity.onGround) 244 | 245 | if (bot.entity.position.distanceTo(nextPoint) < 0.1) { 246 | // console.log('stopping since near goal') 247 | bot.setControlState('forward', false) 248 | bot.setControlState('back', false) 249 | return 250 | } 251 | 252 | if (EIGHT_PI_OVER_TWELVE < diff && diff < SIXTEEN_PI_OVER_TWELVE) { 253 | // console.log('going back') 254 | bot.setControlState('forward', false) 255 | bot.setControlState('sprint', false) 256 | bot.setControlState('back', true) 257 | 258 | // console.log("back"); 259 | } else if (TWENTY_PI_OVER_TWELVE < diff || diff < FOUR_PI_OVER_TWELVE) { 260 | // console.log('going forward') 261 | bot.setControlState('forward', true) 262 | bot.setControlState('sprint', sprint) 263 | bot.setControlState('back', false) 264 | } else { 265 | // console.log('going neither') 266 | bot.setControlState('forward', false) 267 | bot.setControlState('sprint', false) 268 | bot.setControlState('back', false) 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/mineflayer-specific/movements/costs.ts: -------------------------------------------------------------------------------- 1 | // blatantly stolen from Baritone. 2 | 3 | // export const WALK_ONE_BLOCK_COST = 20 / 4.317; 4 | // export const WALK_ONE_IN_WATER_COST = 20 / 2.2; 5 | // export const WALK_ONE_OVER_SOUL_SAND_COST = WALK_ONE_BLOCK_COST * 2; 6 | // export const LADDER_UP_ONE_COST = 20 / 2.35; 7 | // export const LADDER_DOWN_ONE_COST = 20 / 3.0; 8 | // export const SNEAK_ONE_BLOCK_COST = 20 / 1.3; 9 | // export const SPRINT_ONE_BLOCK_COST = 20 / 5.612; 10 | // export const SPRINT_MULTIPLIER = SPRINT_ONE_BLOCK_COST / WALK_ONE_BLOCK_COST; 11 | // export const WALK_OFF_BLOCK_COST = WALK_ONE_BLOCK_COST * 0.8; 12 | // export const CENTER_AFTER_FALL_COST = WALK_ONE_BLOCK_COST - WALK_OFF_BLOCK_COST; 13 | // export const COST_INF = 1000000; 14 | 15 | export const WALK_ONE_BLOCK_COST = 4.633 as const 16 | export const WALK_ONE_IN_WATER_COST = 9.091 as const 17 | export const WALK_ONE_OVER_SOUL_SAND_COST = 9.266 as const 18 | export const LADDER_UP_ONE_COST = 8.511 as const 19 | export const LADDER_DOWN_ONE_COST = 6.667 as const 20 | export const SNEAK_ONE_BLOCK_COST = 15.385 as const 21 | export const SPRINT_ONE_BLOCK_COST = 3.564 as const 22 | export const SPRINT_MULTIPLIER = 0.769 as const 23 | export const WALK_OFF_BLOCK_COST = 3.706 as const 24 | export const CENTER_AFTER_FALL_COST = 0.927 as const 25 | export const COST_INF = 1000000 26 | 27 | export const FALL_N_BLOCKS_COST = generateFallNBlocksCost() 28 | export const FALL_1_25_BLOCKS_COST = distanceToTicks(1.25) 29 | export const FALL_0_25_BLOCKS_COST = distanceToTicks(0.25) 30 | export const JUMP_ONE_BLOCK_COST = FALL_1_25_BLOCKS_COST - FALL_0_25_BLOCKS_COST 31 | 32 | export function distanceToTicks (distance: number): number { 33 | if (distance === 0) { 34 | return 0 35 | } 36 | let tmpDistance = distance 37 | let tickCount = 0 38 | while (true) { 39 | const fallDistance = velocity(tickCount) 40 | if (tmpDistance <= fallDistance) { 41 | return tickCount + tmpDistance / fallDistance 42 | } 43 | tmpDistance -= fallDistance 44 | tickCount++ 45 | } 46 | } 47 | 48 | export function velocity (ticks: number): number { 49 | return (Math.pow(0.98, ticks) - 1) * -3.92 50 | } 51 | 52 | export function oldFormula (ticks: number): number { 53 | return -3.92 * (99 - 49.5 * (Math.pow(0.98, ticks) + 1) - ticks) 54 | } 55 | 56 | export function generateFallNBlocksCost (): number[] { 57 | const costs: number[] = [] 58 | for (let i = 0; i < 4097; i++) { 59 | costs[i] = distanceToTicks(i) 60 | } 61 | return costs 62 | } 63 | -------------------------------------------------------------------------------- /src/mineflayer-specific/movements/index.ts: -------------------------------------------------------------------------------- 1 | import { Bot } from 'mineflayer' 2 | import { World } from '../world/worldInterface' 3 | import { MovementOptions } from './movement' 4 | import { MovementProvider } from './movementProvider' 5 | import { MovementExecutor } from './movementExecutor' 6 | 7 | // Don't mind these stupid ass typings, I'll clean them up later. 8 | export type BuildableMoveProvider = new (bot: Bot, world: World, settings: Partial) => MovementProvider 9 | export type BuildableMoveExecutor = new (bot: Bot, world: World, settings: Partial) => MovementExecutor 10 | 11 | export type MovementSetup = Map 12 | export type ExecutorMap = Map 13 | 14 | export * from './movement' 15 | export * from './movementExecutors' 16 | export * from './movementProviders' 17 | export * from './movementExecutor' 18 | export * from './movementProvider' 19 | // export * from './pp' 20 | -------------------------------------------------------------------------------- /src/mineflayer-specific/movements/movement.ts: -------------------------------------------------------------------------------- 1 | import { BaseSimulator, EPhysicsCtx, EntityPhysics } from '@nxg-org/mineflayer-physics-util' 2 | import { Bot } from 'mineflayer' 3 | import { Vec3 } from 'vec3' 4 | import { Move } from '../move' 5 | import { World } from '../world/worldInterface' 6 | import { BlockInfo } from '../world/cacheWorld' 7 | import { BreakHandler, InteractHandler, InteractType, PlaceHandler } from './interactionUtils' 8 | import { Block, Vec3Properties } from '../../types' 9 | import { COST_INF } from './costs' 10 | 11 | export interface MovementOptions { 12 | allowDiagonalBridging: boolean 13 | allowJumpSprint: boolean 14 | allow1by1towers: boolean 15 | liquidCost: number 16 | digCost: number 17 | forceLook: boolean 18 | jumpCost: number 19 | placeCost: number 20 | velocityKillCost: number 21 | canOpenDoors: boolean 22 | canDig: boolean 23 | canPlace: boolean 24 | dontCreateFlow: boolean 25 | dontMineUnderFallingBlock: boolean 26 | 27 | maxDropDown: number 28 | infiniteLiquidDropdownDistance: boolean 29 | allowSprinting: boolean 30 | careAboutLookAlignment: boolean 31 | 32 | movementTimeoutMs: number 33 | } 34 | 35 | export const DEFAULT_MOVEMENT_OPTS: MovementOptions = { 36 | allowJumpSprint: true, 37 | canOpenDoors: true, 38 | canDig: true, 39 | canPlace: true, 40 | dontCreateFlow: true, 41 | dontMineUnderFallingBlock: true, 42 | allow1by1towers: true, 43 | maxDropDown: 3, 44 | infiniteLiquidDropdownDistance: true, 45 | allowSprinting: true, 46 | liquidCost: 3, 47 | placeCost: 2, 48 | digCost: 1, 49 | jumpCost: 0.5, 50 | velocityKillCost: 2, // implement at a later date. 51 | forceLook: true, 52 | careAboutLookAlignment: true, 53 | allowDiagonalBridging: true, 54 | movementTimeoutMs: 1000 55 | } 56 | 57 | const cardinalVec3s: Vec3[] = [ 58 | // { x: -1, z: 0 }, // West 59 | // { x: 1, z: 0 }, // East 60 | // { x: 0, z: -1 }, // North 61 | // { x: 0, z: 1 }, // South 62 | new Vec3(-1, 0, 0), 63 | new Vec3(1, 0, 0), 64 | new Vec3(0, 0, -1), 65 | new Vec3(0, 0, 1) 66 | ] 67 | 68 | Object.freeze(cardinalVec3s) 69 | cardinalVec3s.forEach(Object.freeze) 70 | 71 | const diagonalVec3s: Vec3[] = [ 72 | // { x: -1, z: -1 }, 73 | // { x: -1, z: 1 }, 74 | // { x: 1, z: -1 }, 75 | // { x: 1, z: 1 }, 76 | new Vec3(-1, 0, -1), 77 | new Vec3(-1, 0, 1), 78 | new Vec3(1, 0, -1), 79 | new Vec3(1, 0, 1) 80 | ] 81 | 82 | Object.freeze(diagonalVec3s) 83 | diagonalVec3s.forEach(Object.freeze) 84 | 85 | const jumpVec3s: Vec3[] = [ 86 | new Vec3(-3, 0, 0), 87 | new Vec3(-2, 0, 1), 88 | new Vec3(-2, 0, -1), 89 | new Vec3(-1, 0, 2), 90 | new Vec3(-1, 0, -2), 91 | new Vec3(0, 0, 3), 92 | new Vec3(0, 0, -3), 93 | new Vec3(1, 0, 2), 94 | new Vec3(1, 0, -2), 95 | new Vec3(2, 0, 1), 96 | new Vec3(2, 0, -1), 97 | new Vec3(3, 0, 0) 98 | ] 99 | 100 | Object.freeze(jumpVec3s) 101 | jumpVec3s.forEach(Object.freeze) 102 | 103 | /** 104 | * TODO: Separate calculation time from runtime. 105 | * 106 | * Calculation time is when the bot is deciding what to do. 107 | * Runtime is when the bot is actually doing it. 108 | * 109 | * This class is currently bloated by providing two functions. 110 | * It should be broken up. 111 | */ 112 | 113 | export abstract class Movement { 114 | static readonly cardinalDirs = cardinalVec3s 115 | static readonly diagonalDirs = diagonalVec3s 116 | static readonly jumpDirs = jumpVec3s 117 | 118 | public readonly bot: Bot 119 | public readonly world: World 120 | public settings: MovementOptions 121 | 122 | /** 123 | * Current move being executed. 124 | * 125 | * This move is the same as the thisMove argument provided to functions. 126 | */ 127 | protected currentMove!: Move 128 | 129 | /** 130 | * Current interaction. 131 | */ 132 | protected _cI?: InteractHandler 133 | 134 | public constructor (bot: Bot, world: World, settings: Partial = {}) { 135 | this.bot = bot 136 | this.world = world 137 | this.settings = Object.assign({}, DEFAULT_MOVEMENT_OPTS, settings) 138 | } 139 | 140 | loadMove (move: Move): void { 141 | this.currentMove = move 142 | } 143 | 144 | toBreak (): BreakHandler[] { 145 | return this.currentMove.toBreak?.filter((b) => !b.allowExit) ?? [] 146 | } 147 | 148 | toBreakLen (): number { 149 | return this.currentMove.toBreak?.filter((b) => !b.allowExit).length ?? 0 150 | } 151 | 152 | toPlace (): PlaceHandler[] { 153 | return this.currentMove.toPlace?.filter((b) => !b.allowExit) ?? [] 154 | } 155 | 156 | toPlaceLen (): number { 157 | return this.currentMove.toPlace?.filter((b) => !b.allowExit).length ?? 0 158 | } 159 | 160 | getBlock (pos: Vec3Properties, dx: number, dy: number, dz: number): Block | null { 161 | return this.world.getBlock(new Vec3(pos.x + dx, pos.y + dy, pos.z + dz)) 162 | } 163 | 164 | getBlockInfo (pos: Vec3Properties, dx: number, dy: number, dz: number): BlockInfo { 165 | const yes = new Vec3(Math.floor(pos.x + dx), Math.floor(pos.y + dy), Math.floor(pos.z + dz)) 166 | return this.world.getBlockInfo(yes) 167 | } 168 | 169 | getBlockInfoRaw (pos: Vec3): BlockInfo { 170 | return this.world.getBlockInfo(pos) 171 | } 172 | 173 | /** 174 | * Returns if a block is safe or not 175 | * @param pos 176 | * @returns 177 | */ 178 | safe (pos: Vec3Properties): number { 179 | const block = this.world.getBlockInfo(new Vec3(pos.x, pos.y, pos.z)) 180 | return block.physical ? 0 : COST_INF 181 | } 182 | 183 | /** 184 | * Takes into account if the block is within a break exclusion area. 185 | * @param {BlockInfo} block 186 | * @returns 187 | */ 188 | safeToBreak (block: BlockInfo): boolean { 189 | if (!this.settings.canDig) { 190 | return false 191 | } 192 | 193 | if (this.settings.dontCreateFlow) { 194 | if (!block.additionalLoaded) block.loadAdditionalInfo(this) 195 | if (block.waterAround) return false 196 | } 197 | 198 | if (this.settings.dontMineUnderFallingBlock) { 199 | if (!block.additionalLoaded) block.loadAdditionalInfo(this) 200 | // TODO: Determine if there are other blocks holding the entity up 201 | if (block.fallBlockOver) return false 202 | // if (this.getBlockInfo(block.position, 0, 1, 0).canFall) { 203 | // // || (this.getNumEntitiesAt(block.position, 0, 1, 0) > 0) 204 | // return false 205 | // } 206 | } 207 | 208 | // console.log('block type:', this.bot.registry.blocks[block.type], block.position, !BlockInfo.blocksCantBreak.has(block.type)) 209 | return BlockInfo.replaceables.has(block.type) || !BlockInfo.blocksCantBreak.has(block.type) // && this.exclusionBreak(block) < COST_INF 210 | } 211 | 212 | /** 213 | * Takes into account if the block is within the stepExclusionAreas. And returns COST_INF if a block to be broken is within break exclusion areas. 214 | * @param {import('prismarine-block').Block} block block 215 | * @param {[]} toBreak 216 | * @returns {number} 217 | */ 218 | safeOrBreak (block: BlockInfo, toBreak: BreakHandler[]): number { 219 | // cost += this.exclusionStep(block) // Is excluded so can't move or break 220 | // cost += this.getNumEntitiesAt(block.position, 0, 0, 0) * this.entityCost 221 | 222 | // if (block.breakCost !== undefined) return block.breakCost // cache breaking cost. 223 | 224 | if (block.walkthrough) { 225 | // if (!block.replaceable) toBreak.push(BreakHandler.fromVec(block.position, "solid")); 226 | return 0 // TODO: block is a carpet or a climbable (BUG) 227 | } 228 | 229 | if (block.block === null) return COST_INF // Don't know its type, but that's only replaceables so just return. 230 | 231 | if (!this.safeToBreak(block)) return COST_INF // Can't break, so can't move 232 | 233 | const cost = this.breakCost(block) 234 | 235 | // console.log('cost for:', block.position, cost) 236 | 237 | if (cost >= COST_INF) return cost 238 | 239 | // TODO: Calculate cost of breaking block 240 | // 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 241 | toBreak.push(BreakHandler.fromVec(block.position, 'solid')) 242 | 243 | return cost 244 | } 245 | 246 | breakCost (block: BlockInfo): number { 247 | if (block.block === null) return COST_INF // Don't know its type, but that's only replaceables so just return. 248 | 249 | // const tool = this.bot.pathfinder.bestHarvestTool(block) 250 | 251 | const digTime = this.bot.pathingUtil.digCost(block.block) 252 | // const tool = null as any; 253 | // const enchants = (tool && tool.nbt) ? nbt.simplify(tool.nbt).Enchantments : [] 254 | // const effects = this.bot.entity.effects 255 | // const digTime = block.block.digTime(tool ? tool.type : null, false, false, false, enchants, effects) 256 | const laborCost = (1 + 3 * digTime / 1000) * this.settings.digCost 257 | return laborCost 258 | } 259 | 260 | safeOrPlace (block: BlockInfo, toPlace: PlaceHandler[], type: InteractType = 'solid'): number { 261 | if (!this.settings.canPlace) return COST_INF 262 | if (this.currentMove.remainingBlocks <= 0) return COST_INF 263 | 264 | if (block.block === null) return COST_INF // Don't know its type, but that's only replaceables so just return. 265 | if (block.solidFull) return 0 // block is already physical at location. 266 | 267 | const cost = this.placeCost(block) 268 | 269 | if (cost >= COST_INF) return cost 270 | toPlace.push(PlaceHandler.fromVec(block.position, type)) 271 | 272 | return cost 273 | } 274 | 275 | /** 276 | * TODO: calculate more accurate place costs. 277 | */ 278 | placeCost (block: BlockInfo): number { 279 | return this.settings.placeCost 280 | } 281 | } 282 | 283 | export abstract class SimMovement extends Movement { 284 | stateCtx: EPhysicsCtx 285 | sim: BaseSimulator 286 | constructor (bot: Bot, world: World, settings: Partial) { 287 | super(bot, world, settings) 288 | this.sim = new BaseSimulator(new EntityPhysics(bot.registry)) 289 | this.stateCtx = EPhysicsCtx.FROM_BOT(this.sim.ctx, bot) 290 | } 291 | 292 | simulateUntil (...args: Parameters): ReturnType { 293 | return this.sim.simulateUntil(...args) 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/mineflayer-specific/movements/movementProvider.ts: -------------------------------------------------------------------------------- 1 | import { Bot } from 'mineflayer' 2 | import { Move } from '../move' 3 | import * as goals from '../goals' 4 | import { World } from '../world/worldInterface' 5 | import { DEFAULT_MOVEMENT_OPTS, Movement, MovementOptions } from './movement' 6 | 7 | import { MovementProvider as AMovementProvider } from '../../abstract' 8 | import { ExecutorMap } from '.' 9 | import { Vec3 } from 'vec3' 10 | import { Vec3Properties } from '../../types' 11 | import { BlockInfo } from '../world/cacheWorld' 12 | 13 | /** 14 | * Movement provider. 15 | * 16 | * Provides movements to the pathfinder. 17 | */ 18 | export abstract class MovementProvider extends Movement { 19 | orgPos!: Vec3 20 | toClear!: Set 21 | 22 | public constructor (bot: Bot, world: World, settings: Partial = {}) { 23 | super(bot, world, settings) 24 | } 25 | 26 | abstract movementDirs: Vec3[] 27 | 28 | private boundaries!: [x: number, z: number, y: number] 29 | private halfway!: [x: number, z: number, y: number] 30 | 31 | /** 32 | * Simulation-time calculation. 33 | * 34 | * Decide whether or not movement is possible. 35 | * If possible, append to provided storage. 36 | * 37 | * 38 | * @param {Move} start 39 | * @param {Move[]} storage put all resulting moves to this storage 40 | * @param {goals.Goal} goal 41 | * @param {Set} closed Closed set of hashed positions for movements. Use this to cancel calculation early. 42 | */ 43 | abstract provideMovements (start: Move, storage: Move[], goal: goals.Goal, closed: Set): void 44 | 45 | private localData: Array = [] 46 | 47 | loadLocalData (orgPos: Vec3, boundaries: [x: number, z: number, y: number], arr: Array, clear: Set): void { 48 | this.orgPos = orgPos 49 | this.localData = arr 50 | this.boundaries = boundaries 51 | this.halfway = [Math.floor(boundaries[0] / 2), Math.floor(boundaries[1] / 2), Math.floor(boundaries[2] / 2)] 52 | this.toClear = clear 53 | // console.log(this.halfway) 54 | } 55 | 56 | getBlockInfo (pos: Vec3Properties, dx: number, dy: number, dz: number): BlockInfo { 57 | const yes = new Vec3(Math.floor(pos.x) + dx, Math.floor(pos.y) + dy, Math.floor(pos.z) + dz) 58 | return this.getBlockInfoRaw(yes) 59 | } 60 | 61 | getBlockInfoRaw (yes: Vec3): BlockInfo { 62 | // 63 | let move: Move | undefined = this.currentMove 64 | 65 | let i = 0 66 | while (move !== undefined && i++ < 3) { // 5 levels 67 | 68 | for (const m of move.toPlace) { 69 | if (m.x === yes.x && m.y === yes.y && m.z === yes.z) { 70 | return m.blockInfo 71 | } 72 | } 73 | 74 | for (const m of move.toBreak) { 75 | if (m.x === yes.x && m.y === yes.y && m.z === yes.z) { 76 | return m.blockInfo 77 | } 78 | } 79 | 80 | move = move.parent 81 | } 82 | 83 | // if (i > 0) console.log('i', i) 84 | // const wantedDx = pos.x - this.orgPos.x + dx + this.halfway[0] 85 | const wantedDx = yes.x - this.orgPos.x + this.halfway[0] 86 | 87 | // if (wantedDx < 0 || wantedDx >= this.boundaries[0]) { 88 | // return super.getBlockInfo(pos, dx, dy, dz); 89 | // } 90 | 91 | // const wantedDz = pos.z - this.orgPos.z + dz + this.halfway[1] 92 | const wantedDz = yes.z - this.orgPos.z + this.halfway[1] 93 | 94 | // if (wantedDz < 0 || wantedDz >= this.boundaries[2]) { 95 | // return super.getBlockInfo(pos, dx, dy, dz); 96 | // } 97 | 98 | // const wantedDy = pos.y - this.orgPos.y + dy + this.halfway[2] 99 | const wantedDy = yes.y - this.orgPos.y + this.halfway[2] 100 | 101 | // if (wantedDy < 0 || wantedDy >= this.boundaries[2]) { 102 | // return super.getBlockInfo(pos, dx, dy, dz); 103 | // } 104 | 105 | // const packed = (wantedDx << 16) + (wantedDz << 8) + wantedDy 106 | 107 | if ( 108 | wantedDx < 0 || 109 | wantedDx >= this.boundaries[0] || 110 | wantedDz < 0 || 111 | wantedDz >= this.boundaries[1] || 112 | wantedDy < 0 || 113 | wantedDy >= this.boundaries[2] 114 | ) { 115 | return this.world.getBlockInfo(yes) 116 | } 117 | // return super.getBlockInfo(pos, dx, dy, dz) 118 | // console.log('out of bounds', pos, this.orgPos, wantedDx, wantedDy, wantedDz, this.boundaries) 119 | // } 120 | 121 | const idx = wantedDx * this.boundaries[2] * this.boundaries[1] + wantedDz * this.boundaries[2] + wantedDy 122 | 123 | // const data = this.localData[wantedDx][wantedDy][wantedDz]; 124 | const data = this.localData[idx] 125 | 126 | if (data !== null) { 127 | // console.log('goddamnbit', data.block?.position.equals(yes), data.position.equals(yes), data.block?.position, data.position, yes, dx, dy, dz, new Vec3(pos.x, pos.y, pos.z)) 128 | // this.toClear.add(packed) 129 | // const target = new Vec3(wantedDx - this.halfway[0], wantedDy - this.halfway[2], wantedDz - this.halfway[1]).plus(this.orgPos) 130 | // if (!data.position.equals(yes)) { 131 | // console.trace( 132 | // 'crap', 133 | // pos, 134 | // dx, 135 | // dy, 136 | // dz, 137 | // data.position, 138 | // '\n\n', 139 | // this.orgPos, 140 | // wantedDx, 141 | // wantedDy, 142 | // wantedDz, 143 | // target, 144 | // this.halfway, 145 | // this.boundaries, 146 | // data.block, 147 | 148 | // this.localData[idx] 149 | // ) 150 | // throw new Error('dang') 151 | // } 152 | 153 | return data 154 | } 155 | 156 | const ret = this.world.getBlockInfo(yes) 157 | // const ret = super.getBlockInfo(pos, dx, dy, dz) 158 | 159 | this.localData[idx] = ret 160 | return ret 161 | } 162 | } 163 | 164 | export class MovementHandler implements AMovementProvider { 165 | movementMap: ExecutorMap 166 | recognizedMovements: MovementProvider[] 167 | goal!: goals.Goal 168 | world: World 169 | 170 | constructor (bot: Bot, world: World, recMovement: MovementProvider[], movementMap: ExecutorMap) { 171 | this.world = world 172 | this.recognizedMovements = recMovement 173 | this.movementMap = movementMap 174 | } 175 | 176 | static create (bot: Bot, world: World, recMovement: ExecutorMap, settings: Partial = {}): MovementHandler { 177 | const opts = Object.assign({}, DEFAULT_MOVEMENT_OPTS, settings) 178 | return new MovementHandler( 179 | bot, 180 | world, 181 | [...recMovement.keys()].map((M) => new M(bot, world, opts)), 182 | recMovement 183 | ) 184 | } 185 | 186 | getMovements (): ExecutorMap { 187 | return this.movementMap 188 | } 189 | 190 | sanitize (): boolean { 191 | // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions 192 | return !!this.goal 193 | } 194 | 195 | loadGoal (goal: goals.Goal): void { 196 | this.goal = goal 197 | } 198 | 199 | private readonly boundaries: [x: number, z: number, y: number] = [7, 7, 7] 200 | private readonly halfway: [x: number, z: number, y: number] = [Math.floor(this.boundaries[0] / 2), Math.floor(this.boundaries[1] / 2), Math.floor(this.boundaries[2] / 2)] 201 | 202 | private readonly maxBound = this.boundaries[0] * this.boundaries[1] * this.boundaries[2] 203 | private readonly toClear: Set = new Set() 204 | private readonly localData: Array = new Array(this.maxBound).fill(null, 0, this.maxBound) 205 | 206 | resetLocalData (): void { 207 | for (let i = 0; i < this.maxBound; i++) { 208 | this.localData[i] = null 209 | } 210 | } 211 | 212 | // Do not reassign localData, must do shift in place. 213 | 214 | private readonly swapArray = new Array(this.maxBound).fill(null) 215 | private readonly swapSet = new Array(this.maxBound) 216 | 217 | static count = 0 218 | static totCount = 0 219 | shiftLocalData (orgPos: Vec3, newPos: Vec3): void { 220 | const diff = newPos.minus(orgPos) 221 | 222 | let swapIdx = 0 223 | for (let idx = 0; idx < this.maxBound; idx++) { 224 | if (this.localData[idx] === null) continue 225 | 226 | // convert i into 3D indexes, boundaries are this.boundaries 227 | const x = Math.floor(idx / (this.boundaries[2] * this.boundaries[1])) 228 | const rest = idx % (this.boundaries[2] * this.boundaries[1]) 229 | const z = Math.floor(rest / this.boundaries[2]) 230 | const y = rest % this.boundaries[2] 231 | 232 | const newX = x - diff.x 233 | const newY = y - diff.y 234 | const newZ = z - diff.z 235 | 236 | if (newX >= 0 && newX < this.boundaries[0] && newY >= 0 && newY < this.boundaries[2] && newZ >= 0 && newZ < this.boundaries[1]) { 237 | const newIdx = newX * this.boundaries[2] * this.boundaries[1] + newZ * this.boundaries[2] + newY 238 | 239 | this.swapArray[newIdx] = this.localData[idx] 240 | 241 | this.swapSet[swapIdx++] = newIdx 242 | } 243 | 244 | this.localData[idx] = null 245 | } 246 | 247 | for (let i = 0; i < swapIdx; i++) { 248 | const idx = this.swapSet[i] 249 | this.localData[idx] = this.swapArray[idx] 250 | } 251 | if (swapIdx > 0) MovementHandler.count++ 252 | MovementHandler.totCount++ 253 | } 254 | 255 | preloadInteractData (orgPos: Vec3, move: Move): void { 256 | // data has already been shifted, no need to worry. 257 | let move1: Move | undefined = move 258 | let exit = false 259 | 260 | const seen = new Set() 261 | 262 | // theoretically, this is incorrect. Newest iteration should occur, not oldest. 263 | // reverse by starting at root then traversing down. 264 | // or keep track of changes. 265 | while (move1 !== undefined && !exit) { 266 | const wantedDx = move1.x - orgPos.x + this.halfway[0] 267 | const wantedDz = move1.z - orgPos.z + this.halfway[1] 268 | const wantedDy = move1.y - orgPos.y + this.halfway[2] 269 | 270 | if (wantedDx < 0 || wantedDx >= this.boundaries[0] || wantedDz < 0 || wantedDz >= this.boundaries[1] || wantedDy < 0 || wantedDy >= this.boundaries[2]) { 271 | exit = true 272 | } 273 | 274 | for (const m of move1.toPlace) { 275 | const wantedDx = m.x - orgPos.x + this.halfway[0] 276 | const wantedDz = m.z - orgPos.z + this.halfway[1] 277 | const wantedDy = m.y - orgPos.y + this.halfway[2] 278 | 279 | if (wantedDx < 0 || wantedDx >= this.boundaries[0] || wantedDz < 0 || wantedDz >= this.boundaries[1] || wantedDy < 0 || wantedDy >= this.boundaries[2]) { 280 | exit = true 281 | } else { 282 | const idx = wantedDx * this.boundaries[2] * this.boundaries[1] + wantedDz * this.boundaries[2] + wantedDy 283 | if (!seen.has(idx)) { 284 | this.localData[idx] = m.blockInfo 285 | seen.add(idx) 286 | } 287 | } 288 | } 289 | 290 | for (const m of move1.toBreak) { 291 | // idx is the index of the block in the localData array 292 | // idx is offset from current position 293 | const wantedDx = m.x - orgPos.x + this.halfway[0] 294 | const wantedDz = m.z - orgPos.z + this.halfway[1] 295 | const wantedDy = m.y - orgPos.y + this.halfway[2] 296 | 297 | if (wantedDx < 0 || wantedDx >= this.boundaries[0] || wantedDz < 0 || wantedDz >= this.boundaries[1] || wantedDy < 0 || wantedDy >= this.boundaries[2]) { 298 | exit = true 299 | } else { 300 | const idx = wantedDx * this.boundaries[2] * this.boundaries[1] + wantedDz * this.boundaries[2] + wantedDy 301 | if (!seen.has(idx)) { 302 | this.localData[idx] = m.blockInfo 303 | seen.add(idx) 304 | } 305 | } 306 | } 307 | move1 = move1.parent 308 | } 309 | // console.log('i', i, seen.size) 310 | // let move2: Move | undefined = move 311 | // for (let j =0; j < i; j++) { 312 | // // console.log(move2?.vec) 313 | // move2 = move2?.parent 314 | // } 315 | } 316 | 317 | private lastPos?: Vec3 318 | getNeighbors (currentMove: Move, closed: Set): Move[] { 319 | const moves: Move[] = [] 320 | 321 | // console.log('hi') 322 | const pos = currentMove.entryPos.floored() 323 | const old = this.lastPos ?? pos 324 | this.shiftLocalData(old, pos) 325 | this.preloadInteractData(pos, currentMove) 326 | this.lastPos = pos 327 | 328 | // const arr = new Array(this.maxBound).fill(null); 329 | 330 | for (const newMove of this.recognizedMovements) { 331 | newMove.loadMove(currentMove) 332 | newMove.loadLocalData(pos, this.boundaries, this.localData, this.toClear) 333 | newMove.provideMovements(currentMove, moves, this.goal, closed) 334 | } 335 | 336 | // for (const move of moves) { 337 | // const bl = move.moveType.getBlockInfo(move, 0, 0, 0) 338 | // if (bl.liquid && move.toPlace.length > 0) { 339 | // const blocksAtPoses = move.toPlace.map((p) => move.moveType.getBlockInfo(p, 0, 0, 0)) 340 | // // console.log(blocksAtPoses.map(i => [i, i.block?.getProperties(), (i.block as any)?._properties])) 341 | 342 | // // throw new Error(`Liquid detected in toPlace: ${move.moveType.constructor.name} with placements ${move.toPlace.map((p) => p.vec).join(', ')} at pos ${move.vec.toString()} `) 343 | // } 344 | // } 345 | // this.resetLocalData() // same speed, but less memory efficient. 346 | 347 | // console.log(moves.length, moves.map(m=>m.moveType.constructor.name)) 348 | 349 | return moves 350 | 351 | // for differences less than 1 block, we only supply best movement to said block. 352 | 353 | // if (moves.length === 0) return moves 354 | 355 | // const visited = new Set() 356 | // for (const move of moves) { 357 | // visited.add(move.hash) 358 | // } 359 | 360 | // // console.log(visited) 361 | 362 | // const ret = [] 363 | // for (const visit of visited) { 364 | // const tmp = moves.filter((m) => m.hash === visit) 365 | // const wantedCost = stableSort1(tmp, (a, b) => a.cost - b.cost)[0].cost 366 | // const wanted = tmp.filter((m) => m.cost === wantedCost).sort((a, b) => this.goal.heuristic(a) - this.goal.heuristic(b))[0] 367 | // ret.push(wanted) 368 | // } 369 | 370 | // for (const move of moves) { 371 | // (move as any).cost = Math.round(move.cost); 372 | // } 373 | 374 | // return ret 375 | } 376 | } 377 | 378 | // type Comparator = (a: T, b: T) => number 379 | 380 | // const defaultCmp: Comparator = (a, b) => { 381 | // if (a < b) return -1 382 | // if (a > b) return 1 383 | // return 0 384 | // } 385 | 386 | // function stableSort1 (arr: T[], cmp: Comparator = defaultCmp): T[] { 387 | // const stabilized = arr.map((el, index) => [el, index] as [T, number]) 388 | // const stableCmp: Comparator<[T, number]> = (a, b) => { 389 | // const order = cmp(a[0], b[0]) 390 | // if (order !== 0) return order 391 | // return a[1] - b[1] 392 | // } 393 | 394 | // stabilized.sort(stableCmp) 395 | // for (let i = 0; i < arr.length; i++) { 396 | // arr[i] = stabilized[i][0] 397 | // } 398 | 399 | // return arr 400 | // } 401 | -------------------------------------------------------------------------------- /src/mineflayer-specific/movements/pp.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | // eslint-disable- 4 | 5 | import { Vec3 } from 'vec3' 6 | import { SimMovement } from '../movements' 7 | import { BaseSimulator, Controller, EPhysicsCtx, EntityState, SimulationGoal } from '@nxg-org/mineflayer-physics-util' 8 | import { Bot } from 'mineflayer' 9 | import { goals } from '../goals' 10 | import { Move } from '../move' 11 | import { emptyVec } from '@nxg-org/mineflayer-physics-util/dist/physics/settings' 12 | 13 | function setState (simCtx: EPhysicsCtx, pos: Vec3, vel: Vec3): void { 14 | simCtx.state.age = 0 15 | simCtx.state.pos.set(pos.x, pos.y, pos.z) 16 | simCtx.state.vel.set(vel.x, vel.y, vel.z) 17 | } 18 | 19 | export class ForwardJumpMovement extends SimMovement { 20 | controlAim (nextPoint: Vec3): Controller { 21 | // const zero = controls.getControllerSmartMovement(nextPoint, true); 22 | // const one = controls.getControllerStrafeAim(nextPoint); 23 | let aimed = false 24 | return (state: EntityState, ticks: number) => { 25 | if (!aimed) { 26 | const dx = nextPoint.x - state.pos.x 27 | const dz = nextPoint.z - state.pos.z 28 | state.yaw = Math.atan2(-dx, -dz) 29 | aimed = true 30 | } 31 | 32 | if (state.isCollidedHorizontally) { 33 | state.control.set('jump', true) 34 | state.control.set('forward', false) 35 | // state.control.set("back", true); 36 | state.control.set('sprint', false) 37 | } else { 38 | if (state.vel.offset(0, -state.vel.y, 0).norm() > 0.15) state.control.set('jump', true) 39 | // state.control.set("back", false); 40 | state.control.set('forward', true) 41 | state.control.set('sprint', true) 42 | } 43 | 44 | // zero(state, ticks); 45 | // one(state, ticks); 46 | } 47 | } 48 | 49 | botAim (bot: Bot, nextMove: Vec3, goal: goals.Goal): () => void { 50 | // const zero = controls.getBotSmartMovement(bot, nextMove, true); 51 | // const one = controls.getBotStrafeAim(bot, nextMove); 52 | let aimed = false 53 | return () => { 54 | if (!aimed) { 55 | const dx = nextMove.x - bot.entity.position.x 56 | const dz = nextMove.z - bot.entity.position.z 57 | bot.entity.yaw = Math.atan2(-dx, -dz) 58 | aimed = true 59 | } 60 | 61 | if ((bot.entity as any).isCollidedHorizontally as boolean) { 62 | bot.setControlState('jump', true) 63 | bot.setControlState('forward', false) 64 | bot.setControlState('back', true) 65 | bot.setControlState('sprint', false) 66 | } else { 67 | // console.log(bot.entity.velocity.offset(0, -bot.entity.velocity.y,0).norm()) 68 | if (bot.entity.velocity.offset(0, -bot.entity.velocity.y, 0).norm() > 0.15) bot.setControlState('jump', true) 69 | bot.setControlState('back', false) 70 | bot.setControlState('forward', true) 71 | bot.setControlState('sprint', true) 72 | } 73 | // zero(); 74 | // one(); 75 | } 76 | } 77 | 78 | // right should be positiive, 79 | // left should be negative. 80 | 81 | getReached (goal: goals.Goal, nextPos: Vec3, start: Move): SimulationGoal { 82 | const vecGoal = goal.toVec() 83 | return (state: EntityState, age: number) => { 84 | if (!state.isCollidedVertically) return false 85 | return vecGoal.minus(state.pos).norm() <= vecGoal.minus(nextPos).norm() // && state.pos.minus(start.entryPos).norm() < 0.5 86 | } 87 | } 88 | 89 | botReach (bot: Bot, move: Move, goal: goals.Goal): () => boolean { 90 | const vecGoal = goal.toVec() 91 | return () => { 92 | if (!bot.entity.onGround) return false 93 | return vecGoal.minus(bot.entity.position).norm() <= vecGoal.minus(move.exitPos).norm() 94 | } 95 | } 96 | 97 | provideMovements (start: Move, storage: Move[], goal: goals.Goal): void { 98 | for (const dir of SimMovement.jumpDirs) { 99 | setState(this.stateCtx, start.exitPos, emptyVec) 100 | this.stateCtx.state.clearControlStates() 101 | 102 | const nextGoal = new Vec3(start.x + dir.x, start.y + dir.y, start.z + dir.z) 103 | const stopOnVertCollision: SimulationGoal = (state, ticks) => { 104 | return state.control.get('jump') && state.isCollidedVertically 105 | } 106 | const reach = this.getReached(goal, start.exitPos, start) 107 | 108 | // console.log(start.exitPos) 109 | const state = this.simulateUntil( 110 | BaseSimulator.buildAnyGoal(stopOnVertCollision), 111 | () => {}, 112 | this.controlAim(nextGoal), 113 | this.stateCtx, 114 | this.bot.world, 115 | 30 116 | ) 117 | 118 | const diff = state.pos.minus(start.exitPos).norm() 119 | 120 | // console.log(state.pos, nextGoal, diff) 121 | if (diff === 0) return 122 | const cost = Math.round(state.age * 1) 123 | 124 | // if (stopOnHoriCollision(state, state.age)) { 125 | // return; 126 | // } 127 | 128 | const good: boolean = reach(state, state.age) 129 | if (good) { 130 | // console.log("GI",state.pos, state.isCollidedVertically, cost) 131 | storage.push(Move.fromPreviousState(cost, state, start, this)) 132 | } 133 | } 134 | } 135 | 136 | align (thisMove: Move, tickCount: number, goal: goals.Goal): boolean { 137 | return this.bot.entity.onGround 138 | } 139 | 140 | performPerTick = (move: Move, tickCount: number, currentIndex: number, path: Move[]): boolean => { 141 | // if (tickCount > 160) throw new CancelError("ForwardJumpMovement", "Took too long to reach goal"); 142 | 143 | // const botAim = this.botAim(this.bot, move.exitPos, goal); 144 | // const botReach = this.botReach(this.bot, move, nextMove); 145 | // botAim(); 146 | // return botReach(); 147 | return true 148 | } 149 | 150 | performInit = async (move: Move, currentIndex: number, path: Move[]): Promise => { 151 | await this.bot.lookAt(new Vec3(move.exitPos.x, move.exitPos.y, move.exitPos.z), true) 152 | 153 | const dx = move.exitPos.x - this.bot.entity.position.x 154 | const dz = move.exitPos.z - this.bot.entity.position.z 155 | const wantedYaw = Math.atan2(-dx, -dz) 156 | this.bot.entity.yaw = wantedYaw 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/mineflayer-specific/movements/simulators/jumpSim.ts: -------------------------------------------------------------------------------- 1 | import { EntityPhysics } from '@nxg-org/mineflayer-physics-util/dist/physics/engines' 2 | import { EntityState } from '@nxg-org/mineflayer-physics-util/dist/physics/states' 3 | import { AABB, AABBUtils } from '@nxg-org/mineflayer-util-plugin' 4 | import { Vec3 } from 'vec3' 5 | import { World } from '../../world/worldInterface' 6 | import { BaseSimulator, Controller, EPhysicsCtx, OnGoalReachFunction, SimulationGoal } from '@nxg-org/mineflayer-physics-util' 7 | import { smartMovement, strafeMovement, wrapRadians } from '../controls' 8 | 9 | // const ZERO = (0 * Math.PI) / 12 10 | const PI_OVER_TWELVE = (1 * Math.PI) / 12 11 | // const TWO_PI_OVER_TWELVE = (2 * Math.PI) / 12 12 | // const THREE_PI_OVER_TWELVE = (3 * Math.PI) / 12 13 | // const FOUR_PI_OVER_TWELVE = (4 * Math.PI) / 12 14 | const FIVE_PI_OVER_TWELVE = (5 * Math.PI) / 12 15 | // const SIX_PI_OVER_TWELVE = (6 * Math.PI) / 12 16 | const SEVEN_PI_OVER_TWELVE = (7 * Math.PI) / 12 17 | // const EIGHT_PI_OVER_TWELVE = (8 * Math.PI) / 12 18 | // const NINE_PI_OVER_TWELVE = (9 * Math.PI) / 12 19 | // const TEN_PI_OVER_TWELVE = (10 * Math.PI) / 12 20 | const ELEVEN_PI_OVER_TWELVE = (11 * Math.PI) / 12 21 | // const TWELVE_PI_OVER_TWELVE = (12 * Math.PI) / 12 22 | const THIRTEEN_PI_OVER_TWELVE = (13 * Math.PI) / 12 23 | // const FOURTEEN_PI_OVER_TWELVE = (14 * Math.PI) / 12 24 | // const FIFTEEN_PI_OVER_TWELVE = (15 * Math.PI) / 12 25 | // const SIXTEEN_PI_OVER_TWELVE = (16 * Math.PI) / 12 26 | const SEVENTEEN_PI_OVER_TWELVE = (17 * Math.PI) / 12 27 | // const EIGHTEEN_PI_OVER_TWELVE = (18 * Math.PI) / 12 28 | const NINETEEN_PI_OVER_TWELVE = (19 * Math.PI) / 12 29 | // const TWENTY_PI_OVER_TWELVE = (20 * Math.PI) / 12 30 | // const TWENTY_ONE_PI_OVER_TWELVE = (21 * Math.PI) / 12 31 | // const TWENTY_TWO_PI_OVER_TWELVE = (22 * Math.PI) / 12 32 | const TWENTY_THREE_PI_OVER_TWELVE = (23 * Math.PI) / 12 33 | // const TWENTY_FOUR_PI_OVER_TWELVE = (24 * Math.PI) / 12 34 | 35 | /** 36 | * To be used once per movement. 37 | * 38 | * Provide state that will serve as a base. The state itself will not be modified/consumed unless called for. 39 | */ 40 | export class JumpSim extends BaseSimulator { 41 | public readonly world: World 42 | 43 | constructor (public readonly physics: EntityPhysics, world: World) { 44 | super(physics) 45 | this.world = world 46 | } 47 | 48 | public clone (): JumpSim { 49 | return new JumpSim(this.physics, this.world) 50 | } 51 | 52 | simulateUntilNextTick (ctx: EPhysicsCtx): EntityState { 53 | return this.simulateUntil( 54 | () => false, 55 | () => {}, 56 | () => {}, 57 | ctx, 58 | this.world, 59 | 1 60 | ) 61 | } 62 | 63 | simulateUntilOnGround (ctx: EPhysicsCtx, ticks = 5, goal: SimulationGoal = () => false): EntityState { 64 | const state = this.simulateUntil( 65 | JumpSim.buildAnyGoal(goal, (state, ticks) => ticks > 0 && state.onGround), 66 | () => {}, 67 | () => {}, 68 | ctx, 69 | this.world, 70 | ticks 71 | ) 72 | 73 | // if (print) { 74 | // console.trace("potentially good", state.pos, state.vel, goal(state, 0)); 75 | // console.trace(); 76 | // } 77 | return state 78 | } 79 | 80 | simulateSmartAim (goal: AABB[], goalVec: Vec3, ctx: EPhysicsCtx, sprint: boolean, jump: boolean, jumpAfter = 0, ticks = 20): EntityState { 81 | return this.simulateUntil( 82 | JumpSim.getReachedAABB(goal), 83 | JumpSim.getCleanupPosition(goalVec), 84 | JumpSim.buildFullController( 85 | JumpSim.getControllerStraightAim(goalVec), 86 | // JumpSim.getControllerStrafeAim(goal), 87 | // JumpSim.getControllerSmartMovement(goal, sprint), 88 | JumpSim.getControllerJumpSprint(jump, sprint, jumpAfter) 89 | ), 90 | ctx, 91 | this.world, 92 | ticks 93 | ) 94 | } 95 | 96 | /** 97 | * Assume we know the correct back-up position. 98 | */ 99 | simulateBackUpBeforeJump (ctx: EPhysicsCtx, goal: Vec3, sprint: boolean, strafe = true, ticks = 20): EntityState { 100 | const aim = strafe ? JumpSim.getControllerStrafeAim(goal) : JumpSim.getControllerStraightAim(goal) 101 | return this.simulateUntil( 102 | (state) => state.pos.xzDistanceTo(goal) < 0.1, 103 | JumpSim.getCleanupPosition(goal), 104 | JumpSim.buildFullController(aim, JumpSim.getControllerSmartMovement(goal, sprint), (state, ticks) => { 105 | state.control.sprint = false 106 | state.control.sneak = true 107 | }), 108 | ctx, 109 | this.world, 110 | ticks 111 | ) 112 | } 113 | 114 | simulateJumpFromEdgeOfBlock (ctx: EPhysicsCtx, srcAABBs: AABB[], goalCorner: Vec3, goalBlock: AABB[], sprint: boolean, ticks = 20): EntityState { 115 | let jump = false 116 | let changed = false 117 | 118 | // console.log('edge jump init', ctx.state.pos) 119 | return this.simulateUntil( 120 | JumpSim.getReachedAABB(goalBlock), 121 | JumpSim.getCleanupPosition(goalCorner), 122 | JumpSim.buildFullController( 123 | JumpSim.getControllerStraightAim(goalCorner), 124 | JumpSim.getControllerStrafeAim(goalCorner), 125 | JumpSim.getControllerSmartMovement(goalCorner, sprint), 126 | (state, ticks) => { 127 | // console.log('jump edge', state.age, state.pos) 128 | state.control.sneak = false 129 | // check if player is leaving src block collision 130 | const playerBB = state.getAABB() 131 | playerBB.expand(0, 1e-6, 0) 132 | if (jump && state.pos.xzDistanceTo(goalCorner) < 0.5 && !changed) { 133 | // goalCorner.set(goalBlockTop.x, goalBlockTop.y, goalBlockTop.z) 134 | changed = true 135 | } 136 | if (ticks > 0 && srcAABBs.every((src) => !src.intersects(playerBB)) && !jump) { 137 | state.control.jump = true 138 | jump = true 139 | } else { 140 | state.control.jump = false 141 | } 142 | } 143 | ), 144 | ctx, 145 | this.world, 146 | ticks 147 | ) 148 | } 149 | 150 | static getReachedAABB (bbs: AABB[]): SimulationGoal { 151 | if (bbs.length === 0) throw new Error('JumpSim: No AABBs for goal provided') 152 | const maxY = bbs.reduce((max, a) => Math.max(max, a.maxY), -Infinity) 153 | 154 | const lastXZVel = new Vec3(0, 0, 0) 155 | return (state) => { 156 | const bb = AABBUtils.getPlayerAABB({ position: state.pos, width: 0.5999, height: 1.8 }) 157 | // if (state.pos.y >= maxY-0.3) console.log('hm?',state.age, state.pos, bbs) 158 | const xzVel = state.vel.offset(0, -state.vel.y, 0) 159 | 160 | // make sure bump does not occur. 161 | const ret = state.pos.y >= maxY && bbs.some((a) => a.collides(bb)) && lastXZVel.norm() - xzVel.norm() < 0.04 162 | 163 | return ret 164 | } 165 | } 166 | 167 | static getCleanupPosition (...path: Vec3[]): OnGoalReachFunction { 168 | return (state) => { 169 | state.clearControlStates() 170 | } 171 | } 172 | 173 | static getControllerStraightAim (nextPoint: Vec3): Controller { 174 | return (state, ticks) => { 175 | const dx = nextPoint.x - state.pos.x 176 | const dz = nextPoint.z - state.pos.z 177 | state.yaw = Math.atan2(-dx, -dz) 178 | } 179 | } 180 | 181 | // right should be positiive, 182 | // left should be negative. 183 | static getControllerStrafeAim (nextPoint: Vec3): Controller { 184 | return (state, ticks) => strafeMovement(state, nextPoint) 185 | 186 | // commented out for testing. 187 | // eslint-disable-next-line no-unreachable 188 | return (state, ticks) => { 189 | const offset = state.pos.plus(state.onGround ? state.vel : state.vel.scaled(1)) 190 | const dx = nextPoint.x - offset.x 191 | const dz = nextPoint.z - offset.z 192 | const wantedYaw = wrapRadians(Math.atan2(-dx, -dz)) 193 | const diff = wrapRadians(wantedYaw - state.yaw) 194 | 195 | if (PI_OVER_TWELVE < diff && diff < ELEVEN_PI_OVER_TWELVE) { 196 | state.control.left = true // are these reversed? tf 197 | state.control.right = false 198 | // console.log("left"); 199 | } else if (THIRTEEN_PI_OVER_TWELVE < diff && diff < TWENTY_THREE_PI_OVER_TWELVE) { 200 | state.control.left = false 201 | state.control.right = true 202 | // console.log("right"); 203 | } else { 204 | state.control.left = false 205 | state.control.right = false 206 | // console.log("rotate neither, left:", state.control.movements.left, "right:", state.control.movements.right); 207 | } 208 | } 209 | } 210 | 211 | static getControllerJumpSprint (jump: boolean, sprint: boolean, jumpAfter = 0): Controller { 212 | return (state, ticks) => { 213 | state.control.jump = state.onGround && jump && ticks >= jumpAfter 214 | state.control.sprint = sprint 215 | } 216 | } 217 | 218 | // forward should be any value that abs. val to below pi / 2 219 | // backward is any value that abs. val to above pi / 2 220 | static getControllerSmartMovement (goal: Vec3, sprint: boolean): Controller { 221 | return (state, ticks) => smartMovement(state, goal, sprint) 222 | 223 | // commented out for testing. 224 | // eslint-disable-next-line no-unreachable 225 | return (state, ticks) => { 226 | const offset = state.pos.plus(state.onGround ? state.vel : state.vel.scaled(1)) 227 | const dx = goal.x - offset.x 228 | const dz = goal.z - offset.z 229 | const wantedYaw = wrapRadians(Math.atan2(-dx, -dz)) 230 | const diff = wrapRadians(wantedYaw - state.yaw) 231 | // console.log(diff / Math.PI * 12) 232 | if (SEVEN_PI_OVER_TWELVE < diff && diff < SEVENTEEN_PI_OVER_TWELVE) { 233 | state.control.forward = false 234 | state.control.sprint = false 235 | state.control.back = true 236 | // console.log("back"); 237 | } else if (NINETEEN_PI_OVER_TWELVE < diff || diff < FIVE_PI_OVER_TWELVE) { 238 | state.control.forward = true 239 | state.control.sprint = sprint 240 | state.control.back = false 241 | } else { 242 | state.control.forward = false 243 | state.control.back = false 244 | state.control.sprint = false 245 | } 246 | } 247 | } 248 | 249 | static buildFullController (...controllers: Controller[]): Controller { 250 | return (state, ticks) => { 251 | controllers.forEach((control) => control(state, ticks)) 252 | } 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/mineflayer-specific/node.ts: -------------------------------------------------------------------------------- 1 | import { PathNode as BaseNode } from '../abstract' 2 | import { Move } from './move' 3 | 4 | export class PathNode extends BaseNode {} 5 | -------------------------------------------------------------------------------- /src/mineflayer-specific/pathProducers/continuousPathProducer.ts: -------------------------------------------------------------------------------- 1 | import { Bot } from 'mineflayer' 2 | import { PathProducer, AStar } from '../../mineflayer-specific/algs' 3 | import * as goals from '../goals' 4 | import { Move } from '../move' 5 | import { ExecutorMap, MovementHandler, MovementOptions } from '../movements' 6 | import { World } from '../world/worldInterface' 7 | import { AdvanceRes } from '.' 8 | 9 | export class ContinuousPathProducer implements PathProducer { 10 | private readonly start: Move 11 | private readonly goal: goals.Goal 12 | private readonly settings: MovementOptions 13 | private readonly bot: Bot 14 | private readonly world: World 15 | private readonly movements: ExecutorMap 16 | private astarContext: AStar | undefined 17 | private _currentPath: Move[] = [] 18 | 19 | private readonly gcInterval: number = 10 20 | private lastGc: number = 0 21 | private readonly lastStartTime = performance.now() 22 | constructor (start: Move, goal: goals.Goal, settings: MovementOptions, bot: Bot, world: World, movements: ExecutorMap) { 23 | this.start = start 24 | this.goal = goal 25 | this.settings = settings 26 | this.bot = bot 27 | this.world = world 28 | this.movements = movements 29 | } 30 | 31 | getAstarContext (): AStar | undefined { 32 | return this.astarContext 33 | } 34 | 35 | getCurrentPath (): Move[] { 36 | return this._currentPath 37 | } 38 | 39 | advance (): AdvanceRes { 40 | if (this.astarContext == null) { 41 | const moveHandler = MovementHandler.create(this.bot, this.world, this.movements, this.settings) 42 | moveHandler.loadGoal(this.goal) 43 | 44 | this.astarContext = new AStar(this.start, moveHandler, this.goal, -1, 40, -1, 0) 45 | } 46 | 47 | const result = this.astarContext.compute() 48 | this._currentPath = result.path 49 | 50 | // console.log('advancing!') 51 | 52 | if (global.gc != null && ++this.lastGc % this.gcInterval === 0) { 53 | // const starttime = performance.now() 54 | 55 | if (this.lastGc % (this.gcInterval * 10) === 0) { 56 | // global.gc(); 57 | } else { 58 | (global as any).gc(true) 59 | } 60 | 61 | // console.log('Garbage collection took', performance.now() - starttime, 'ms') 62 | } else { 63 | // console.log('Garbage collection unavailable. Pass --expose-gc ' 64 | // + 'when launching node to enable forced garbage collection.'); 65 | } 66 | 67 | // debug all same info in partialPathProducer 68 | 69 | // const cost = this.astarContext?.bestNode?.g ?? 0 70 | // const nodecount = this.astarContext?.nodeConsiderCount ?? 0 71 | // const seensize = this.astarContext?.closedDataSet.size ?? 0 72 | // const movecount = this.astarContext?.moveConsiderCount ?? 0 73 | 74 | // const time1 = performance.now() - this.lastStartTime 75 | 76 | // console.log('\nthis iter:', time1) 77 | // console.log('itered considered nodes', nodecount, 'nodes/s', (nodecount / time1) * 1000) 78 | // console.log('itered seen size', seensize, 'nodes/s', (seensize / time1) * 1000) 79 | // console.log('itered move considered', movecount, 'nodes/s', (movecount / time1) * 1000) 80 | 81 | // console.log('locality %', (MovementHandler.count / MovementHandler.totCount) * 100) 82 | // console.log('cost', cost) 83 | // console.log('path length', result.path.length) 84 | // this.lastStartTime = performance.now() 85 | // const time = performance.now() - this.startTime 86 | // console.log('\ntotal', time, 'ms') 87 | // console.log('total considered nodes', this.consideredNodeCount, time, (this.consideredNodeCount / time) * 1000, 'nodes/s') 88 | // console.log('total seen size', this.latestClosedNodeCount, time, (this.latestClosedNodeCount / time) * 1000, 'nodes/s') 89 | // console.log('total move considered', this.latestMoveCount, time, (this.latestMoveCount / time) * 1000, 'nodes/s') 90 | 91 | return { result, astarContext: this.astarContext } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/mineflayer-specific/pathProducers/index.ts: -------------------------------------------------------------------------------- 1 | import { AStar, Path } from '../algs' 2 | 3 | export * from './continuousPathProducer' 4 | export * from './partialPathProducer' 5 | 6 | // temp typing 7 | export interface AdvanceRes { 8 | result: Path 9 | astarContext: AStar 10 | } 11 | -------------------------------------------------------------------------------- /src/mineflayer-specific/pathProducers/partialPathProducer.ts: -------------------------------------------------------------------------------- 1 | import { Bot } from 'mineflayer' 2 | import { PathProducer, AStar } from '../../mineflayer-specific/algs' 3 | import * as goals from '../goals' 4 | import { Move } from '../move' 5 | import { ExecutorMap, MovementHandler, MovementOptions } from '../movements' 6 | import { World } from '../world/worldInterface' 7 | import { AdvanceRes } from '.' 8 | 9 | export class PartialPathProducer implements PathProducer { 10 | private readonly start: Move 11 | private readonly goal: goals.Goal 12 | private readonly settings: MovementOptions 13 | private readonly bot: Bot 14 | private readonly world: World 15 | private readonly movements: ExecutorMap 16 | private latestMove: Move | undefined 17 | private readonly latestMoves: Move[] = [] 18 | 19 | private latestClosedNodeCount: number = 0 20 | private latestCost: number = 0 21 | private lastPath: Move[] = [] 22 | 23 | private readonly gcInterval: number = 10 24 | private readonly lastGc: number = 0 25 | 26 | private readonly startTime = performance.now() 27 | private lastStartTime = performance.now() 28 | consideredNodeCount: number = 0 29 | latestMoveCount: number = 0 30 | // private readonly maxPathLen: number = 30 31 | 32 | private _lastContext: AStar | undefined 33 | 34 | public get maxPathLength (): number { 35 | return this.bot.pathfinder.pathfinderSettings.partialPathLength 36 | } 37 | 38 | public get lastAstarContext (): AStar | undefined { 39 | return this._lastContext 40 | } 41 | 42 | constructor (start: Move, goal: goals.Goal, settings: MovementOptions, bot: Bot, world: World, movements: ExecutorMap) { 43 | this.start = start 44 | this.goal = goal 45 | this.settings = settings 46 | this.bot = bot 47 | this.world = world 48 | this.movements = movements 49 | } 50 | 51 | getAstarContext (): AStar | undefined { 52 | return this._lastContext 53 | } 54 | 55 | getCurrentPath (): Move[] { 56 | return this.lastPath 57 | } 58 | 59 | private getSliceLen (orgLen: number): number { 60 | return Math.min(orgLen - 1, Math.floor(orgLen * 0.9)) 61 | } 62 | 63 | private handleAstarContext (foundPathLen: number, maxPathLen = this.maxPathLength): AStar | undefined { 64 | // if the path length is less than 50, return the previous astar context. 65 | // otherwise, return a new one. 66 | 67 | if (this._lastContext != null && foundPathLen <= maxPathLen) { 68 | return this._lastContext 69 | } 70 | 71 | return this.generateAstarContext() 72 | } 73 | 74 | private generateAstarContext (): AStar { 75 | const moveHandler = MovementHandler.create(this.bot, this.world, this.movements, this.settings) 76 | moveHandler.loadGoal(this.goal) 77 | 78 | let start 79 | if (this.latestMove != null) { 80 | start = this.latestMove 81 | } else { 82 | start = this.start 83 | } 84 | 85 | // const lastClosedSet = this.lastAstarContext != null ? this.lastAstarContext.closedDataSet : new Set() 86 | const ret = new AStar(start, moveHandler, this.goal, -1, 40, -1, 0) 87 | 88 | // ret.closedDataSet = lastClosedSet 89 | return ret 90 | } 91 | 92 | advance (): AdvanceRes { 93 | if (this._lastContext == null) this._lastContext = this.generateAstarContext() 94 | 95 | const result = this._lastContext.compute() 96 | 97 | let status = result.status 98 | 99 | if (result.status === 'noPath') { 100 | this.latestMoves.pop() 101 | 102 | if (this.latestMoves.length === 0) { 103 | const astarContext = this._lastContext 104 | delete this._lastContext 105 | return { 106 | result: { 107 | ...result, 108 | status, 109 | cost: this.latestCost, 110 | path: this.lastPath 111 | }, 112 | astarContext 113 | } 114 | } 115 | } 116 | 117 | if (result.path.length > this.maxPathLength || result.status === 'success') { 118 | status = status === 'success' ? 'success' : 'partialSuccess' 119 | 120 | // const val = result.path.length - 1 121 | const val = this.getSliceLen(result.path.length) 122 | this.latestMove = result.path[val] 123 | const toTake = result.path.slice(0, val + 1) 124 | this.latestMoves.push(this.latestMove) 125 | this.lastPath = [...this.lastPath, ...toTake] 126 | 127 | const cost = toTake.reduce((acc, move) => acc + move.cost, 0) 128 | const nodecount = this._lastContext?.nodeConsiderCount ?? 0 129 | const seensize = this._lastContext?.closedDataSet.size ?? 0 130 | const movecount = this._lastContext?.moveConsiderCount ?? 0 131 | 132 | this.latestCost += cost 133 | this.consideredNodeCount += nodecount 134 | this.latestClosedNodeCount += seensize 135 | this.latestMoveCount += movecount 136 | console.info('Partial Path cost increased by', cost, 'to', this.latestCost, 'total', this.latestMove?.vec) 137 | 138 | const time1 = performance.now() - this.lastStartTime 139 | console.log('\nthis iter:', time1) 140 | console.log('itered considered nodes', nodecount, 'nodes/s', (nodecount / time1) * 1000) 141 | console.log('itered seen size', seensize, 'nodes/s', (seensize / time1) * 1000) 142 | console.log('itered move considered', movecount, 'nodes/s', (movecount / time1) * 1000) 143 | 144 | this.lastStartTime = performance.now() 145 | const time = performance.now() - this.startTime 146 | console.log('\ntotal', time, 'ms') 147 | console.log('total considered nodes', this.consideredNodeCount, time, (this.consideredNodeCount / time) * 1000, 'nodes/s') 148 | console.log('total seen size', this.latestClosedNodeCount, time, (this.latestClosedNodeCount / time) * 1000, 'nodes/s') 149 | console.log('total move considered', this.latestMoveCount, time, (this.latestMoveCount / time) * 1000, 'nodes/s') 150 | } 151 | 152 | // console.log(result.path.length, 'found path length', this.lastPath.length, 'total length', this.lastPath.map(p => p.entryPos.toString()), this.lastPath[this.lastPath.length - 1].entryPos) 153 | const ret = { 154 | result: { 155 | ...result, 156 | status, 157 | cost: this.latestCost, 158 | path: this.lastPath 159 | }, 160 | astarContext: this._lastContext 161 | } 162 | 163 | this._lastContext = this.handleAstarContext(result.path.length) 164 | 165 | return ret 166 | } 167 | 168 | private mergePathspath (path1: Move[], path2: Move[]): void { 169 | let newPath = path1 170 | for (let i = 0; i < path2.length; i++) { 171 | if (path1[i] === undefined) { 172 | newPath = newPath.concat(path2.slice(i)) 173 | break 174 | } 175 | if (path1[i].exitPos.distanceTo(path2[i].entryPos) > 0.5) { 176 | newPath = newPath.concat(path2.slice(i)) 177 | break 178 | } 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/mineflayer-specific/performer.ts: -------------------------------------------------------------------------------- 1 | import { Algorithm, Path } from '../abstract' 2 | import * as goals from './goals' 3 | import { Move } from './move' 4 | 5 | export interface Performer { 6 | status: 'idle' | 'performing' 7 | 8 | cancel: () => void 9 | performAll: (goal: goals.Goal, path: Path>) => Promise 10 | performMove: (goal: goals.Goal, move: Move) => Promise 11 | } 12 | -------------------------------------------------------------------------------- /src/mineflayer-specific/post/index.ts: -------------------------------------------------------------------------------- 1 | import { Bot } from 'mineflayer' 2 | import { BuildableMoveProvider } from '../movements' 3 | import { World } from '../world/worldInterface' 4 | import { MovementOptimizer } from './optimizer' 5 | import { MovementReplacement } from './replacement' 6 | 7 | export * from './optimizer' 8 | 9 | export type OptimizationSetup = Map 10 | 11 | export type BuildableOptimizer = new ( 12 | bot: Bot, 13 | world: World, 14 | ) => MovementOptimizer 15 | 16 | export type OptimizationMap = Map 17 | 18 | export type ReplacementMap = Map 19 | -------------------------------------------------------------------------------- /src/mineflayer-specific/post/optimizer.ts: -------------------------------------------------------------------------------- 1 | import { Bot } from 'mineflayer' 2 | import { OptimizationMap } from '.' 3 | import { BuildableMoveProvider } from '../movements' 4 | import { World } from '../world/worldInterface' 5 | import { Move } from '../move' 6 | import { BaseSimulator, EntityPhysics } from '@nxg-org/mineflayer-physics-util' 7 | 8 | export abstract class MovementOptimizer { 9 | bot: Bot 10 | world: World 11 | sim: BaseSimulator 12 | 13 | public readonly mergeInteracts: boolean = true 14 | 15 | constructor (bot: Bot, world: World) { 16 | this.bot = bot 17 | this.world = world 18 | this.sim = new BaseSimulator(new EntityPhysics(bot.registry)) 19 | } 20 | 21 | abstract identEndOpt (currentIndex: number, path: Move[]): number | Promise 22 | 23 | mergeMoves (startIndex: number, endIndex: number, path: readonly Move[]): Move { 24 | // console.log('merging', path[startIndex].moveType.constructor.name, path[endIndex].moveType.constructor.name, path.length) 25 | const startMove = path[startIndex] 26 | const endMove = path[endIndex] 27 | 28 | // console.log('start', startMove.x, startMove.y, startMove.z, startMove.entryPos, startMove.moveType.constructor.name) 29 | // console.log('end', endMove.x, endMove.y, endMove.z, endMove.exitPos, endMove.moveType.constructor.name) 30 | const toBreak = [...startMove.toBreak] 31 | const toPlace = [...startMove.toPlace] 32 | let costSum = 0 33 | 34 | for (let i = startIndex + 1; i < endIndex; i++) { 35 | const intermediateMove = path[i] 36 | if (this.mergeInteracts) { 37 | toBreak.push(...intermediateMove.toBreak) 38 | toPlace.push(...intermediateMove.toPlace) 39 | } 40 | 41 | // TODO: calculate semi-accurate cost by reversing C heuristic of algorithm, 42 | // and then calculating the cost of the path from the start to the end. 43 | costSum += intermediateMove.cost 44 | } 45 | 46 | toBreak.push(...endMove.toBreak) 47 | toPlace.push(...endMove.toPlace) 48 | 49 | costSum += endMove.cost 50 | 51 | // console.log('fully merged', startMove.entryPos, endMove.exitPos, costSum) 52 | return new Move( 53 | startMove.x, 54 | startMove.y, 55 | startMove.z, 56 | toPlace, 57 | toBreak, 58 | endMove.remainingBlocks, 59 | costSum, 60 | startMove.moveType, 61 | startMove.entryPos, 62 | startMove.entryVel, 63 | endMove.exitPos, 64 | endMove.exitVel, 65 | startMove.parent 66 | ) 67 | } 68 | } 69 | 70 | export class Optimizer { 71 | optMap: OptimizationMap 72 | 73 | private pathCopy!: Move[] 74 | private currentIndex: number 75 | 76 | constructor (bot: Bot, world: World, optMap: OptimizationMap) { 77 | this.currentIndex = 0 78 | this.optMap = optMap 79 | } 80 | 81 | loadPath (path: Move[]): void { 82 | // console.log('original path length', path.length) 83 | this.pathCopy = path 84 | this.currentIndex = 0 85 | } 86 | 87 | sanitize (): boolean { 88 | // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions 89 | return !!this.pathCopy 90 | } 91 | 92 | private mergeMoves (startIndex: number, endIndex: number, optimizer: MovementOptimizer): void { 93 | const newMove = optimizer.mergeMoves(startIndex, endIndex, this.pathCopy) 94 | 95 | // console.log("from\n\n", this.pathCopy.map((m, i)=>[i,m.x,m.y,m.z, m.moveType.constructor.name]).join('\n')) 96 | this.pathCopy[startIndex] = newMove 97 | this.pathCopy.splice(startIndex + 1, endIndex - startIndex) 98 | // console.log("to\n\n", this.pathCopy.map((m,i)=>[i,m.x,m.y,m.z, m.moveType.constructor.name]).join('\n')) 99 | } 100 | 101 | async compute (): Promise { 102 | if (!this.sanitize()) { 103 | throw new Error('Optimizer not sanitized') 104 | } 105 | 106 | while (this.currentIndex < this.pathCopy.length) { 107 | const move = this.pathCopy[this.currentIndex] 108 | const opt = this.optMap.get(move.moveType.constructor as BuildableMoveProvider) 109 | // console.log(opt?.constructor.name, this.currentIndex) 110 | if (opt == null) { 111 | this.currentIndex++ 112 | continue 113 | } 114 | const newEnd = await opt.identEndOpt(this.currentIndex, this.pathCopy) 115 | // if (opt.mergeToEntry) newEnd--; 116 | 117 | // console.log('found opt for:', opt?.constructor.name, this.currentIndex, ': here, newEnd', newEnd) 118 | if (newEnd !== this.currentIndex) { 119 | this.mergeMoves(this.currentIndex, newEnd, opt) 120 | } 121 | 122 | // we simply move onto next movement, which will be the next movement after the merged movement. 123 | this.currentIndex++ 124 | } 125 | 126 | // console.log('optimized path length', this.pathCopy.length) 127 | return this.pathCopy 128 | } 129 | 130 | makeResult (): Move[] { 131 | return this.pathCopy 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/mineflayer-specific/post/optimizers.ts: -------------------------------------------------------------------------------- 1 | import { ControlStateHandler, EPhysicsCtx } from '@nxg-org/mineflayer-physics-util' 2 | import { Move } from '../move' 3 | import { RayType } from '../movements/interactionUtils' 4 | import { BlockInfo } from '../world/cacheWorld' 5 | import { MovementOptimizer } from './optimizer' 6 | 7 | import { AABB, AABBUtils } from '@nxg-org/mineflayer-util-plugin' 8 | import { stateLookAt } from '../movements/movementUtils' 9 | 10 | export class LandStraightAheadOpt extends MovementOptimizer { 11 | async identEndOpt (currentIndex: number, path: Move[]): Promise { 12 | const thisMove = path[currentIndex] // starting move 13 | 14 | let lastMove = path[currentIndex] 15 | let nextMove = path[++currentIndex] 16 | 17 | if (nextMove === undefined) return --currentIndex 18 | 19 | const orgY = thisMove.entryPos.y 20 | 21 | const orgPos = thisMove.entryPos.floored().translate(0.5, 0, 0.5) // ensure middle of block. 22 | const hW = 0.6 // ensure safety (larger than actual bot aabb) 23 | const uW = 0.4 24 | 25 | const bb = AABBUtils.getEntityAABBRaw({ position: orgPos, width: hW, height: 1.8 }) 26 | const verts = bb.expand(0, -0.1, 0).toVertices() 27 | // console.log(orgPos, verts) 28 | 29 | const verts1 = [ 30 | orgPos.offset(-uW / 2, -0.6, -uW / 2), 31 | orgPos.offset(-uW / 2, -0.6, uW / 2), 32 | orgPos.offset(uW / 2, -0.6, -uW / 2), 33 | orgPos.offset(uW / 2, -0.6, uW / 2) 34 | ] 35 | 36 | while (lastMove.exitPos.y === orgY && nextMove.exitPos.y === orgY) { 37 | if (!AABB.fromBlockPos(nextMove.entryPos).collides(AABB.fromBlockPos(nextMove.exitPos))) { 38 | return --currentIndex 39 | } 40 | 41 | if (nextMove === undefined) return --currentIndex 42 | for (const vert of verts) { 43 | const offset = vert.minus(orgPos) 44 | const test1 = nextMove.exitPos.offset(0, orgY - nextMove.exitPos.y, 0) 45 | const test = test1.plus(offset) 46 | const dist = nextMove.exitPos.distanceTo(orgPos) 47 | const raycast0 = this.bot.world.raycast( 48 | vert, 49 | test.minus(vert).normalize(), 50 | dist, 51 | (block) => !BlockInfo.replaceables.has(block.type) || BlockInfo.liquids.has(block.type) || BlockInfo.blocksToAvoid.has(block.type) 52 | ) as unknown as RayType | null 53 | const valid0 = (raycast0 == null) || raycast0.position.distanceTo(orgPos) > dist 54 | 55 | // console.log('\n\nBLOCK CHECK') 56 | // console.log('offset', offset) 57 | // console.log('vert', vert) 58 | // console.log('orgPos', orgPos) 59 | // console.log('test1', test1) 60 | // console.log('test', test) 61 | // console.log('raycast0', raycast0) 62 | // console.log('valid0', valid0) 63 | // console.log('test.minus(vert).normalize()', test.minus(vert).normalize()) 64 | // console.log('raycast0.position.distanceTo(orgPos)', raycast0?.position.distanceTo(orgPos)) 65 | // console.log('dist', dist) 66 | 67 | if (!valid0) { 68 | return --currentIndex 69 | } 70 | } 71 | 72 | let counter = verts1.length 73 | for (const vert of verts1) { 74 | const offset = vert.minus(orgPos) 75 | const test1 = nextMove.exitPos.offset(0, orgY - nextMove.exitPos.y, 0) 76 | const test = test1.plus(offset) 77 | const dist = nextMove.exitPos.distanceTo(orgPos) 78 | const raycast0 = (await this.bot.world.raycast( 79 | vert, 80 | test.minus(vert).normalize(), 81 | dist, 82 | (block) => BlockInfo.replaceables.has(block.type) || BlockInfo.liquids.has(block.type) || block.shapes.length === 0 83 | )) as unknown as RayType | null 84 | 85 | const valid0 = (raycast0 == null) || raycast0.shapes.length > 0 || raycast0.position.distanceTo(orgPos) > dist 86 | 87 | // console.log('\n\nAIR CHECK') 88 | // console.log('offset', offset) 89 | // console.log('vert', vert) 90 | // console.log('orgPos', orgPos) 91 | // console.log('test1', test1) 92 | // console.log('test', test) 93 | // console.log('raycast0', raycast0) 94 | // console.log('valid0', valid0) 95 | // console.log('test.minus(vert).normalize()', test.minus(vert).normalize()) 96 | // console.log('raycast0.position.distanceTo(orgPos)', raycast0?.position.distanceTo(orgPos)) 97 | // console.log('dist', dist) 98 | 99 | if (!valid0) { 100 | counter-- 101 | } 102 | } 103 | 104 | if (counter === 0) return --currentIndex 105 | 106 | if (++currentIndex >= path.length) return --currentIndex 107 | lastMove = nextMove 108 | nextMove = path[currentIndex] 109 | } 110 | return --currentIndex 111 | } 112 | } 113 | 114 | export class DropDownOpt extends MovementOptimizer { 115 | // TODO: Add fall damage checks and whatnot. 116 | 117 | // TODO: Fix bugs. (e.g. if bot is on a block that is not a full block, it will not be able to drop down) 118 | readonly mergeInteracts = false 119 | 120 | identEndOpt (currentIndex: number, path: Move[]): number | Promise { 121 | // const thisMove = path[currentIndex] // starting move 122 | let lastMove = path[currentIndex] 123 | let nextMove = path[++currentIndex] 124 | 125 | if (nextMove === undefined) return --currentIndex 126 | 127 | const firstPos = lastMove.exitPos 128 | 129 | let flag0 = false 130 | let flag1 = false 131 | while (currentIndex < path.length) { 132 | if (nextMove.exitPos.y > lastMove.exitPos.y) return --currentIndex 133 | if (nextMove.toPlace.length > 0 || nextMove.toBreak.length > 0) return --currentIndex 134 | 135 | if (!AABB.fromBlockPos(nextMove.entryPos).collides(AABB.fromBlockPos(nextMove.exitPos))) return --currentIndex 136 | 137 | // rough fix. 138 | if (nextMove.exitPos.xzDistanceTo(firstPos) < lastMove.exitPos.xzDistanceTo(firstPos)) return --currentIndex 139 | 140 | const ctx = EPhysicsCtx.FROM_BOT(this.bot.physicsUtil.engine, this.bot) 141 | // ctx.position.set(firstPos.x, firstPos.y, firstPos.z); 142 | ctx.velocity.set(0, 0, 0) 143 | // ctx.position.set(lastMove.exitPos.x, lastMove.exitPos.y, lastMove.exitPos.z); 144 | ctx.position.set(lastMove.entryPos.x, lastMove.entryPos.y, lastMove.entryPos.z) 145 | // ctx.velocity.set(lastMove.entryVel.x, lastMove.entryVel.y, lastMove.entryVel.z); // 0,0,0 146 | stateLookAt(ctx.state, nextMove.entryPos) 147 | ctx.state.control = ControlStateHandler.DEFAULT() 148 | ctx.state.control.forward = true 149 | ctx.state.control.sprint = true 150 | 151 | const bl0 = lastMove.moveType.getBlockInfo(nextMove.entryPos, 0, -1, 0) 152 | const bl1 = lastMove.moveType.getBlockInfo(nextMove.exitPos, 0, -1, 0) 153 | const bb0solid = bl0.physical || bl0.liquid 154 | const bb1solid = bl1.physical || bl1.liquid 155 | const blockBB0 = AABB.fromBlockPos(nextMove.entryPos.offset(0, -1, 0)) 156 | const blockBB1 = AABB.fromBlockPos(nextMove.exitPos.offset(0, -1, 0)) 157 | let flag = false 158 | let good = false 159 | this.sim.simulateUntil( 160 | (state, ticks) => { 161 | const pBB = AABBUtils.getPlayerAABB({ position: ctx.state.pos, width: 0.6, height: 1.8 }) 162 | const collided = 163 | (pBB.collides(blockBB0) && bb0solid) || (pBB.collides(blockBB1) && bb1solid && (state.onGround || state.isInWater)) 164 | // console.log(pBB, blockBB0, bb0solid, blockBB1, bb1solid); 165 | // console.log(state.onGround, state.isCollidedHorizontally, collided, flag); 166 | if (collided) { 167 | good = true 168 | return true 169 | } 170 | 171 | if (state.pos.y < nextMove.entryPos.y && state.pos.y < nextMove.exitPos.y) flag = true 172 | 173 | // if (state.pos.y < lastMove.entryPos.y || state.pos.y < lastMove.exitPos.y) flag = true; 174 | if (flag) return (ticks > 0 && state.onGround) || state.isCollidedHorizontally 175 | else return false 176 | }, 177 | () => {}, 178 | (state) => stateLookAt(state, nextMove.exitPos), 179 | ctx, 180 | this.world, 181 | 1000 182 | ) 183 | 184 | if (!good) return --currentIndex 185 | 186 | if (ctx.state.isInWater) flag1 = true 187 | else if (flag1) return --currentIndex 188 | 189 | if (nextMove.exitPos.y === nextMove.entryPos.y) { 190 | if (!bb1solid) return --currentIndex 191 | if (flag0) return currentIndex 192 | else flag0 = true 193 | } 194 | 195 | if (++currentIndex >= path.length) return --currentIndex 196 | lastMove = nextMove 197 | nextMove = path[currentIndex] 198 | } 199 | 200 | return --currentIndex 201 | } 202 | } 203 | 204 | export class ForwardJumpUpOpt extends MovementOptimizer { 205 | // TODO: Add fall damage checks and whatnot. 206 | 207 | identEndOpt (currentIndex: number, path: Move[]): number | Promise { 208 | let lastMove = path[currentIndex] 209 | let nextMove = path[++currentIndex] 210 | 211 | if (lastMove.toPlace.length > 0) return --currentIndex 212 | 213 | if (nextMove === undefined) return --currentIndex 214 | 215 | while ( 216 | lastMove.exitPos.y === nextMove.exitPos.y && 217 | lastMove.entryPos.y !== lastMove.exitPos.y && 218 | nextMove.toPlace.length === 0 && 219 | nextMove.toBreak.length === 0 220 | ) { 221 | if (lastMove.toPlace.length > 1) return --currentIndex 222 | 223 | if (!AABB.fromBlockPos(nextMove.entryPos).collides(AABB.fromBlockPos(nextMove.exitPos))) return --currentIndex 224 | if (++currentIndex >= path.length) return --currentIndex 225 | lastMove = nextMove 226 | nextMove = path[currentIndex] 227 | } 228 | 229 | const firstPos = lastMove.exitPos 230 | 231 | while ( 232 | lastMove.exitPos.y === nextMove.exitPos.y && 233 | nextMove.exitPos.distanceTo(firstPos) <= 2 && // remove for more aggressive opt. 234 | nextMove.toPlace.length === 0 && 235 | nextMove.toBreak.length === 0 236 | ) { 237 | if (nextMove.exitPos.y > firstPos.y) return --currentIndex 238 | if (++currentIndex >= path.length) return --currentIndex 239 | lastMove = nextMove 240 | nextMove = path[currentIndex] 241 | } 242 | 243 | return --currentIndex 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/mineflayer-specific/post/replacement.ts: -------------------------------------------------------------------------------- 1 | import { Move } from '../move' 2 | import { Algorithm, Path } from '../../abstract' 3 | import { ReplacementMap } from '.' 4 | import { Bot } from 'mineflayer' 5 | import { World } from '../world/worldInterface' 6 | import { BuildableMoveProvider } from '../movements' 7 | 8 | /** 9 | * Provide an intrascture for replacing optimized moves in a path with various new moves. 10 | * 11 | * This will be A* based. 12 | */ 13 | 14 | // temp typing 15 | interface Result { 16 | referencePath: Move[] 17 | replacements: Map> 18 | context: Replacer 19 | } 20 | 21 | export interface MovementReplacement extends Algorithm { 22 | canReplace: (move: Move) => boolean 23 | initialize: (move: Move) => void 24 | compute: () => Path | null 25 | } 26 | 27 | export class Replacer { 28 | repMap: ReplacementMap 29 | 30 | private pathCopy!: Move[] 31 | private currentIndex: number 32 | 33 | constructor (bot: Bot, world: World, optMap: ReplacementMap) { 34 | this.currentIndex = 0 35 | this.repMap = optMap 36 | } 37 | 38 | loadPath (path: Move[]): void { 39 | // console.log('original path length', path.length) 40 | this.pathCopy = path 41 | this.currentIndex = 0 42 | } 43 | 44 | sanitize (): boolean { 45 | // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions 46 | return !!this.pathCopy 47 | } 48 | 49 | makeResult (this: Replacer, repRetMap: Map>): Result { 50 | return { 51 | referencePath: this.pathCopy, 52 | replacements: repRetMap, 53 | context: this 54 | } 55 | } 56 | 57 | async compute (): Promise { 58 | if (!this.sanitize()) { 59 | throw new Error('Optimizer not sanitized') 60 | } 61 | 62 | const ret = new Map>() 63 | 64 | while (this.currentIndex < this.pathCopy.length) { 65 | const move = this.pathCopy[this.currentIndex] 66 | const opt = this.repMap.get(move.moveType.constructor as BuildableMoveProvider) 67 | // console.log(opt?.constructor.name, this.currentIndex) 68 | if (opt == null) { 69 | this.currentIndex++ 70 | continue 71 | } 72 | 73 | if (!opt.canReplace(move)) { 74 | this.currentIndex++ 75 | continue 76 | } 77 | 78 | opt.initialize(move) 79 | const path = opt.compute() 80 | 81 | if (path === null) { 82 | this.currentIndex++ 83 | continue 84 | } 85 | 86 | ret.set(this.currentIndex, path) 87 | 88 | // console.log('found replacement for:', opt?.constructor.name, this.currentIndex, ': here using', path.path.length, 'moves') 89 | 90 | this.currentIndex++ 91 | } 92 | 93 | // console.log('optimized path length', this.pathCopy.length) 94 | return this.makeResult(ret) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/mineflayer-specific/post/replacements.ts: -------------------------------------------------------------------------------- 1 | import { Vec3 } from 'vec3' 2 | import { Path, MovementProvider as AMovementProvider, PathNode, Algorithm } from '../../abstract' 3 | import { Move } from '../move' 4 | import { Movement, MovementProvider } from '../movements' 5 | import { MovementReplacement } from './replacement' 6 | 7 | export class ReplacementHandler implements AMovementProvider { 8 | constructor (private readonly orgMove: Move, private readonly replacement: MovementProvider) {} 9 | 10 | static createFromSingle (move: Move, replacement: MovementProvider): ReplacementHandler { 11 | return new ReplacementHandler(move, replacement) 12 | } 13 | 14 | sanitize (): boolean { 15 | return true 16 | // throw new Error("Method not implemented."); 17 | } 18 | 19 | getNeighbors (org: Move): Move[] { 20 | throw new Error('Method not implemented.') 21 | } 22 | } 23 | 24 | export class SimpleJumpSprintReplacement extends Movement implements MovementReplacement { 25 | movementProvider!: ReplacementHandler 26 | 27 | startPosition!: Vec3 28 | endPosition!: Vec3 29 | 30 | canReplace (move: Move): boolean { 31 | const sameY = move.entryPos.y === move.exitPos.y 32 | const distance = move.entryPos.distanceTo(move.exitPos) 33 | 34 | return sameY && distance > 6 35 | } 36 | 37 | initialize (move: Move): void { 38 | this.startPosition = move.entryPos 39 | this.endPosition = move.exitPos 40 | 41 | // this.movementProvider = ReplacementHandler.createFromSingle(move, this); 42 | } 43 | 44 | compute (): Path | null { 45 | throw new Error('Method not implemented.') 46 | } 47 | 48 | makeResult (status: string, node: PathNode): Path> { 49 | return { 50 | calcTime: 0, 51 | context: this, 52 | cost: 0, 53 | generatedNodes: 0, 54 | path: [], 55 | status: 'noPath', 56 | visitedNodes: 0, 57 | movementProvider: this.movementProvider 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/mineflayer-specific/world/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minecraft-Pathfinding/minecraft-pathfinding/9089b71a194cedf322882dc575c60f7b9b456e3d/src/mineflayer-specific/world/index.ts -------------------------------------------------------------------------------- /src/mineflayer-specific/world/interactable.ts: -------------------------------------------------------------------------------- 1 | const Interactables = [ 2 | 'acacia_door', 3 | 'acacia_fence_gate', 4 | 'acacia_button', 5 | 'acacia_trapdoor', 6 | 'anvil', 7 | 'armor_stand', 8 | 'barrel', 9 | 'beacon', 10 | 'bed_block', 11 | 'bell', 12 | 'birch_boat', 13 | 'birch_button', 14 | 'birch_door', 15 | 'birch_fence_gate', 16 | 'birch_trapdoor', 17 | 'black_bed', 18 | 'black_shulker_box', 19 | 'blast_furnace', 20 | 'blue_bed', 21 | 'blue_shulker_box', 22 | 'brewing_stand', 23 | 'brown_bed', 24 | 'brown_shulker_box', 25 | 'campfire', 26 | 'cauldron', 27 | 'chest', 28 | 'chest_minecart', 29 | 'chipped_anvil', 30 | 'command', 31 | 'command_block', 32 | 'command_block_minecart', 33 | 'comparator', 34 | 'composter', 35 | 'crafting_table', 36 | 'cyan_bed', 37 | 'cyan_shulker_box', 38 | 'damaged_anvil', 39 | 'dark_oak_boat', 40 | 'dark_oak_button', 41 | 'dark_oak_fence_gate', 42 | 'dark_oak_trapdoor', 43 | 'dark_oak_door', 44 | 'daylight_detector', 45 | 'daylight_detector_inverted', 46 | 'diode', 47 | 'diode_block_off', 48 | 'diode_block_on', 49 | 'dispenser', 50 | 'door', 51 | 'dragon_egg', 52 | 'dropper', 53 | 'enchanting_table', 54 | 'enchantment_table', 55 | 'end_crystal', 56 | 'end_portal_frame', 57 | 'ender_portal_frame', 58 | 'ender_chest', 59 | 'explosive_minecart', 60 | 'farmland', 61 | 'fence_gate', 62 | 'fletching_table', 63 | 'flower_pot', 64 | 'furnace', 65 | 'furnace_minecart', 66 | 'gray_bed', 67 | 'gray_shulker_box', 68 | 'green_bed', 69 | 'green_shulker_box', 70 | 'hopper', 71 | 'hopper_minecart', 72 | 'iron_door', 73 | 'iron_trapdoor', 74 | 'item_frame', 75 | 'jukebox', 76 | 'jungle_button', 77 | 'jungle_boat', 78 | 'jungle_door', 79 | 'jungle_fence_gate', 80 | 'jungle_trapdoor', 81 | 'lever', 82 | 'light_blue_bed', 83 | 'light_blue_shulker_box', 84 | 'light_gray_bed', 85 | 'light_gray_shulker_box', 86 | 'lime_bed', 87 | 'lime_shulker_box', 88 | 'magenta_bed', 89 | 'magenta_shulker_box', 90 | 'minecart', 91 | 'note_block', 92 | 'oak_boat', 93 | 'oak_button', 94 | 'oak_door', 95 | 'oak_fence_gate', 96 | 'oak_trapdoor', 97 | 'orange_bed', 98 | 'orange_shulker_box', 99 | 'pink_bed', 100 | 'pink_shulker_box', 101 | 'powered_minecart', 102 | 'purple_bed', 103 | 'purple_shulker_box', 104 | 'red_bed', 105 | 'red_shulker_box', 106 | 'redstone_ore', 107 | 'redstone_comparator_off', 108 | 'redstone_comparator_on', 109 | 'repeating_command_block', 110 | 'repeater', 111 | 'powered_repeater', 112 | 'unpowered_repeater', 113 | 'redstone_torch', 114 | 'saddle', 115 | 'shulker_box', 116 | 'sign', 117 | 'sign_post', 118 | 'smithing_table', 119 | 'smoker', 120 | 'spruce_boat', 121 | 'spruce_button', 122 | 'spruce_door', 123 | 'spruce_fence_gate', 124 | 'stonecutter', 125 | 'stone_button', 126 | 'storage_minecart', 127 | 'tnt_minecart', 128 | 'tnt', 129 | 'trap_door', 130 | 'trapped_chest', 131 | 'white_bed', 132 | 'white_shulker_box', 133 | 'wood_button', 134 | 'yellow_bed', 135 | 'yelow_shulker_box' 136 | ] 137 | 138 | export default Interactables 139 | -------------------------------------------------------------------------------- /src/mineflayer-specific/world/utils.ts: -------------------------------------------------------------------------------- 1 | import { Vec3 } from 'vec3' 2 | import { Block } from '../../types' 3 | import { Bot } from 'mineflayer' 4 | 5 | export function fasterGetBlock (this: Bot['world'], pos: Vec3): Block { 6 | const cX = pos.x >> 4 7 | const cZ = pos.z >> 4 8 | 9 | const colKey = `${cX},${cZ}` 10 | const col = (this.async as any).columns[colKey] 11 | 12 | if (col == null) { 13 | return null as unknown as Block 14 | } 15 | const colPos = { x: pos.x & 0xf, y: pos.y, z: pos.z & 0xf } 16 | 17 | const ret1 = col.getBlock(colPos as any) 18 | ret1.position = pos 19 | return ret1 20 | } 21 | -------------------------------------------------------------------------------- /src/mineflayer-specific/world/worldInterface.ts: -------------------------------------------------------------------------------- 1 | import { Vec3 } from 'vec3' 2 | import type { BlockInfo } from './cacheWorld' 3 | import { Block } from '../../types' 4 | import { RayType } from '../movements/interactionUtils' 5 | 6 | export interface World { 7 | minY: number 8 | raycast: (from: Vec3, direction: Vec3, range: number, matcher?: (block: Block) => boolean) => RayType | null 9 | getBlock: (pos: Vec3) => Block | null 10 | getBlockInfo: (pos: Vec3) => BlockInfo 11 | getBlockStateId: (pos: Vec3) => number | undefined 12 | cleanup?: () => void 13 | } 14 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { PathfinderOptions } from './ThePathfinder' 2 | import { MovementOptions, MovementSetup } from './mineflayer-specific/movements' 3 | import { OptimizationSetup } from './mineflayer-specific/post' 4 | import { World } from './mineflayer-specific/world/worldInterface' 5 | 6 | export interface Vec3Properties { 7 | x: number 8 | y: number 9 | z: number 10 | } 11 | 12 | export interface HandlerOpts { 13 | world?: World 14 | movements?: MovementSetup 15 | optimizers?: OptimizationSetup 16 | moveSettings?: MovementOptions 17 | pathfinderSettings?: PathfinderOptions 18 | } 19 | 20 | export type PathStatus = 'noPath' | 'timeout' | 'partial' | 'success' | 'partialSuccess' | 'canceled' 21 | 22 | export type ResetReason = 'blockUpdate' | 'chunkLoad' | 'goalUpdated' 23 | 24 | export type BlockType = ReturnType 25 | export type Block = import('prismarine-block').Block 26 | 27 | export type MCData = ReturnType<(typeof import('prismarine-registry'))> 28 | 29 | export interface PlaceBlockOptions { 30 | half?: 'top' | 'bottom' 31 | delta?: Vec3Properties 32 | forceLook?: boolean | 'ignore' 33 | offhand?: boolean 34 | swingArm?: 'right' | 'left' 35 | showHand?: boolean 36 | } 37 | 38 | export interface InteractionPerformInfo { 39 | raycasts: any[] 40 | ticks: number 41 | shiftTick: number 42 | } 43 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { Bot } from 'mineflayer' 2 | import { Vec3 } from 'vec3' 3 | import { BlockInfo } from './mineflayer-specific/world/cacheWorld' 4 | 5 | export function printBotControls (bot: Bot): void { 6 | // console.log('forward', bot.getControlState('forward')) 7 | // console.log('back', bot.getControlState('back')) 8 | // console.log('left', bot.getControlState('left')) 9 | // console.log('right', bot.getControlState('right')) 10 | // console.log('jump', bot.getControlState('jump')) 11 | // console.log('sprint', bot.getControlState('sprint')) 12 | // console.log('sneak', bot.getControlState('sneak')) 13 | } 14 | 15 | export const debug = (bot: Bot | undefined, ...args: any[]): void => { 16 | if (bot != null) { 17 | bot.chat(args.join(' ')) 18 | } 19 | console.trace(...args) 20 | } 21 | 22 | export const getScaffoldCount = (bot: Bot): number => { 23 | if (!BlockInfo.initialized) throw new Error('BlockInfo not initialized') 24 | const amt = bot.inventory.items().reduce((acc, item) => (BlockInfo.scaffoldingBlockItems.has(item.type) ? item.count + acc : acc), 0) 25 | if (bot.game.gameMode === 'creative') { 26 | return amt > 0 ? Infinity : 0 27 | } 28 | return amt 29 | } 30 | 31 | /** 32 | * Gen here, this code is alright. 33 | * Taken from: https://github.com/PrismarineJS/mineflayer-pathfinder/blob/d69a02904bc83f4c36598ae90d470a009a130105/index.js#L285 34 | */ 35 | export function closestPointOnLineSegment (point: Vec3, segmentStart: Vec3, segmentEnd: Vec3): Vec3 { 36 | const segmentLength = segmentEnd.minus(segmentStart).norm() 37 | 38 | if (segmentLength === 0) { 39 | return segmentStart 40 | } 41 | 42 | // given the start and end segment of a line that is of arbitrary length, 43 | // identify the closest point on the line to the given point. 44 | 45 | const t = point.minus(segmentStart).dot(segmentEnd.minus(segmentStart)) / segmentLength ** 2 46 | 47 | if (t < 0) { 48 | return segmentStart 49 | } 50 | 51 | if (t > 1) { 52 | return segmentEnd 53 | } 54 | 55 | return segmentStart.plus(segmentEnd.minus(segmentStart).scaled(t)) 56 | } 57 | 58 | export function getNormalizedPos (bot: Bot, startPos?: Vec3): Vec3 { 59 | if (!BlockInfo.initialized) throw new Error('BlockInfo not initialized') 60 | // check if we are on carpet 61 | const pos = startPos ?? bot.entity.position.clone() 62 | 63 | const block = bot.pathfinder.world.getBlockInfo(pos) 64 | if (BlockInfo.carpets.has(block.type)) { 65 | return pos.floor() 66 | } 67 | 68 | return pos 69 | } 70 | 71 | export async function onceWithCleanup ( 72 | emitter: NodeJS.EventEmitter, 73 | event: string, 74 | options: { timeout?: number, checkCondition?: (data?: T) => boolean } = {} 75 | ): Promise { 76 | return await new Promise((resolve, reject) => { 77 | const timeout = options.timeout ?? 10000 78 | 79 | let checkCondition: (data?: T) => boolean 80 | if (options.checkCondition != null) checkCondition = options.checkCondition 81 | else checkCondition = () => true 82 | 83 | const timeoutId = setTimeout(() => { 84 | emitter.removeListener(event, listener) 85 | reject(new Error(`Timeout waiting for ${event}`)) 86 | }, timeout) 87 | const listener = (data: T): void => { 88 | if (checkCondition(data)) { 89 | clearTimeout(timeoutId) 90 | emitter.removeListener(event, listener) 91 | resolve(data) 92 | } 93 | } 94 | emitter.on(event, listener) 95 | }) 96 | } 97 | 98 | export class Task { 99 | done: boolean = false 100 | canceled: boolean = false 101 | promise: Promise 102 | cancel!: (err: Rej) => void 103 | finish!: (result: Res) => void 104 | 105 | constructor () { 106 | this.promise = new Promise((resolve, reject) => { 107 | this.cancel = (err) => { 108 | if (!this.done) { 109 | this.done = true 110 | this.canceled = true 111 | reject(err) 112 | // throw err; 113 | } 114 | } 115 | this.finish = (result) => { 116 | if (!this.done) { 117 | this.done = true 118 | resolve(result) 119 | // return result; 120 | } 121 | } 122 | }) 123 | } 124 | 125 | static doneTask(): Task { 126 | const task = new Task() 127 | task.done = true 128 | task.promise = Promise.resolve() 129 | task.cancel = () => {} 130 | task.finish = () => {} 131 | return task 132 | } 133 | } 134 | 135 | export function getViewDir (info: { yaw: number, pitch: number }): Vec3 { 136 | return new Vec3(-Math.sin(info.yaw) * Math.cos(info.pitch), Math.sin(info.pitch), -Math.cos(info.yaw) * Math.cos(info.pitch)) 137 | } 138 | 139 | // (async () => { 140 | // const task0 = new Task(); 141 | // const task1 = new Task(); 142 | // const task2 = new Task(); 143 | // const task3 = new Task(); 144 | 145 | // const data = task0.promise 146 | // task0.finish(1); 147 | 148 | // console.log(await data) 149 | 150 | // })() 151 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "CommonJS", 4 | // "moduleResolution": "", 5 | "noImplicitAny": true, 6 | "removeComments": true, 7 | "preserveConstEnums": true, 8 | "sourceMap": true, 9 | "strict": true, 10 | "outDir": "dist", 11 | "skipLibCheck": true, 12 | "target": "ES2015", 13 | "declaration": true, 14 | "resolveJsonModule": true, 15 | "esModuleInterop": true, 16 | }, 17 | "include": ["src/**/*"], 18 | "exclude": ["node_modules", "examples"], 19 | "compileOnSave": true, 20 | } 21 | --------------------------------------------------------------------------------