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