Botcraft 1.21.4
Loading...
Searching...
No Matches
PathfindingTask.cpp
Go to the documentation of this file.
12
13namespace Botcraft
14{
16 {
17 public:
19 PathfindingBlockstate(const Blockstate* block, const Position& pos, const bool take_damage) : block(block)
20 {
21 height = pos.y;
22 if (block == nullptr)
23 {
24 empty = true;
25 return;
26 }
27 empty = false;
28
29 if (take_damage && block->IsHazardous())
30 {
31 hazardous = true;
32 return;
33 }
34
36 {
37 climbable = true;
38 fluid = true;
39 return;
40 }
41
42 if (block->IsClimbable())
43 {
44 climbable = true;
45 return;
46 }
47
48 if (block->IsSolid())
49 {
50 solid = true;
51 const auto& colliders = block->GetCollidersAtPos(pos);
52 for (const auto& c : colliders)
53 {
54 height = std::max(static_cast<float>(c.GetMax().y), height);
55 }
56 }
57 else
58 {
59 empty = true;
60 }
61 }
62
63 const Blockstate* GetBlockstate() const { return block; }
64 bool IsEmpty() const { return empty; }
65 bool IsSolid() const { return solid; }
66 bool IsHazardous() const { return hazardous; }
67 bool IsClimbable() const { return climbable; }
68 bool IsFluid() const { return fluid; }
69 float GetHeight() const { return height; }
70
71 private:
72 const Blockstate* block = nullptr;
73 bool empty = true;
74 bool solid = false;
75 bool hazardous = false;
76 bool climbable = false;
77 bool fluid = false;
78 float height = 0.0f;
79 };
80
82 {
83 public:
84 size_t operator()(const std::pair<Position, float>& p) const
85 {
86 size_t value = std::hash<Position>()(p.first);
87 value ^= std::hash<float>()(p.second) + 0x9e3779b9 + (value << 6) + (value >> 2);
88 return value;
89 }
90 };
91
92 std::vector<std::pair<Position, float>> FindPath(const BehaviourClient& client, const Position& start, const Position& end, const int dist_tolerance, const int min_end_dist, const int min_end_dist_xz, const bool allow_jump)
93 {
94 struct PathNode
95 {
96 std::pair<Position, float> pos; // <Block in which the feet are, feet height>
97 float score; // distance from start + heuristic to goal
98
99 PathNode(const std::pair<Position, float>& p, const float s)
100 {
101 pos = p;
102 score = s;
103 }
104
105 bool operator>(const PathNode& rhs) const
106 {
107 return score > rhs.score;
108 }
109
110 static float Heuristic(const Position& a, const Position& b)
111 {
112 return static_cast<float>(std::abs(a.x - b.x) + std::abs(a.y - b.y) + std::abs(a.z - b.z));
113 }
114 };
115
116 constexpr int budget_visit = 15000;
117
118 const std::array<Position, 4> neighbour_offsets = { Position(1, 0, 0), Position(-1, 0, 0), Position(0, 0, 1), Position(0, 0, -1) };
119 std::priority_queue<PathNode, std::vector<PathNode>, std::greater<PathNode> > nodes_to_explore;
120 std::unordered_map<std::pair<Position, float>, std::pair<Position, float>, PosFloatPairHash> came_from;
121 std::unordered_map<std::pair<Position, float>, float, PosFloatPairHash> cost;
122
123 const bool takes_damage = !client.GetLocalPlayer()->GetInvulnerable();
124 std::shared_ptr<World> world = client.GetWorld();
125 const Blockstate* block = world->GetBlock(start);
126 nodes_to_explore.emplace(PathNode({ start, PathfindingBlockstate(block, start, takes_damage).GetHeight() }, 0.0f));
127 came_from[nodes_to_explore.top().pos] = nodes_to_explore.top().pos;
128 cost[nodes_to_explore.top().pos] = 0.0f;
129
130 int count_visit = 0;
131 // We found one location matching all the criterion, but
132 // continue the search to see if we can find a better one
133 bool suitable_location_found = false;
134 // We found a path to the desired goal
135 bool end_reached = false;
136
137 bool end_is_inside_solid = false;
138 block = world->GetBlock(end);
139 end_is_inside_solid = block != nullptr && block->IsSolid();
140
141 while (!nodes_to_explore.empty())
142 {
143 count_visit++;
144 PathNode current_node = nodes_to_explore.top();
145 nodes_to_explore.pop();
146
147 end_reached |= current_node.pos.first == end;
148 suitable_location_found |=
149 std::abs(end.x - current_node.pos.first.x) + std::abs(end.y - current_node.pos.first.y) + std::abs(end.z - current_node.pos.first.z) <= dist_tolerance &&
150 std::abs(end.x - current_node.pos.first.x) + std::abs(end.y - current_node.pos.first.y) + std::abs(end.z - current_node.pos.first.z) >= min_end_dist &&
151 std::abs(end.x - current_node.pos.first.x) + std::abs(end.z - current_node.pos.first.z) >= min_end_dist_xz;
152
153 if (// If we exceeded the search budget
154 count_visit > budget_visit ||
155 // Or if we found a suitable location in the process and already reached the goal/can't reach it anyway
156 (suitable_location_found && (end_reached || end_is_inside_solid)))
157 {
158 break;
159 }
160
161 // Get the state around the player in the given location
162 std::array<PathfindingBlockstate, 6> vertical_surroundings;
163
164 // Assuming the player is standing on 3 (feeet on 2 and head on 1)
165 // 0
166 // 1
167 // 2
168 // 3
169 // 4
170 // 5
171 Position pos = current_node.pos.first + Position(0, 2, 0);
172 block = world->GetBlock(pos);
173 vertical_surroundings[0] = PathfindingBlockstate(block, pos, takes_damage);
174 pos = current_node.pos.first + Position(0, 1, 0);
175 block = world->GetBlock(pos);
176 vertical_surroundings[1] = PathfindingBlockstate(block, pos, takes_damage);
177 // Current feet block
178 pos = current_node.pos.first;
179 block = world->GetBlock(pos);
180 vertical_surroundings[2] = PathfindingBlockstate(block, pos, takes_damage);
181
182 // if 2 is solid or hazardous, no down pathfinding is possible,
183 // so we can skip a few checks
184 if (!vertical_surroundings[2].IsSolid() && !vertical_surroundings[2].IsHazardous())
185 {
186 // if 3 is solid or hazardous, no down pathfinding is possible,
187 // so we can skip a few checks
188 pos = current_node.pos.first + Position(0, -1, 0);
189 block = world->GetBlock(pos);
190 vertical_surroundings[3] = PathfindingBlockstate(block, pos, takes_damage);
191
192 // If we can move down, we need 4 and 5
193 if (!vertical_surroundings[3].IsSolid() && !vertical_surroundings[3].IsHazardous())
194 {
195 pos = current_node.pos.first + Position(0, -2, 0);
196 block = world->GetBlock(pos);
197 vertical_surroundings[4] = PathfindingBlockstate(block, pos, takes_damage);
198 pos = current_node.pos.first + Position(0, -3, 0);
199 block = world->GetBlock(pos);
200 vertical_surroundings[5] = PathfindingBlockstate(block, pos, takes_damage);
201 }
202 }
203
204
205 // Check all vertical cases that would allow the bot to pass
206 // -
207 // x
208 // ^
209 // ?
210 // ?
211 // ?
212 if (vertical_surroundings[2].IsClimbable()
213 && !vertical_surroundings[1].IsSolid()
214 && !vertical_surroundings[1].IsHazardous()
215 && !vertical_surroundings[0].IsSolid()
216 && !vertical_surroundings[0].IsHazardous()
217 )
218 {
219 const float new_cost = cost[current_node.pos] + 1.0f;
220 const std::pair<Position, float> new_pos = {
221 current_node.pos.first + Position(0, 1, 0),
222 current_node.pos.first.y + 1.0f
223 };
224 auto it = cost.find(new_pos);
225 // If we don't already know this node with a better path, add it
226 if (it == cost.end() ||
227 new_cost < it->second)
228 {
229 cost[new_pos] = new_cost;
230 nodes_to_explore.emplace(PathNode(
231 new_pos,
232 new_cost + PathNode::Heuristic(new_pos.first, end))
233 );
234 came_from[new_pos] = current_node.pos;
235 }
236 }
237
238 // -
239 // ^
240 // x
241 // o
242 // ?
243 // ?
244 if (vertical_surroundings[1].IsClimbable()
245 && !vertical_surroundings[0].IsSolid()
246 && !vertical_surroundings[0].IsHazardous()
247 && (vertical_surroundings[2].IsSolid() || // we stand on top of 2
248 (!vertical_surroundings[2].IsClimbable() && vertical_surroundings[3].IsSolid()) // if not, it means we stand on 3. Height difference check is not necessary, as the feet are in 2, we know 3 is at least 1 tall
249 )
250 )
251 {
252 const float new_cost = cost[current_node.pos] + 1.5f;
253 const std::pair<Position, float> new_pos = {
254 current_node.pos.first + Position(0, 1, 0),
255 current_node.pos.first.y + 1.0f
256 };
257 auto it = cost.find(new_pos);
258 // If we don't already know this node with a better path, add it
259 if (it == cost.end() ||
260 new_cost < it->second)
261 {
262 cost[new_pos] = new_cost;
263 nodes_to_explore.emplace(PathNode(
264 new_pos,
265 new_cost + PathNode::Heuristic(new_pos.first, end))
266 );
267 came_from[new_pos] = current_node.pos;
268 }
269 }
270
271 // ?
272 // x
273 //
274 // -
275 // ?
276 // ?
277 if (!vertical_surroundings[2].IsSolid() &&
278 vertical_surroundings[3].IsClimbable()
279 )
280 {
281 const float new_cost = cost[current_node.pos] + 1.0f;
282 const std::pair<Position, float> new_pos = {
283 current_node.pos.first + Position(0, -1, 0),
284 current_node.pos.first.y - 1.0f
285 };
286 auto it = cost.find(new_pos);
287 // If we don't already know this node with a better path, add it
288 if (it == cost.end() ||
289 new_cost < it->second)
290 {
291 cost[new_pos] = new_cost;
292 nodes_to_explore.emplace(PathNode(new_pos, new_cost + PathNode::Heuristic(new_pos.first, end)));
293 came_from[new_pos] = current_node.pos;
294 }
295 }
296
297 // ?
298 // x
299 //
300 // -
301 //
302 // o
303 if (!vertical_surroundings[2].IsSolid() &&
304 vertical_surroundings[3].IsClimbable()
305 && vertical_surroundings[4].IsEmpty()
306 && !vertical_surroundings[5].IsEmpty()
307 && !vertical_surroundings[5].IsHazardous()
308 )
309 {
310 const bool above_block = vertical_surroundings[5].IsClimbable() || vertical_surroundings[5].GetHeight() + 1e-3f > current_node.pos.first.y - 2;
311 const float new_cost = cost[current_node.pos] + 3.0f - 1.0f * above_block;
312 const std::pair<Position, float> new_pos = {
313 current_node.pos.first + Position(0, -3 + 1 * above_block, 0),
314 above_block ? std::max(current_node.pos.first.y - 2.0f, vertical_surroundings[5].GetHeight()) : vertical_surroundings[5].GetHeight()
315 };
316 auto it = cost.find(new_pos);
317 // If we don't already know this node with a better path, add it
318 if (it == cost.end() ||
319 new_cost < it->second)
320 {
321 cost[new_pos] = new_cost;
322 nodes_to_explore.emplace(PathNode(
323 new_pos,
324 new_cost + PathNode::Heuristic(new_pos.first, end))
325 );
326 came_from[new_pos] = current_node.pos;
327 }
328 }
329
330
331
332 // ?
333 // x
334 // ^
335 //
336 //
337 // o
338 if (vertical_surroundings[2].IsClimbable()
339 && vertical_surroundings[3].IsEmpty()
340 && vertical_surroundings[4].IsEmpty()
341 && !vertical_surroundings[5].IsEmpty()
342 && !vertical_surroundings[5].IsHazardous()
343 )
344 {
345 const bool above_block = vertical_surroundings[5].IsClimbable() || vertical_surroundings[5].GetHeight() + 1e-3f > current_node.pos.first.y - 2;
346 const float new_cost = cost[current_node.pos] + 3.0f - 1.0f * above_block;
347 const std::pair<Position, float> new_pos = {
348 current_node.pos.first + Position(0, -3 + 1 * above_block, 0),
349 above_block ? std::max(current_node.pos.first.y - 2.0f, vertical_surroundings[5].GetHeight()) : vertical_surroundings[5].GetHeight()
350 };
351 auto it = cost.find(new_pos);
352 // If we don't already know this node with a better path, add it
353 if (it == cost.end() ||
354 new_cost < it->second)
355 {
356 cost[new_pos] = new_cost;
357 nodes_to_explore.emplace(PathNode(
358 new_pos,
359 new_cost + PathNode::Heuristic(new_pos.first, end))
360 );
361 came_from[new_pos] = current_node.pos;
362 }
363 }
364
365
366 // ?
367 // x
368 //
369 // -
370 //
371 //
372 // Special case here, we can drop down
373 // if there is a climbable at the bottom
374 if (!vertical_surroundings[2].IsSolid() &&
375 vertical_surroundings[3].IsClimbable()
376 && vertical_surroundings[4].IsEmpty()
377 && vertical_surroundings[5].IsEmpty()
378 )
379 {
380 for (int y = -4; current_node.pos.first.y + y >= world->GetMinY(); --y)
381 {
382 pos = current_node.pos.first + Position(0, y, 0);
383 block = world->GetBlock(pos);
384
385 if (block != nullptr && block->IsSolid() && !block->IsClimbable())
386 {
387 break;
388 }
389
390 const PathfindingBlockstate landing_block(block, pos, takes_damage);
391 if (landing_block.IsClimbable())
392 {
393 const float new_cost = cost[current_node.pos] + std::abs(y);
394 const std::pair<Position, float> new_pos = {
395 current_node.pos.first + Position(0, y + 1, 0),
396 current_node.pos.first.y + y + 1.0f
397 };
398 auto it = cost.find(new_pos);
399 // If we don't already know this node with a better path, add it
400 if (it == cost.end() ||
401 new_cost < it->second)
402 {
403 cost[new_pos] = new_cost;
404 nodes_to_explore.emplace(PathNode(
405 new_pos,
406 new_cost + PathNode::Heuristic(new_pos.first, end))
407 );
408 came_from[new_pos] = current_node.pos;
409 }
410
411 break;
412 }
413 }
414 }
415
416
417 // For each neighbour, check if it's reachable
418 // and add it to the search list if it is
419 for (int i = 0; i < neighbour_offsets.size(); ++i)
420 {
421 const Position next_location = current_node.pos.first + neighbour_offsets[i];
422 const Position next_next_location = next_location + neighbour_offsets[i];
423
424 // Get the state around the player in the given direction
425 std::array<PathfindingBlockstate, 12> horizontal_surroundings;
426
427 // Assuming the player is standing on v3 (feeet on v2 and head on v1)
428 // v0 0 6 --> ? ? ?
429 // v1 1 7 --> x ? ?
430 // v2 2 8 --> x ? ?
431 // v3 3 9 --> ? ? ?
432 // v4 4 10 --> ? ? ?
433 // v5 5 11 --> ? ? ?
434
435 // if 1 is solid and tall, no horizontal pathfinding is possible,
436 // so we can skip a lot of checks
437 pos = next_location + Position(0, 2, 0);
438 block = world->GetBlock(pos);
439 horizontal_surroundings[0] = PathfindingBlockstate(block, pos, takes_damage);
440 pos = next_location + Position(0, 1, 0);
441 block = world->GetBlock(pos);
442 horizontal_surroundings[1] = PathfindingBlockstate(block, pos, takes_damage);
443 const bool horizontal_movement =
444 (!horizontal_surroundings[1].IsSolid() || // 1 is not solid
445 (horizontal_surroundings[1].GetHeight() - current_node.pos.second < 1.25f && // or 1 is solid and small
446 !horizontal_surroundings[0].IsSolid() && !horizontal_surroundings[0].IsHazardous()) // and 0 does not prevent standing
447 ) && !horizontal_surroundings[1].IsHazardous();
448
449 // If we can move horizontally, get the full column
450 if (horizontal_movement)
451 {
452 pos = next_location;
453 block = world->GetBlock(pos);
454 horizontal_surroundings[2] = PathfindingBlockstate(block, pos, takes_damage);
455 pos = next_location + Position(0, -1, 0);
456 block = world->GetBlock(pos);
457 horizontal_surroundings[3] = PathfindingBlockstate(block, pos, takes_damage);
458 pos = next_location + Position(0, -2, 0);
459 block = world->GetBlock(pos);
460 horizontal_surroundings[4] = PathfindingBlockstate(block, pos, takes_damage);
461 pos = next_location + Position(0, -3, 0);
462 block = world->GetBlock(pos);
463 horizontal_surroundings[5] = PathfindingBlockstate(block, pos, takes_damage);
464 }
465
466 // You can't make large jumps if your feet are in a climbable block
467 // If we can jump, then we need the third column
468 if (allow_jump && !vertical_surroundings[2].IsClimbable())
469 {
470 pos = next_next_location + Position(0, 2, 0);
471 block = world->GetBlock(pos);
472 horizontal_surroundings[6] = PathfindingBlockstate(block, pos, takes_damage);
473 pos = next_next_location + Position(0, 1, 0);
474 block = world->GetBlock(pos);
475 horizontal_surroundings[7] = PathfindingBlockstate(block, pos, takes_damage);
476 pos = next_next_location;
477 block = world->GetBlock(pos);
478 horizontal_surroundings[8] = PathfindingBlockstate(block, pos, takes_damage);
479 pos = next_next_location + Position(0, -1, 0);
480 block = world->GetBlock(pos);
481 horizontal_surroundings[9] = PathfindingBlockstate(block, pos, takes_damage);
482 pos = next_next_location + Position(0, -2, 0);
483 block = world->GetBlock(pos);
484 horizontal_surroundings[10] = PathfindingBlockstate(block, pos, takes_damage);
485 pos = next_next_location + Position(0, -3, 0);
486 block = world->GetBlock(pos);
487 horizontal_surroundings[11] = PathfindingBlockstate(block, pos, takes_damage);
488 }
489
490 // Now that we know the surroundings, we can check all
491 // horizontal cases that would allow the bot to pass
492
493 /************ HORIZONTAL **************/
494
495 // ? ? ?
496 // x - ?
497 // x - ?
498 //--- o ?
499 // ? ?
500 // ? ?
501 if (!horizontal_surroundings[1].IsSolid()
502 && !horizontal_surroundings[1].IsHazardous()
503 && !horizontal_surroundings[2].IsSolid()
504 && !horizontal_surroundings[2].IsHazardous()
505 && !horizontal_surroundings[3].IsEmpty()
506 && !horizontal_surroundings[3].IsHazardous()
507 && (!horizontal_surroundings[3].IsFluid() // We can't go from above a fluid to above
508 || !vertical_surroundings[3].IsFluid() // another one to avoid "walking on water"
509 || horizontal_surroundings[2].IsFluid() // except if one or both "leg level" blocks
510 || vertical_surroundings[2].IsFluid()) // are also fluids
511 )
512 {
513 const bool above_block = horizontal_surroundings[2].IsClimbable() || horizontal_surroundings[3].IsClimbable() || horizontal_surroundings[3].GetHeight() + 1e-3f > current_node.pos.first.y;
514 const float new_cost = cost[current_node.pos] + 2.0f - 1.0f * above_block;
515 const std::pair<Position, float> new_pos = {
516 next_location + Position(0, 1 - 1 * above_block, 0),
517 above_block ? std::max(static_cast<float>(next_location.y), horizontal_surroundings[3].GetHeight()) : std::max(horizontal_surroundings[3].GetHeight(), horizontal_surroundings[4].GetHeight())
518 };
519 auto it = cost.find(new_pos);
520 // If we don't already know this node with a better path, add it
521 if (it == cost.end() ||
522 new_cost < it->second)
523 {
524 cost[new_pos] = new_cost;
525 nodes_to_explore.emplace(PathNode(
526 new_pos,
527 new_cost + PathNode::Heuristic(new_pos.first, end))
528 );
529 came_from[new_pos] = current_node.pos;
530 }
531 }
532
533
534 // - - ?
535 // x o ?
536 // x ? ?
537 //--- ? ?
538 // ? ?
539 // ? ?
540 if (!vertical_surroundings[0].IsSolid()
541 && !vertical_surroundings[0].IsHazardous()
542 && vertical_surroundings[1].IsEmpty()
543 && !vertical_surroundings[2].IsClimbable()
544 && (vertical_surroundings[2].IsSolid() || !vertical_surroundings[3].IsClimbable())
545 && !horizontal_surroundings[0].IsSolid()
546 && !horizontal_surroundings[0].IsHazardous()
547 && !horizontal_surroundings[1].IsEmpty()
548 && !horizontal_surroundings[1].IsHazardous()
549 && horizontal_surroundings[1].GetHeight() - current_node.pos.second < 1.25f
550 )
551 {
552 const float new_cost = cost[current_node.pos] + 2.5f;
553 const std::pair<Position, float> new_pos = {
554 next_location + Position(0, 1, 0),
555 std::max(horizontal_surroundings[1].GetHeight(), horizontal_surroundings[2].GetHeight()) // for the carpet on wall trick
556 };
557 auto it = cost.find(new_pos);
558 // If we don't already know this node with a better path, add it
559 if (it == cost.end() ||
560 new_cost < it->second)
561 {
562 cost[new_pos] = new_cost;
563 nodes_to_explore.emplace(PathNode(
564 new_pos,
565 new_cost + PathNode::Heuristic(new_pos.first, end))
566 );
567 came_from[new_pos] = current_node.pos;
568 }
569 }
570
571 // - - ?
572 // x - ?
573 // x o ?
574 //--- ? ?
575 // ? ?
576 // ? ?
577 if (!vertical_surroundings[0].IsSolid()
578 && !vertical_surroundings[0].IsHazardous()
579 && vertical_surroundings[1].IsEmpty()
580 && (vertical_surroundings[2].IsSolid() || (vertical_surroundings[2].IsEmpty() && vertical_surroundings[3].IsSolid()))
581 && !horizontal_surroundings[0].IsSolid()
582 && !horizontal_surroundings[0].IsHazardous()
583 && !horizontal_surroundings[1].IsSolid()
584 && !horizontal_surroundings[1].IsHazardous()
585 && !horizontal_surroundings[2].IsEmpty()
586 && !horizontal_surroundings[2].IsHazardous()
587 && horizontal_surroundings[2].GetHeight() - current_node.pos.second < 1.25f
588 )
589 {
590 const bool above_block = horizontal_surroundings[1].IsClimbable() || horizontal_surroundings[2].IsClimbable() || horizontal_surroundings[2].GetHeight() + 1e-3f > current_node.pos.first.y + 1;
591 const float new_cost = cost[current_node.pos] + 1.0f + 1.0f * above_block + 0.5f * (horizontal_surroundings[1].IsClimbable() || horizontal_surroundings[2].GetHeight() - vertical_surroundings[2].GetHeight() > 0.5);
592 const std::pair<Position, float> new_pos = {
593 next_location + Position(0, 1 * above_block, 0),
594 above_block ? std::max(current_node.pos.first.y + 1.0f, horizontal_surroundings[2].GetHeight()) : std::max(horizontal_surroundings[2].GetHeight(), horizontal_surroundings[3].GetHeight())
595 };
596 auto it = cost.find(new_pos);
597 // If we don't already know this node with a better path, add it
598 if (it == cost.end() ||
599 new_cost < it->second)
600 {
601 cost[new_pos] = new_cost;
602 nodes_to_explore.emplace(PathNode(
603 new_pos,
604 new_cost + PathNode::Heuristic(new_pos.first, end))
605 );
606 came_from[new_pos] = current_node.pos;
607 }
608 }
609
610 // ? ? ?
611 // x - ?
612 // x ?
613 //--- ?
614 // o ?
615 // ? ?
616 if (!horizontal_surroundings[1].IsSolid()
617 && !horizontal_surroundings[1].IsHazardous()
618 && horizontal_surroundings[2].IsEmpty()
619 && horizontal_surroundings[3].IsEmpty()
620 && !horizontal_surroundings[4].IsEmpty()
621 && !horizontal_surroundings[4].IsHazardous()
622 )
623 {
624 const bool above_block = horizontal_surroundings[4].IsClimbable() || horizontal_surroundings[4].GetHeight() + 1e-3f > current_node.pos.first.y - 1;
625 const float new_cost = cost[current_node.pos] + 3.5f - 1.0f * above_block;
626 const std::pair<Position, float> new_pos = {
627 next_location + Position(0, -2 + 1 * above_block, 0),
628 above_block ? std::max(current_node.pos.first.y - 1.0f, horizontal_surroundings[4].GetHeight()) : std::max(horizontal_surroundings[4].GetHeight(), horizontal_surroundings[5].GetHeight())
629 };
630 auto it = cost.find(new_pos);
631 // If we don't already know this node with a better path, add it
632 if (it == cost.end() ||
633 new_cost < it->second)
634 {
635 cost[new_pos] = new_cost;
636 nodes_to_explore.emplace(PathNode(
637 new_pos,
638 new_cost + PathNode::Heuristic(new_pos.first, end))
639 );
640 came_from[new_pos] = current_node.pos;
641 }
642 }
643
644 // ? ? ?
645 // x - ?
646 // x ?
647 //--- ?
648 // ?
649 // o ?
650 if (!horizontal_surroundings[1].IsSolid()
651 && !horizontal_surroundings[1].IsHazardous()
652 && horizontal_surroundings[2].IsEmpty()
653 && horizontal_surroundings[3].IsEmpty()
654 && horizontal_surroundings[4].IsEmpty()
655 && !horizontal_surroundings[5].IsEmpty()
656 && !horizontal_surroundings[5].IsHazardous()
657 )
658 {
659 const bool above_block = horizontal_surroundings[5].IsClimbable() || horizontal_surroundings[5].GetHeight() + 1e-3f > current_node.pos.first.y - 2;
660 const float new_cost = cost[current_node.pos] + 4.5f - 1.0f * above_block;
661 const std::pair<Position, float> new_pos = {
662 next_location + Position(0, -3 + 1 * above_block, 0),
663 above_block ? std::max(current_node.pos.first.y - 2.0f, horizontal_surroundings[5].GetHeight()) : horizontal_surroundings[5].GetHeight() // no carpet on wall check here as we don't have the block below
664 };
665 auto it = cost.find(new_pos);
666 // If we don't already know this node with a better path, add it
667 if (it == cost.end() ||
668 new_cost < it->second)
669 {
670 cost[new_pos] = new_cost;
671 nodes_to_explore.emplace(PathNode(
672 new_pos,
673 new_cost + PathNode::Heuristic(new_pos.first, end))
674 );
675 came_from[new_pos] = current_node.pos;
676 }
677 }
678
679 // ? ? ?
680 // x - ?
681 // x ?
682 //--- ?
683 // ?
684 // ?
685 // Special case here, we can drop down
686 // if there is a climbable at the bottom
687 if (!horizontal_surroundings[1].IsSolid()
688 && !horizontal_surroundings[1].IsHazardous()
689 && horizontal_surroundings[2].IsEmpty()
690 && horizontal_surroundings[3].IsEmpty()
691 && horizontal_surroundings[4].IsEmpty()
692 && horizontal_surroundings[5].IsEmpty()
693 )
694 {
695 for (int y = -4; next_location.y + y >= world->GetMinY(); --y)
696 {
697 pos = next_location + Position(0, y, 0);
698 block = world->GetBlock(pos);
699
700 if (block != nullptr && block->IsSolid() && !block->IsClimbable())
701 {
702 break;
703 }
704
705 const PathfindingBlockstate landing_block(block, pos, takes_damage);
706 if (landing_block.IsClimbable())
707 {
708 const float new_cost = cost[current_node.pos] + std::abs(y) + 1.5f;
709 const std::pair<Position, float> new_pos = {
710 next_location + Position(0, y + 1, 0),
711 next_location.y + y + 1.0f
712 };
713 auto it = cost.find(new_pos);
714 // If we don't already know this node with a better path, add it
715 if (it == cost.end() ||
716 new_cost < it->second)
717 {
718 cost[new_pos] = new_cost;
719 nodes_to_explore.emplace(PathNode(
720 new_pos,
721 new_cost + PathNode::Heuristic(new_pos.first, end))
722 );
723 came_from[new_pos] = current_node.pos;
724 }
725
726 break;
727 }
728 }
729 }
730
731 // If we can't make jumps, don't bother explore the rest
732 // of the cases
733 if (!allow_jump
734 || vertical_surroundings[0].IsSolid() // Block above
735 || vertical_surroundings[0].IsHazardous() // Block above
736 || !vertical_surroundings[1].IsEmpty() // Block above
737 || vertical_surroundings[2].IsClimbable() // Feet inside climbable
738 || vertical_surroundings[3].IsFluid() // "Walking" on fluid
739 || vertical_surroundings[3].IsEmpty() // Feet on nothing (inside climbable)
740 || horizontal_surroundings[0].IsSolid() // Block above next column
741 || horizontal_surroundings[0].IsHazardous() // Hazard above next column
742 || !horizontal_surroundings[1].IsEmpty() // Non empty block in next column, can't jump through it
743 || !horizontal_surroundings[2].IsEmpty() // Non empty block in next column, can't jump through it
744 || horizontal_surroundings[6].IsSolid() // Block above nextnext column
745 || horizontal_surroundings[6].IsHazardous() // Hazard above nextnext column
746 )
747 {
748 continue;
749 }
750
751 /************ BIG JUMP **************/
752 // - - -
753 // x o
754 // x ?
755 //--- ? ?
756 // ? ?
757 // ? ?
758 if (!horizontal_surroundings[7].IsEmpty()
759 && !horizontal_surroundings[7].IsHazardous()
760 && horizontal_surroundings[7].GetHeight() - current_node.pos.second < 1.25f
761 )
762 {
763 // 5 > 4.5 as if horizontal_surroundings[3] is solid we prefer to walk then jump instead of big jump
764 // but if horizontal_surroundings[3] is hazardous we can jump over it
765 const float new_cost = cost[current_node.pos] + 5.0f;
766 const std::pair<Position, float> new_pos = {
767 next_next_location + Position(0, 1, 0),
768 std::max(horizontal_surroundings[7].GetHeight(), horizontal_surroundings[8].GetHeight()), // for the carpet on wall trick
769 };
770 auto it = cost.find(new_pos);
771 // If we don't already know this node with a better path, add it
772 if (it == cost.end() ||
773 new_cost < it->second)
774 {
775 cost[new_pos] = new_cost;
776 nodes_to_explore.emplace(PathNode(
777 new_pos,
778 new_cost + PathNode::Heuristic(new_pos.first, end))
779 );
780 came_from[new_pos] = current_node.pos;
781 }
782 }
783
784 // - - -
785 // x
786 // x o
787 //--- ? ?
788 // ? ?
789 // ? ?
790 if (horizontal_surroundings[7].IsEmpty()
791 && !horizontal_surroundings[8].IsEmpty()
792 && !horizontal_surroundings[8].IsHazardous()
793 && horizontal_surroundings[8].GetHeight() - current_node.pos.second < 1.25f
794 )
795 {
796 const bool above_block = horizontal_surroundings[8].IsClimbable() || horizontal_surroundings[8].GetHeight() + 1e-3f > current_node.pos.first.y + 1;
797 // 4 > 3.5 as if horizontal_surroundings[3] is solid we prefer to walk then jump instead of big jump
798 // but if horizontal_surroundings[3] is hazardous we can jump over it
799 const float new_cost = cost[current_node.pos] + 3.0f + 1.0f * above_block;
800 const std::pair<Position, float> new_pos = {
801 next_next_location + Position(0, above_block * 1, 0),
802 above_block ? std::max(current_node.pos.first.y + 1.0f, horizontal_surroundings[8].GetHeight()) : std::max(horizontal_surroundings[8].GetHeight(), horizontal_surroundings[9].GetHeight())
803 };
804 auto it = cost.find(new_pos);
805 // If we don't already know this node with a better path, add it
806 if (it == cost.end() ||
807 new_cost < it->second)
808 {
809 cost[new_pos] = new_cost;
810 nodes_to_explore.emplace(PathNode(
811 new_pos,
812 new_cost + PathNode::Heuristic(new_pos.first, end))
813 );
814 came_from[new_pos] = current_node.pos;
815 }
816 }
817
818 // - - -
819 // x
820 // x
821 //--- ? o
822 // ? ?
823 // ? ?
824 if (horizontal_surroundings[7].IsEmpty()
825 && horizontal_surroundings[8].IsEmpty()
826 && !horizontal_surroundings[9].IsEmpty()
827 && !horizontal_surroundings[9].IsHazardous()
828 )
829 {
830 const bool above_block = horizontal_surroundings[9].IsClimbable() || horizontal_surroundings[9].GetHeight() + 1e-3f > current_node.pos.first.y;
831 const float new_cost = cost[current_node.pos] + 3.5f - 1.0f * above_block;
832 const std::pair<Position, float> new_pos = {
833 next_next_location + Position(0, -1 + 1 * above_block, 0),
834 above_block ? std::max(static_cast<float>(current_node.pos.first.y), horizontal_surroundings[9].GetHeight()) : std::max(horizontal_surroundings[9].GetHeight(), horizontal_surroundings[10].GetHeight())
835 };
836 auto it = cost.find(new_pos);
837 // If we don't already know this node with a better path, add it
838 if (it == cost.end() ||
839 new_cost < it->second)
840 {
841 cost[new_pos] = new_cost;
842 nodes_to_explore.emplace(PathNode(
843 new_pos,
844 new_cost + PathNode::Heuristic(new_pos.first, end))
845 );
846 came_from[new_pos] = current_node.pos;
847 }
848 }
849
850 // - - -
851 // x
852 // x
853 //--- ?
854 // ? o
855 // ? ?
856 if (horizontal_surroundings[7].IsEmpty()
857 && horizontal_surroundings[8].IsEmpty()
858 && horizontal_surroundings[9].IsEmpty()
859 && !horizontal_surroundings[10].IsEmpty()
860 && !horizontal_surroundings[10].IsHazardous()
861 )
862 {
863 const bool above_block = horizontal_surroundings[10].IsClimbable() || horizontal_surroundings[10].GetHeight() + 1e-3f > current_node.pos.first.y - 1;
864 const float new_cost = cost[current_node.pos] + 4.5f - 1.0f * above_block;
865 const std::pair<Position, float> new_pos = {
866 next_next_location + Position(0, -2 + 1 * above_block, 0),
867 above_block ? std::max(current_node.pos.first.y - 1.0f, horizontal_surroundings[10].GetHeight()) : std::max(horizontal_surroundings[10].GetHeight(), horizontal_surroundings[11].GetHeight())
868 };
869 auto it = cost.find(new_pos);
870 // If we don't already know this node with a better path, add it
871 if (it == cost.end() ||
872 new_cost < it->second)
873 {
874 cost[new_pos] = new_cost;
875 nodes_to_explore.emplace(PathNode(
876 new_pos,
877 new_cost + PathNode::Heuristic(new_pos.first, end))
878 );
879 came_from[new_pos] = current_node.pos;
880 }
881 }
882
883 // - - -
884 // x
885 // x
886 //--- ?
887 // ?
888 // ? o
889 if (horizontal_surroundings[7].IsEmpty()
890 && horizontal_surroundings[8].IsEmpty()
891 && horizontal_surroundings[9].IsEmpty()
892 && horizontal_surroundings[10].IsEmpty()
893 && !horizontal_surroundings[11].IsEmpty()
894 && !horizontal_surroundings[11].IsHazardous()
895 )
896 {
897 const bool above_block = horizontal_surroundings[11].IsClimbable() || horizontal_surroundings[11].GetHeight() + 1e-3f > current_node.pos.first.y - 2;
898 const float new_cost = cost[current_node.pos] + 6.5f - 1.0f * above_block;
899 const std::pair<Position, float> new_pos = {
900 next_next_location + Position(0, -3 + 1 * above_block, 0),
901 above_block ? std::max(current_node.pos.first.y - 2.0f, horizontal_surroundings[11].GetHeight()) : horizontal_surroundings[1].GetHeight()
902 };
903 auto it = cost.find(new_pos);
904 // If we don't already know this node with a better path, add it
905 if (it == cost.end() ||
906 new_cost < it->second)
907 {
908 cost[new_pos] = new_cost;
909 nodes_to_explore.emplace(PathNode(
910 new_pos,
911 new_cost + PathNode::Heuristic(new_pos.first, end))
912 );
913 came_from[new_pos] = current_node.pos;
914 }
915 }
916 } // neighbour loop
917 }
918
919 auto it_end_path = came_from.begin();
920
921 // We search for the node respecting
922 // the criteria AND the closest to
923 // start as it should often lead
924 // to a shorter path. In case of a tie,
925 // take the one the closest to the end
926 int best_dist = std::numeric_limits<int>::max();
927 int best_dist_start = std::numeric_limits<int>::max();
928 for (auto it = came_from.begin(); it != came_from.end(); ++it)
929 {
930 const Position diff = it->first.first - end;
931 const int d_xz = std::abs(diff.x) + std::abs(diff.z);
932 const int d = d_xz + std::abs(diff.y);
933 const Position diff_start = it->first.first - start;
934 const int d_start = std::abs(diff_start.x) + std::abs(diff_start.y) + std::abs(diff_start.z);
935 if (d <= dist_tolerance && d >= min_end_dist && d_xz >= min_end_dist_xz &&
936 (d_start < best_dist_start || (d_start == best_dist_start && d < best_dist))
937 )
938 {
939 best_dist = d;
940 best_dist_start = d_start;
941 it_end_path = it;
942 }
943 }
944
945 // There were no node respecting the criteria in the search,
946 // this might mean we reached search limit
947 // Take closest node to the goal in this case
948 if (best_dist == std::numeric_limits<int>::max())
949 {
950 for (auto it = came_from.begin(); it != came_from.end(); ++it)
951 {
952 const Position diff = it->first.first - end;
953 const int d_xz = std::abs(diff.x) + std::abs(diff.z);
954 const int d = d_xz + std::abs(diff.y);
955 const Position diff_start = it->first.first - start;
956 const int d_start = std::abs(diff_start.x) + std::abs(diff_start.y) + std::abs(diff_start.z);
957 if (d < best_dist || (d == best_dist && d_start < best_dist_start))
958 {
959 best_dist = d;
960 best_dist_start = d_start;
961 it_end_path = it;
962 }
963 }
964 }
965
966 std::deque<std::pair<Position, float>> output_deque;
967 output_deque.push_front(it_end_path->first);
968 while (it_end_path->second.first != start)
969 {
970 it_end_path = came_from.find(it_end_path->second);
971 output_deque.push_front(it_end_path->first);
972 }
973
974 return std::vector<std::pair<Position, float>>(output_deque.begin(), output_deque.end());
975 }
976
977#if PROTOCOL_VERSION < 767 /* < 1.21 */
978 // a75f87e0-0583-435b-847a-cf0c18ede2d1
979 static constexpr std::array<unsigned char, 16> botcraft_pathfinding_speed_key= { 0xA7, 0x5F, 0x87, 0xE0, 0x05, 0x83, 0x43, 0x5B, 0x84, 0x7A, 0xCF, 0x0C, 0x18, 0xED, 0xE2, 0xD1 };
980#else
981 static const std::string botcraft_pathfinding_speed_key = "botcraft:speed";
982#endif
983
984 bool Move(BehaviourClient& client, std::shared_ptr<LocalPlayer>& local_player, const Vector3<double>& target_position, const float speed_factor, const bool sprint)
985 {
986 const Position target_block(
987 static_cast<int>(std::floor(target_position.x)),
988 static_cast<int>(std::floor(target_position.y)),
989 static_cast<int>(std::floor(target_position.z))
990 );
991 const Vector3<double> look_at_target = target_position + Vector3<double>(0.0, local_player->GetEyeHeight(), 0.0);
992 const Vector3<double> motion_vector = target_position - local_player->GetPosition();
993 const double half_player_width = 0.5 * local_player->GetWidth();
994 const Vector3<double> horizontal_target_position(target_position.x, 0.0, target_position.z);
995 const double ms_per_tick = client.GetPhysicsManager()->GetMsPerTick();
996
997 std::shared_ptr<World> world = client.GetWorld();
998
999 if (speed_factor != 1.0f)
1000 {
1001 local_player->SetAttributeModifier(
1005 speed_factor - 1.0f, // -1 as MultiplyTotal will multiply by (1.0 + x)
1007 });
1008 }
1009 Utilities::OnEndScope botcraft_speed_modifier_remover([&]() {
1010 local_player->RemoveAttributeModifier(EntityAttribute::Type::MovementSpeed, botcraft_pathfinding_speed_key);
1011 });
1012
1013 local_player->LookAt(look_at_target, true);
1014
1015 // Move horizontally (with a jump if necessary)
1016 if (std::abs(motion_vector.x) > 0.5 || std::abs(motion_vector.z) > 0.5)
1017 {
1018 // If we need to jump
1019 if (motion_vector.y > 0.5 || std::abs(motion_vector.x) > 1.5 || std::abs(motion_vector.z) > 1.5)
1020 {
1021 // If not a jump above a gap, wait until reaching the next Y value before moving X/Z
1022 if (std::abs(motion_vector.x) < 1.5 && std::abs(motion_vector.z) < 1.5)
1023 {
1024 local_player->SetInputsJump(true);
1025
1026 if (!Utilities::YieldForCondition([&]() -> bool
1027 {
1028 return local_player->GetY() >= target_block.y;
1029 }, client, 40.0 * ms_per_tick))
1030 {
1031 return false;
1032 }
1033 }
1034 // It's a long jump above a gap
1035 else
1036 {
1037 const Vector3<double> current_block_center_xz(
1038 std::floor(local_player->GetX()) + 0.5,
1039 0.0,
1040 std::floor(local_player->GetZ()) + 0.5
1041 );
1042 // Start to move forward to have speed before jumping
1043 if (!Utilities::YieldForCondition([&]() -> bool
1044 {
1045 if (local_player->GetDirtyInputs())
1046 {
1047 return false;
1048 }
1049
1050 // We need to add the forward input even for the tick we do the jump
1051 local_player->LookAt(look_at_target, false);
1052 local_player->SetInputsForward(1.0);
1053 local_player->SetInputsSprint(sprint);
1054
1055 if (Vector3<double>(local_player->GetX(), 0.0, local_player->GetZ()).SqrDist(current_block_center_xz) > half_player_width * half_player_width)
1056 {
1057 // Then jump
1058 local_player->SetInputsJump(true);
1059 return true;
1060 }
1061
1062 return false;
1063 }, client, 20.0 * ms_per_tick))
1064 {
1065 return false;
1066 }
1067 }
1068 }
1069 // Move forward to the right X/Z location
1070 if (!Utilities::YieldForCondition([&]() -> bool
1071 {
1072 if (local_player->GetDirtyInputs())
1073 {
1074 return false;
1075 }
1076
1077 const Vector3<double> current_pos = local_player->GetPosition();
1078 if (Vector3<double>(current_pos.x, 0.0, current_pos.z).SqrDist(horizontal_target_position) < (0.5 - half_player_width) * (0.5 - half_player_width))
1079 {
1080 return true;
1081 }
1082
1083 local_player->LookAt(look_at_target, false);
1084 const Vector3<double> speed = local_player->GetSpeed();
1085 double forward = 1.0;
1086 // If we need to fall, stop accelerating to prevent "overshooting" and potentially
1087 // hit some block on the bottom or on the side of the dropshoot
1088 if (motion_vector.y < -0.5 &&
1089 static_cast<int>(std::floor(current_pos.x)) == target_block.x &&
1090 static_cast<int>(std::floor(current_pos.z)) == target_block.z)
1091 {
1092 if (std::max(std::abs(speed.x), std::abs(speed.z)) > 0.12) // 0.12 because I needed a value so why not
1093 {
1094 forward = -1.0;
1095 }
1096 else if (std::max(std::abs(speed.x), std::abs(speed.z)) > 0.06)
1097 {
1098 forward = 0.0;
1099 }
1100 }
1101 local_player->SetInputsForward(forward);
1102 local_player->SetInputsSprint(sprint && (forward == 1.0));
1103
1104 return false;
1105 }, client, (std::abs(motion_vector.x) + std::abs(motion_vector.z) + (motion_vector.y < -0.5)) * 20.0 * ms_per_tick))
1106 {
1107 return false;
1108 }
1109 }
1110
1111 // If we need to go down, let the gravity do it's job, unless we are in a scaffholding or water,
1112 // in which case we need to press sneak to go down. If free falling in air, press sneak to catch
1113 // climbable at the bottom, preventing fall damage
1114 if (local_player->GetY() > target_position.y && !Utilities::YieldForCondition([&]() -> bool
1115 {
1116 // Previous inputs have not been processed by physics thread yet
1117 if (local_player->GetDirtyInputs())
1118 {
1119 return false;
1120 }
1121
1122 if (static_cast<int>(std::floor(local_player->GetY())) <= target_block.y &&
1123 (local_player->GetOnGround() || local_player->IsClimbing() || local_player->IsInFluid()))
1124 {
1125 return true;
1126 }
1127
1128 const Vector3<double> current_pos = local_player->GetPosition();
1129 const Blockstate* feet_block = world->GetBlock(Position(
1130 static_cast<int>(std::floor(current_pos.x)),
1131 static_cast<int>(std::floor(current_pos.y)),
1132 static_cast<int>(std::floor(current_pos.z))
1133 ));
1134 local_player->SetInputsSneak(feet_block != nullptr &&
1135 (feet_block->IsFluidOrWaterlogged() || feet_block->IsScaffolding() || (feet_block->IsAir() && motion_vector.y < -2.5)));
1136 local_player->SetInputsJump(local_player->GetFlying()); // Stop flying
1137
1138 // If we drifted too much, adjust toward target X/Z position
1139 if (Vector3<double>(current_pos.x, 0.0, current_pos.z).SqrDist(horizontal_target_position) > (0.5 - half_player_width) * (0.5 - half_player_width))
1140 {
1141 local_player->LookAt(look_at_target, false);
1142 local_player->SetInputsForward(1.0);
1143 }
1144
1145 return false;
1146 }, client, 20.0 * ms_per_tick + (1 + std::abs(motion_vector.y))))
1147 {
1148 return false;
1149 }
1150
1151 // We need to go up (either from ground or in a climbable/water)
1152 if (local_player->GetY() < target_position.y && !Utilities::YieldForCondition([&]() -> bool
1153 {
1154 // Previous inputs have not been processed by physics thread yet
1155 if (local_player->GetDirtyInputs())
1156 {
1157 return false;
1158 }
1159
1160 const Vector3<double> current_pos = local_player->GetPosition();
1161 if (static_cast<int>(std::floor(current_pos.y)) >= target_block.y)
1162 {
1163 return true;
1164 }
1165
1166 local_player->SetInputsJump(true);
1167
1168 // If we drifted too much, adjust toward target X/Z position
1169 if (Vector3<double>(current_pos.x, 0.0, current_pos.z).SqrDist(horizontal_target_position) > (0.5 - half_player_width) * (0.5 - half_player_width))
1170 {
1171 local_player->LookAt(look_at_target, false);
1172 local_player->SetInputsForward(1.0);
1173 }
1174
1175 return false;
1176 }, client, 20.0 * ms_per_tick * (1 + std::abs(motion_vector.y))))
1177 {
1178 return false;
1179 }
1180
1181 // We are in the target block, make sure we are not a bit too high
1182 return Utilities::YieldForCondition([&]() -> bool
1183 {
1184 if (local_player->GetDirtyInputs())
1185 {
1186 return false;
1187 }
1188
1189 // One physics tick climbing down is 0.15 at most, so this should never get too low in theory
1190 if (local_player->GetY() >= target_position.y && local_player->GetY() - target_position.y < 0.2)
1191 {
1192 return true;
1193 }
1194
1195 const Vector3<double> current_pos = local_player->GetPosition();
1196 const Blockstate* feet_block = world->GetBlock(Position(
1197 static_cast<int>(std::floor(current_pos.x)),
1198 static_cast<int>(std::floor(current_pos.y)),
1199 static_cast<int>(std::floor(current_pos.z))
1200 ));
1201 local_player->SetInputsSneak(feet_block != nullptr &&
1202 (feet_block->IsFluidOrWaterlogged() || feet_block->IsScaffolding() || (feet_block->IsAir() && motion_vector.y < -2.5)));
1203 local_player->SetInputsJump(local_player->GetFlying()); // Stop flying
1204
1205 // If we drifted too much, adjust toward target X/Z position
1206 if (Vector3<double>(current_pos.x, 0.0, current_pos.z).SqrDist(horizontal_target_position) > (0.5 - half_player_width) * (0.5 - half_player_width))
1207 {
1208 local_player->LookAt(look_at_target, false);
1209 local_player->SetInputsForward(1.0);
1210 }
1211
1212 return false;
1213 }, client, 20.0 * ms_per_tick);
1214 }
1215
1216 // Try to cancel speed while going toward the target position
1218 {
1219 std::shared_ptr<LocalPlayer> player = client.GetLocalPlayer();
1220 const Vector3<double> pos = player->GetPosition();
1221 const double ms_per_tick = client.GetPhysicsManager()->GetMsPerTick();
1222
1223 Utilities::YieldForCondition([&]() -> bool
1224 {
1225 if (player->GetDirtyInputs())
1226 {
1227 return false;
1228 }
1229
1230 const Vector3<double> pos = player->GetPosition();
1231 const Vector3<double> speed = player->GetSpeed();
1232 if (std::abs(pos.x - target.x) < 0.2 &&
1233 std::abs(pos.z - target.z) < 0.2 &&
1234 std::abs(speed.x) < 0.05 &&
1235 std::abs(speed.z) < 0.05)
1236 {
1237 return true;
1238 }
1239
1240 const float yaw_rad = player->GetYaw() * 0.01745329251994 /* PI/180 */;
1241 const double cos_yaw = std::cos(yaw_rad);
1242 const double sin_yaw = std::sin(yaw_rad);
1243
1244 // Project on forward/left axis
1245 const double delta_forward = (target.z - pos.z) * cos_yaw - (target.x - pos.x) * sin_yaw;
1246 const double delta_left = (target.x - pos.x) * cos_yaw + (target.z - pos.z) * sin_yaw;
1247 const double speed_forward = speed.z * cos_yaw - speed.x * sin_yaw;
1248 const double speed_left = speed.x * cos_yaw + speed.z * sin_yaw;
1249
1250 // If going too fast, reduce speed
1251 if (std::abs(speed_forward) > 0.1)
1252 {
1253 player->SetInputsForward(speed_forward > 0.0 ? -1.0f : 1.0f);
1254 }
1255 // Opposite signs or going toward the same direction but slowly
1256 else if (delta_forward * speed_forward < 0.0 || std::abs(speed_forward) < 0.02)
1257 {
1258 player->SetInputsForward(delta_forward > 0.0 ? 1.0f : -1.0f);
1259 }
1260 else
1261 {
1262 player->SetInputsForward(0.0f);
1263 }
1264
1265 // Same thing on left axis
1266 if (std::abs(speed_left) > 0.1)
1267 {
1268 player->SetInputsLeft(speed_left > 0.0 ? -1.0f : 1.0f);
1269 }
1270 else if (delta_left * speed_left < 0.0 || std::abs(speed_left) < 0.02)
1271 {
1272 player->SetInputsLeft(delta_left > 0.0 ? 1.0f : -1.0f);
1273 }
1274 else
1275 {
1276 player->SetInputsLeft(0.0f);
1277 }
1278
1279 return false;
1280 }, client, 20.0 * ms_per_tick);
1281 }
1282
1283 Status GoToImpl(BehaviourClient& client, const Vector3<double>& goal, const int dist_tolerance, const int min_end_dist, const int min_end_dist_xz, const bool allow_jump, const bool sprint, float speed_factor)
1284 {
1285 if (min_end_dist > dist_tolerance)
1286 {
1287 LOG_WARNING("GoTo.min_end_dist should be <= dist_tolerance if you want pathfinding to work as expected");
1288 }
1289 if (min_end_dist_xz > dist_tolerance)
1290 {
1291 LOG_WARNING("GoTo.min_end_dist_xz should be <= dist_tolerance if you want pathfinding to work as expected");
1292 }
1293 if (speed_factor <= 0.0f)
1294 {
1295 LOG_WARNING("GoTo.speed_factor should be >0 or the bot won't move. Setting it to default value of 1.0");
1296 speed_factor = 1.0f;
1297 }
1298 std::shared_ptr<LocalPlayer> local_player = client.GetLocalPlayer();
1299 const Position goal_block(
1300 static_cast<int>(std::floor(goal.x)),
1301 static_cast<int>(std::floor(goal.y)),
1302 static_cast<int>(std::floor(goal.z))
1303 );
1304
1305 // Don't bother with pathfinding in spectator mode, directly go to the goal
1306 if (local_player->GetGameMode() == GameType::Spectator)
1307 {
1308 const Vector3<double> target(0.5 + goal_block.x, 0.5 + goal_block.y, 0.5 + goal_block.z);
1309 const double square_half_width = local_player->GetWidth() * local_player->GetWidth() / 4.0;
1310 const double dist = std::sqrt((target - local_player->GetPosition()).SqrNorm());
1311 const double ms_per_tick = client.GetPhysicsManager()->GetMsPerTick();
1312 if (Utilities::YieldForCondition([&]() -> bool
1313 {
1314 if (!local_player->GetDirtyInputs())
1315 {
1316 local_player->LookAt(target, true);
1317 local_player->SetInputsForward(1.0);
1318 }
1319
1320 return local_player->GetPosition().SqrDist(target) < square_half_width;
1321 }, client, dist * 20.0 * ms_per_tick))
1322 {
1323 AdjustPosSpeed(client, goal);
1324 return Status::Success;
1325 }
1326 else
1327 {
1328 return Status::Failure;
1329 }
1330 }
1331
1332 if (StopFlying(client) == Status::Failure)
1333 {
1334 return Status::Failure;
1335 }
1336
1337 std::shared_ptr<World> world = client.GetWorld();
1338 Position current_position;
1339 do
1340 {
1341 // Wait until we are on the ground or climbing
1342 const std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
1343 const double ms_per_tick = client.GetPhysicsManager()->GetMsPerTick();
1344 while (!local_player->GetOnGround() && !local_player->IsClimbing() && !local_player->IsInFluid())
1345 {
1346 if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() >= 40.0 * ms_per_tick)
1347 {
1348 LOG_WARNING("Timeout waiting for the bot to land on the floor between two block move. Staying at " << local_player->GetPosition());
1349 return Status::Failure;
1350 }
1351 client.Yield();
1352 }
1353
1354 // Get the position, we add 0.25 to Y in case we are at X.97 instead of X+1
1355 current_position = Position(
1356 static_cast<int>(std::floor(local_player->GetPosition().x)),
1357 static_cast<int>(std::floor(local_player->GetPosition().y + 0.25)),
1358 static_cast<int>(std::floor(local_player->GetPosition().z))
1359 );
1360
1361 std::vector<std::pair<Position, float>> path;
1362 const bool is_goal_loaded = world->IsLoaded(goal_block);
1363
1364 const int current_diff_xz = std::abs(goal_block.x - current_position.x) + std::abs(goal_block.z - current_position.z);
1365 const int current_diff = current_diff_xz + std::abs(goal_block.y - current_position.y);
1366 // Path finding step
1367 if (!is_goal_loaded)
1368 {
1369 LOG_INFO('[' << client.GetNetworkManager()->GetMyName() << "] Current goal position " << goal_block << " is either air or not loaded, trying to get closer to load the chunk");
1370 Vector3<double> goal_direction(goal_block.x - current_position.x, goal_block.y - current_position.y, goal_block.z - current_position.z);
1371 goal_direction.Normalize();
1372 path = FindPath(client, current_position,
1373 current_position + Position(
1374 static_cast<int>(goal_direction.x * 32.0),
1375 static_cast<int>(goal_direction.y * 32.0),
1376 static_cast<int>(goal_direction.z * 32.0)
1377 ), dist_tolerance, min_end_dist, min_end_dist_xz, allow_jump);
1378 }
1379 else
1380 {
1381 if (dist_tolerance && current_diff <= dist_tolerance && current_diff >= min_end_dist && current_diff_xz >= min_end_dist_xz)
1382 {
1383 AdjustPosSpeed(client, goal);
1384 return Status::Success;
1385 }
1386 path = FindPath(client, current_position, goal_block, dist_tolerance, min_end_dist, min_end_dist_xz, allow_jump);
1387 }
1388
1389 if (path.size() == 0 || path.back().first == current_position)
1390 {
1391 if (!dist_tolerance && current_diff > 0)
1392 {
1393 LOG_WARNING("Pathfinding cannot find a better position than " << current_position << " (asked " << goal_block << "). Staying there.");
1394 return Status::Failure;
1395 }
1396 else if (!dist_tolerance)
1397 {
1398 AdjustPosSpeed(client, goal);
1399 return Status::Success;
1400 }
1401 else
1402 {
1403 if (current_diff <= dist_tolerance)
1404 {
1405 AdjustPosSpeed(client, goal);
1406 return Status::Success;
1407 }
1408 else
1409 {
1410 return Status::Failure;
1411 }
1412 }
1413 }
1414 // To avoid going back and forth two positions that are at the same distance of an unreachable goal
1415 else if (current_diff >= min_end_dist && current_diff_xz >= min_end_dist_xz && std::abs(goal_block.x - path.back().first.x) + std::abs(goal_block.y - path.back().first.y) + std::abs(goal_block.z - path.back().first.z) >= current_diff)
1416 {
1417 LOG_WARNING("Pathfinding cannot find a better position than " << current_position << " (asked " << goal_block << "). Staying there.");
1418 return Status::Failure;
1419 }
1420
1421 for (int i = 0; i < path.size(); ++i)
1422 {
1423 // Basic verification to check we won't try to walk on air.
1424 // If so, it means some blocks have changed, better to
1425 // recompute a new path
1426 const Blockstate* next_target = world->GetBlock(path[i].first);
1427 const Blockstate* below = world->GetBlock(path[i].first + Position(0, -1, 0));
1428 if ((next_target == nullptr || (!next_target->IsClimbable() && !next_target->IsFluid())) &&
1429 (below == nullptr || below->IsAir()))
1430 {
1431 break;
1432 }
1433
1434 // If something went wrong, break and
1435 // replan the whole path to the goal
1436 if (!Move(client, local_player, Vector3<double>(path[i].first.x + 0.5, path[i].second, path[i].first.z + 0.5), speed_factor, sprint))
1437 {
1438 break;
1439 }
1440 // Otherwise just update current position for
1441 // next move
1442 else
1443 {
1444 const Vector3<double> local_player_pos = local_player->GetPosition();
1445 // Get the position, we add 0.25 to Y in case we are at X.97 instead of X+1
1446 current_position = Position(
1447 static_cast<int>(std::floor(local_player_pos.x)),
1448 static_cast<int>(std::floor(local_player_pos.y + 0.25)),
1449 static_cast<int>(std::floor(local_player_pos.z))
1450 );
1451 }
1452 }
1453 } while (current_position != goal_block);
1454
1455 AdjustPosSpeed(client, goal);
1456 return Status::Success;
1457 }
1458
1459 Status GoTo(BehaviourClient& client, const Position& goal, const int dist_tolerance, const int min_end_dist, const int min_end_dist_xz, const bool allow_jump, const bool sprint, const float speed_factor)
1460 {
1461 constexpr std::array variable_names = {
1462 "GoTo.goal",
1463 "GoTo.dist_tolerance",
1464 "GoTo.min_end_dist",
1465 "GoTo.min_end_dist_xz",
1466 "GoTo.allow_jump",
1467 "GoTo.sprint",
1468 "GoTo.speed_factor"
1469 };
1470
1471 Blackboard& blackboard = client.GetBlackboard();
1472
1473 blackboard.Set<Position>(variable_names[0], goal);
1474 blackboard.Set<int>(variable_names[1], dist_tolerance);
1475 blackboard.Set<int>(variable_names[2], min_end_dist);
1476 blackboard.Set<int>(variable_names[3], min_end_dist_xz);
1477 blackboard.Set<bool>(variable_names[4], allow_jump);
1478 blackboard.Set<bool>(variable_names[5], sprint);
1479 blackboard.Set<float>(variable_names[6], speed_factor);
1480
1481 return GoToImpl(client, Vector3<double>(goal.x + 0.5, goal.y, goal.z + 0.5), dist_tolerance, min_end_dist, min_end_dist_xz, allow_jump, sprint, speed_factor);
1482 }
1483
1485 {
1486 constexpr std::array variable_names = {
1487 "GoTo.goal",
1488 "GoTo.dist_tolerance",
1489 "GoTo.min_end_dist",
1490 "GoTo.min_end_dist_xz",
1491 "GoTo.allow_jump",
1492 "GoTo.sprint",
1493 "GoTo.speed_factor"
1494 };
1495
1496 Blackboard& blackboard = client.GetBlackboard();
1497
1498 // Mandatory
1499 const Position& goal = blackboard.Get<Position>(variable_names[0]);
1500
1501 // Optional
1502 const int dist_tolerance = blackboard.Get(variable_names[1], 0);
1503 const int min_end_dist = blackboard.Get(variable_names[2], 0);
1504 const int min_end_dist_xz = blackboard.Get(variable_names[3], 0);
1505 const bool allow_jump = blackboard.Get(variable_names[4], true);
1506 const bool sprint = blackboard.Get(variable_names[5], true);
1507 const float speed_factor = blackboard.Get(variable_names[6], 1.0f);
1508
1509 return GoToImpl(client, Vector3<double>(goal.x + 0.5, goal.y, goal.z + 0.5), dist_tolerance, min_end_dist, min_end_dist_xz, allow_jump, sprint, speed_factor);
1510 }
1511
1512 Status GoToDouble(BehaviourClient& client, const Vector3<double>& goal, const bool allow_jump, const bool sprint, const float speed_factor)
1513 {
1514 constexpr std::array variable_names = {
1515 "GoToDouble.goal",
1516 "GoToDouble.allow_jump",
1517 "GoToDouble.sprint",
1518 "GoToDouble.speed_factor"
1519 };
1520
1521 Blackboard& blackboard = client.GetBlackboard();
1522
1523 blackboard.Set<Vector3<double>>(variable_names[0], goal);
1524 blackboard.Set<bool>(variable_names[1], allow_jump);
1525 blackboard.Set<bool>(variable_names[2], sprint);
1526 blackboard.Set<float>(variable_names[3], speed_factor);
1527
1528 return GoToImpl(client, goal, 0, 0, 0, allow_jump, sprint, speed_factor);
1529 }
1530
1532 {
1533 constexpr std::array variable_names = {
1534 "GoToDouble.goal",
1535 "GoToDouble.allow_jump",
1536 "GoToDouble.sprint",
1537 "GoToDouble.speed_factor"
1538 };
1539
1540 Blackboard& blackboard = client.GetBlackboard();
1541
1542 // Mandatory
1543 const Vector3<double>& goal = blackboard.Get<Vector3<double>>(variable_names[0]);
1544
1545 // Optional
1546 const bool allow_jump = blackboard.Get(variable_names[1], true);
1547 const bool sprint = blackboard.Get(variable_names[2], true);
1548 const float speed_factor = blackboard.Get(variable_names[3], 1.0f);
1549
1550 return GoToImpl(client, goal, 0, 0, 0, allow_jump, sprint, speed_factor);
1551 }
1552
1553
1554 Status LookAtImpl(BehaviourClient& client, const Vector3<double>& target, const bool set_pitch, const bool sync_to_server)
1555 {
1556 client.GetLocalPlayer()->LookAt(target, set_pitch);
1557
1558 if (!sync_to_server)
1559 {
1560 return Status::Success;
1561 }
1562
1563 return SyncPosRotToServer(client);
1564 }
1565
1566 Status LookAt(BehaviourClient& client, const Vector3<double>& target, const bool set_pitch, const bool sync_to_server)
1567 {
1568 const std::array variable_names = {
1569 "LookAt.target",
1570 "LookAt.set_pitch",
1571 "LookAt.sync_to_server",
1572 };
1573
1574 Blackboard& blackboard = client.GetBlackboard();
1575
1576 blackboard.Set<Vector3<double>>(variable_names[0], target);
1577 blackboard.Set<bool>(variable_names[1], set_pitch);
1578 blackboard.Set<bool>(variable_names[2], sync_to_server);
1579
1580 return LookAtImpl(client, target, set_pitch, sync_to_server);
1581 }
1582
1584 {
1585 const std::array variable_names = {
1586 "LookAt.target",
1587 "LookAt.set_pitch",
1588 "LookAt.sync_to_server",
1589 };
1590
1591 Blackboard& blackboard = client.GetBlackboard();
1592
1593 // Mandatory
1594 const Vector3<double>& target = blackboard.Get<Vector3<double>>(variable_names[0]);
1595
1596 // Optional
1597 const bool set_pitch = blackboard.Get(variable_names[1], true);
1598 const bool sync_to_server = blackboard.Get(variable_names[2], true);
1599
1600 return LookAtImpl(client, target, set_pitch, sync_to_server);
1601 }
1602
1604 {
1605 std::shared_ptr<LocalPlayer> local_player = client.GetLocalPlayer();
1606 const double ms_per_tick = client.GetPhysicsManager()->GetMsPerTick();
1607 if (!local_player->GetMayFly())
1608 {
1609 return Status::Failure;
1610 }
1611
1612 if (local_player->GetFlying())
1613 {
1614 return Status::Success;
1615 }
1616
1617 local_player->SetInputsJump(true);
1618 if (!Utilities::YieldForCondition([&]() -> bool
1619 {
1620 return !local_player->GetDirtyInputs();
1621 }, client, 3.0 * ms_per_tick))
1622 {
1623 return Status::Failure;
1624 }
1625 local_player->SetInputsJump(false);
1626 if (!Utilities::YieldForCondition([&]() -> bool
1627 {
1628 return !local_player->GetDirtyInputs();
1629 }, client, 3.0 * ms_per_tick))
1630 {
1631 return Status::Failure;
1632 }
1633 local_player->SetInputsJump(true);
1634 if (!Utilities::YieldForCondition([&]() -> bool
1635 {
1636 return !local_player->GetDirtyInputs();
1637 }, client, 3.0 * ms_per_tick))
1638 {
1639 return Status::Failure;
1640 }
1641
1642 return local_player->GetFlying() ? Status::Success : Status::Failure;
1643 }
1644
1646 {
1647 std::shared_ptr<LocalPlayer> local_player = client.GetLocalPlayer();
1648 const double ms_per_tick = client.GetPhysicsManager()->GetMsPerTick();
1649 if (!local_player->GetFlying())
1650 {
1651 return Status::Success;
1652 }
1653
1654 local_player->SetInputsJump(true);
1655 if (!Utilities::YieldForCondition([&]() -> bool
1656 {
1657 return !local_player->GetDirtyInputs();
1658 }, client, 3.0 * ms_per_tick))
1659 {
1660 return Status::Failure;
1661 }
1662 local_player->SetInputsJump(false);
1663 if (!Utilities::YieldForCondition([&]() -> bool
1664 {
1665 return !local_player->GetDirtyInputs();
1666 }, client, 3.0 * ms_per_tick))
1667 {
1668 return Status::Failure;
1669 }
1670 local_player->SetInputsJump(true);
1671 if (!Utilities::YieldForCondition([&]() -> bool
1672 {
1673 return !local_player->GetDirtyInputs();
1674 }, client, 3.0 * ms_per_tick))
1675 {
1676 return Status::Failure;
1677 }
1678
1679 return local_player->GetFlying() ? Status::Failure : Status::Success;
1680 }
1681
1683 {
1684 std::shared_ptr<LocalPlayer> local_player = client.GetLocalPlayer();
1685 const double ms_per_tick = client.GetPhysicsManager()->GetMsPerTick();
1686 // Wait for next physics update
1687 local_player->SetDirtyInputs();
1688 return Utilities::YieldForCondition([&] {
1689 return !local_player->GetDirtyInputs();
1690 }, client, 5.0 * ms_per_tick) ? Status::Success : Status::Failure;
1691 }
1692}
#define LOG_WARNING(osstream)
Definition Logger.hpp:44
#define LOG_INFO(osstream)
Definition Logger.hpp:43
A ManagersClient extended with a blackboard that can store any kind of data and a virtual Yield funct...
virtual void Yield()=0
A map wrapper to store arbitrary data.
void Set(const std::string &key, const T &value)
Set map entry at key to value.
const T & Get(const std::string &key)
Get the map value at key, casting it to T.
bool IsHazardous() const
bool IsFluidOrWaterlogged() const
bool IsClimbable() const
std::set< AABB > GetCollidersAtPos(const Position &pos) const
bool IsScaffolding() const
std::shared_ptr< NetworkManager > GetNetworkManager() const
std::shared_ptr< PhysicsManager > GetPhysicsManager() const
std::shared_ptr< LocalPlayer > GetLocalPlayer() const
std::shared_ptr< World > GetWorld() const
const Blockstate * GetBlockstate() const
PathfindingBlockstate(const Blockstate *block, const Position &pos, const bool take_damage)
A class to execute a callback when destroyed.
bool YieldForCondition(const std::function< bool()> &condition, BehaviourClient &client, const long long int timeout_ms=0)
Status GoToImpl(BehaviourClient &client, const Vector3< double > &goal, const int dist_tolerance, const int min_end_dist, const int min_end_dist_xz, const bool allow_jump, const bool sprint, float speed_factor)
Status GoToDouble(BehaviourClient &client, const Vector3< double > &goal, const bool allow_jump=true, const bool sprint=true, const float speed_factor=1.0f)
Find a path to a position and navigate to it.
Status GoTo(BehaviourClient &client, const Position &goal, const int dist_tolerance=0, const int min_end_dist=0, const int min_end_dist_xz=0, const bool allow_jump=true, const bool sprint=true, const float speed_factor=1.0f)
Find a path to a block position and navigate to it.
Status LookAtImpl(BehaviourClient &client, const Vector3< double > &target, const bool set_pitch, const bool sync_to_server)
void AdjustPosSpeed(BehaviourClient &client, const Vector3< double > &target)
Status StartFlying(BehaviourClient &client)
Make the current player fly (as in creative/spectator mode, NOT WITH ELYTRA)
Status SyncPosRotToServer(BehaviourClient &client)
This task will make sure the current player position/orientation have been sent to the server This is...
Vector3< int > Position
Definition Vector3.hpp:282
Status LookAt(BehaviourClient &client, const Vector3< double > &target, const bool set_pitch=true, const bool sync_to_server=true)
Turn the camera to look at a given target and send the new rotation to the server.
std::vector< std::pair< Position, float > > FindPath(const BehaviourClient &client, const Position &start, const Position &end, const int dist_tolerance, const int min_end_dist, const int min_end_dist_xz, const bool allow_jump)
Not actually a task.
bool Move(BehaviourClient &client, std::shared_ptr< LocalPlayer > &local_player, const Vector3< double > &target_position, const float speed_factor, const bool sprint)
Status GoToDoubleBlackboard(BehaviourClient &client)
Same thing as GoToDouble, but reads its parameters from the blackboard.
static const std::string botcraft_pathfinding_speed_key
Status GoToBlackboard(BehaviourClient &client)
Same thing as GoTo, but reads its parameters from the blackboard.
Status StopFlying(BehaviourClient &client)
Make the current player not fly (as in creative/spectator mode, NOT WITH ELYTRA)
Status LookAtBlackboard(BehaviourClient &client)
Same thing as LookAt, but reads its parameters from the blackboard.
size_t operator()(const std::pair< Position, float > &p) const
double SqrDist(const Vector3 &v) const
Definition Vector3.hpp:192