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