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