├── .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 |
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 |
--------------------------------------------------------------------------------