Botcraft 1.21.5
Loading...
Searching...
No Matches
InventoryTasks.cpp
Go to the documentation of this file.
6
16
17using namespace ProtocolCraft;
18
19namespace Botcraft
20{
21 Status ClickSlotInContainerImpl(BehaviourClient& client, const short container_id, const short slot_id, const int click_type, const char button_num)
22 {
23 std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
24
25 std::shared_ptr<ServerboundContainerClickPacket> click_window_packet = std::make_shared<ServerboundContainerClickPacket>();
26
27 click_window_packet->SetContainerId(static_cast<unsigned char>(container_id));
28 click_window_packet->SetSlotNum(slot_id);
29 click_window_packet->SetButtonNum(button_num);
30 click_window_packet->SetClickType(click_type);
31
32 // ItemStack/CarriedItem, StateId and ChangedSlots will be set in SendInventoryTransaction
33 int transaction_id = client.SendInventoryTransaction(click_window_packet);
34
35 // Wait for the click confirmation (versions < 1.17)
36#if PROTOCOL_VERSION < 755 /* < 1.17 */
37 auto start = std::chrono::steady_clock::now();
38 while (true)
39 {
40 if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() >= 10000)
41 {
42 LOG_WARNING("Something went wrong trying to click slot (Timeout).");
43 return Status::Failure;
44 }
45 TransactionState transaction_state = inventory_manager->GetTransactionState(container_id, transaction_id);
46 if (transaction_state == TransactionState::Accepted)
47 {
48 break;
49 }
50 // The transaction has been refused by the server
51 else if (transaction_state == TransactionState::Refused)
52 {
53 return Status::Failure;
54 }
55
56 client.Yield();
57 }
58#endif
59 return Status::Success;
60 }
61
62 Status ClickSlotInContainer(BehaviourClient& client, const short container_id, const short slot_id, const int click_type, const char button_num)
63 {
64 constexpr std::array variable_names = {
65 "ClickSlotInContainer.container_id",
66 "ClickSlotInContainer.slot_id",
67 "ClickSlotInContainer.click_type",
68 "ClickSlotInContainer.button_num"
69 };
70
71 Blackboard& blackboard = client.GetBlackboard();
72
73 // Mandatory
74 blackboard.Set<short>(variable_names[0], container_id);
75 blackboard.Set<short>(variable_names[1], slot_id);
76 blackboard.Set<int>(variable_names[2], click_type);
77 blackboard.Set<char>(variable_names[3], button_num);
78
79 return ClickSlotInContainerImpl(client, container_id, slot_id, click_type, button_num);
80 }
81
83 {
84 constexpr std::array variable_names = {
85 "ClickSlotInContainer.container_id",
86 "ClickSlotInContainer.slot_id",
87 "ClickSlotInContainer.click_type",
88 "ClickSlotInContainer.button_num"
89 };
90
91 Blackboard& blackboard = client.GetBlackboard();
92
93 // Mandatory
94 const short container_id = blackboard.Get<short>(variable_names[0]);
95 const short slot_id = blackboard.Get<short>(variable_names[1]);
96 const int click_type = blackboard.Get<int>(variable_names[2]);
97 const char button_num = blackboard.Get<char>(variable_names[3]);
98
99 return ClickSlotInContainerImpl(client, container_id, slot_id, click_type, button_num);
100 }
101
102
103 Status SwapItemsInContainerImpl(BehaviourClient& client, const short container_id, const short first_slot, const short second_slot)
104 {
105 // If both slots are equal, clicking three times will transfer the content to the cursor instead of being a no-op
106 if (first_slot == second_slot)
107 {
108 return Status::Success;
109 }
110
111 // Left click on the first slot, transferring the slot to the cursor
112 if (ClickSlotInContainer(client, container_id, first_slot, 0, 0) == Status::Failure)
113 {
114 LOG_WARNING("Failed to swap items (first click)");
115 return Status::Failure;
116 }
117
118 // Left click on the second slot, transferring the cursor to the slot
119 if (ClickSlotInContainer(client, container_id, second_slot, 0, 0) == Status::Failure)
120 {
121 LOG_WARNING("Failed to swap items (second click)");
122 return Status::Failure;
123 }
124
125 // Left click on the first slot, transferring back the cursor to the slot
126 if (ClickSlotInContainer(client, container_id, first_slot, 0, 0) == Status::Failure)
127 {
128 LOG_WARNING("Failed to swap items (third click)");
129 return Status::Failure;
130 }
131
132 return Status::Success;
133 }
134
135 Status SwapItemsInContainer(BehaviourClient& client, const short container_id, const short first_slot, const short second_slot)
136 {
137 constexpr std::array variable_names = {
138 "SwapItemsInContainer.container_id",
139 "SwapItemsInContainer.first_slot",
140 "SwapItemsInContainer.second_slot"
141 };
142
143 Blackboard& blackboard = client.GetBlackboard();
144
145 blackboard.Set<short>(variable_names[0], container_id);
146 blackboard.Set<short>(variable_names[1], first_slot);
147 blackboard.Set<short>(variable_names[2], second_slot);
148
149 return SwapItemsInContainerImpl(client, container_id, first_slot, second_slot);
150 }
151
153 {
154 constexpr std::array variable_names = {
155 "SwapItemsInContainer.container_id",
156 "SwapItemsInContainer.first_slot",
157 "SwapItemsInContainer.second_slot"
158 };
159
160 Blackboard& blackboard = client.GetBlackboard();
161
162 // Mandatory
163 const short container_id = blackboard.Get<short>(variable_names[0]);
164 const short first_slot = blackboard.Get<short>(variable_names[1]);
165 const short second_slot = blackboard.Get<short>(variable_names[2]);
166
167 return SwapItemsInContainerImpl(client, container_id, first_slot, second_slot);
168 }
169
170
171 Status DropItemsFromContainerImpl(BehaviourClient& client, const short container_id, const short slot_id, const short num_to_keep)
172 {
173 if (ClickSlotInContainer(client, container_id, slot_id, 0, 0) == Status::Failure)
174 {
175 return Status::Failure;
176 }
177
178 // Drop all
179 if (num_to_keep == 0)
180 {
181 return ClickSlotInContainer(client, container_id, -999, 0, 0);
182 }
183
184 int item_count = client.GetInventoryManager()->GetCursor().GetItemCount();
185
186 // Drop the right amount of items
187 while (item_count > num_to_keep)
188 {
189 if (ClickSlotInContainer(client, container_id, -999, 0, 1) == Status::Failure)
190 {
191 return Status::Failure;
192 }
193 item_count -= 1;
194 }
195
196 // Put back remaining items in the initial slot
197 return ClickSlotInContainer(client, container_id, slot_id, 0, 0);
198 }
199
200 Status DropItemsFromContainer(BehaviourClient& client, const short container_id, const short slot_id, const short num_to_keep)
201 {
202 constexpr std::array variable_names = {
203 "DropItemsFromContainer.container_id",
204 "DropItemsFromContainer.slot_id",
205 "DropItemsFromContainer.num_to_keep"
206 };
207
208 Blackboard& blackboard = client.GetBlackboard();
209
210 blackboard.Set<short>(variable_names[0], container_id);
211 blackboard.Set<short>(variable_names[1], slot_id);
212 blackboard.Set<short>(variable_names[2], num_to_keep);
213
214 return DropItemsFromContainerImpl(client, container_id, slot_id, num_to_keep);
215 }
216
218 {
219 constexpr std::array variable_names = {
220 "DropItemsFromContainer.container_id",
221 "DropItemsFromContainer.slot_id",
222 "DropItemsFromContainer.num_to_keep"
223 };
224
225 Blackboard& blackboard = client.GetBlackboard();
226
227 // Mandatory
228 const short container_id = blackboard.Get<short>(variable_names[0]);
229 const short slot_id = blackboard.Get<short>(variable_names[1]);
230
231 // Optional
232 const short num_to_keep = blackboard.Get<short>(variable_names[2], 0);
233
234 return DropItemsFromContainerImpl(client, container_id, slot_id, num_to_keep);
235 }
236
237
238 Status PutOneItemInContainerSlotImpl(BehaviourClient& client, const short container_id, const short source_slot, const short destination_slot)
239 {
240 // Left click on the first slot, transferring the slot to the cursor
241 if (ClickSlotInContainer(client, container_id, source_slot, 0, 0) == Status::Failure)
242 {
243 LOG_WARNING("Failed to put one item in slot (first click)");
244 return Status::Failure;
245 }
246
247 // Right click on the second slot, transferring one item of the cursor to the slot
248 if (ClickSlotInContainer(client, container_id, destination_slot, 0, 1) == Status::Failure)
249 {
250 LOG_WARNING("Failed to put one item in slot (second click)");
251 return Status::Failure;
252 }
253
254 // Left click on the first slot, transferring back the cursor to the slot
255 if (ClickSlotInContainer(client, container_id, source_slot, 0, 0) == Status::Failure)
256 {
257 LOG_WARNING("Failed to put one item in slot (third click)");
258 return Status::Failure;
259 }
260
261 return Status::Success;
262 }
263
264 Status PutOneItemInContainerSlot(BehaviourClient& client, const short container_id, const short source_slot, const short destination_slot)
265 {
266 constexpr std::array variable_names = {
267 "PutOneItemInContainerSlot.container_id",
268 "PutOneItemInContainerSlot.source_slot",
269 "PutOneItemInContainerSlot.destination_slot"
270 };
271
272 Blackboard& blackboard = client.GetBlackboard();
273
274 blackboard.Set<short>(variable_names[0], container_id);
275 blackboard.Set<short>(variable_names[1], source_slot);
276 blackboard.Set<short>(variable_names[2], destination_slot);
277
278 return PutOneItemInContainerSlotImpl(client, container_id, source_slot, destination_slot);
279 }
280
282 {
283 constexpr std::array variable_names = {
284 "PutOneItemInContainerSlot.container_id",
285 "PutOneItemInContainerSlot.source_slot",
286 "PutOneItemInContainerSlot.destination_slot"
287 };
288
289 Blackboard& blackboard = client.GetBlackboard();
290
291 // Mandatory
292 const short container_id = blackboard.Get<short>(variable_names[0]);
293 const short source_slot = blackboard.Get<short>(variable_names[1]);
294 const short destination_slot = blackboard.Get<short>(variable_names[2]);
295
296 return PutOneItemInContainerSlotImpl(client, container_id, source_slot, destination_slot);
297 }
298
299
300 Status SetItemInHandImpl(BehaviourClient& client, const ItemId item_id, const Hand hand)
301 {
302 std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
303
304 short inventory_correct_slot_index = -1;
305 short inventory_destination_slot_index = hand == Hand::Left ? Window::INVENTORY_OFFHAND_INDEX : (Window::INVENTORY_HOTBAR_START + inventory_manager->GetIndexHotbarSelected());
306
307 // We need to check the inventory
308 // If the currently selected item is the right one, just go for it
309 const Slot current_selected = hand == Hand::Left ? inventory_manager->GetOffHand() : inventory_manager->GetHotbarSelected();
310 if (!current_selected.IsEmptySlot()
311 && current_selected.GetItemId() == item_id)
312
313 {
314 return Status::Success;
315 }
316
317 // Otherwise we need to find a slot with the given item
318 { // slots scope
319 const auto slots = inventory_manager->GetPlayerInventory()->GetLockedSlots();
320 for (const auto& [id, slot] : *slots)
321 {
324 && !slot.IsEmptySlot()
325 && slot.GetItemId() == item_id)
326 {
327 inventory_correct_slot_index = id;
328 break;
329 }
330 }
331 }
332
333 // If there is no stack with the given item in the inventory
334 if (inventory_correct_slot_index == -1)
335 {
336 return Status::Failure;
337 }
338
339 return SwapItemsInContainer(client, Window::PLAYER_INVENTORY_INDEX, inventory_correct_slot_index, inventory_destination_slot_index);
340 }
341
342
343 Status SetItemIdInHand(BehaviourClient& client, const ItemId item_id, const Hand hand)
344 {
345 constexpr std::array variable_names = {
346 "SetItemIdInHand.item_name",
347 "SetItemIdInHand.hand"
348 };
349
350 Blackboard& blackboard = client.GetBlackboard();
351
352 blackboard.Set<ItemId>(variable_names[0], item_id);
353 blackboard.Set<Hand>(variable_names[1], hand);
354
355 return SetItemInHandImpl(client, item_id, hand);
356 }
357
359 {
360 constexpr std::array variable_names = {
361 "SetItemIdInHand.item_name",
362 "SetItemIdInHand.hand"
363 };
364
365 Blackboard& blackboard = client.GetBlackboard();
366
367 // Mandatory
368 const ItemId item_id = blackboard.Get<ItemId>(variable_names[0]);
369 const Hand hand = blackboard.Get<Hand>(variable_names[1], Hand::Right);
370
371 return SetItemInHandImpl(client, item_id, hand);
372 }
373
374 Status SetItemInHand(BehaviourClient& client, const std::string& item_name, const Hand hand)
375 {
376 constexpr std::array variable_names = {
377 "SetItemInHand.item_name",
378 "SetItemInHand.hand"
379 };
380
381 Blackboard& blackboard = client.GetBlackboard();
382
383 blackboard.Set<std::string>(variable_names[0], item_name);
384 blackboard.Set<Hand>(variable_names[1], hand);
385
386 const ItemId item_id = AssetsManager::getInstance().GetItemID(item_name);
387
388 return SetItemInHandImpl(client, item_id, hand);
389 }
390
392 {
393 constexpr std::array variable_names = {
394 "SetItemInHand.item_name",
395 "SetItemInHand.hand"
396 };
397
398 Blackboard& blackboard = client.GetBlackboard();
399
400 // Mandatory
401 const std::string& item_name = blackboard.Get<std::string>(variable_names[0]);
402 const Hand hand = blackboard.Get<Hand>(variable_names[1], Hand::Right);
403
404 const ItemId item_id = AssetsManager::getInstance().GetItemID(item_name);
405
406 return SetItemInHandImpl(client, item_id, hand);
407 }
408
409
410 Status PlaceBlockImpl(BehaviourClient& client, const std::string& item_name, const Position& pos, std::optional<PlayerDiggingFace> face, const bool wait_confirmation, const bool allow_midair_placing, const bool allow_pathfinding)
411 {
412 std::shared_ptr<World> world = client.GetWorld();
413 std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
414 std::shared_ptr<EntityManager> entity_manager = client.GetEntityManager();
415 std::shared_ptr<NetworkManager> network_manager = client.GetNetworkManager();
416 std::shared_ptr<LocalPlayer> local_player = entity_manager->GetLocalPlayer();
417
418 // Compute the distance from the hand? Might be from somewhere else
419 const Vector3<double> hand_pos = local_player->GetPosition() + Vector3<double>(0.0, 1.0, 0.0);
420
421 if (hand_pos.SqrDist(Vector3<double>(0.5, 0.5, 0.5) + pos) > 16.0f)
422 {
423 if (!allow_pathfinding || GoTo(client, pos, 4, 0, 1) == Status::Failure)
424 {
425 return Status::Failure;
426 }
427 }
428
429 LookAt(client, Vector3<double>(0.5) + pos, true);
430
431 const std::vector<Position> neighbour_offsets({
432 Position(0, 1, 0), Position(0, -1, 0),
433 Position(0, 0, 1), Position(0, 0, -1),
434 Position(1, 0, 0), Position(-1, 0, 0)
435 });
436
437 bool midair_placing = true;
438 // If no face specified
439 if (!face.has_value())
440 {
441 if (allow_midair_placing) // Then we don't care and default to Up
442 {
444 }
445 else
446 {
447 std::vector<PlayerDiggingFace> premium_face_candidates; // Faces next to a solid block
448 premium_face_candidates.reserve(6);
449 std::vector<PlayerDiggingFace> second_choice_face_candidates; // Faces next to a non solid block (like ferns that would make the block replace the fern instead of going next to it)
450 second_choice_face_candidates.reserve(6);
451 for (int face_idx = 0; face_idx < 6; face_idx++)
452 {
453 const Blockstate* neighbour_block = world->GetBlock(pos + neighbour_offsets[face_idx]);
454 // Placing against fluids is not allowed
455 if (neighbour_block != nullptr && !neighbour_block->IsAir() && !neighbour_block->IsFluid())
456 {
457 (neighbour_block->IsSolid() ? premium_face_candidates : second_choice_face_candidates).push_back(static_cast<PlayerDiggingFace>(face_idx));
458 }
459 }
460 if (premium_face_candidates.size() + second_choice_face_candidates.size() == 0)
461 {
462 LOG_WARNING("Can't place a block in midair at " << pos);
463 return Status::Failure;
464 }
465 std::vector<PlayerDiggingFace>& face_candidates = (premium_face_candidates.size() > 0 ? premium_face_candidates : second_choice_face_candidates);
466 const Vector3<double> player_orientation = local_player->GetFrontVector();
467 std::sort( // Find the face face closest to player looking direction
468 face_candidates.begin(), face_candidates.end(), [&](const PlayerDiggingFace a, const PlayerDiggingFace b) -> bool
469 {
470 Vector3<double> a_offset = neighbour_offsets[static_cast<int>(a)];
471 Vector3<double> b_offset = neighbour_offsets[static_cast<int>(b)];
472 return player_orientation.dot(a_offset) > player_orientation.dot(b_offset);
473 // a > b because a negative dot product means the vectors are in opposite directions IE the player is looking at the face.
474 // But because we place the block in the inner faces we negate the result.
475 }
476 );
477 face = face_candidates.front(); // This does not guarantees that the choosed PlayerDiggingFace is facing the player IE player_orientation.dot(face) can be less or equal than 0.
478 }
479 }
480 else // Check if block is air
481 {
482 const Blockstate* block = world->GetBlock(pos);
483
484 if (block != nullptr && !block->IsAir() && !block->IsFluid())
485 {
486 return Status::Failure;
487 }
488
489 const Blockstate* neighbour_block = world->GetBlock(pos + neighbour_offsets[static_cast<int>(face.value())]);
490 midair_placing = neighbour_block == nullptr || neighbour_block->IsAir();
491
492 if (!allow_midair_placing && midair_placing)
493 {
494 LOG_WARNING("Can't place a block in midair at " << pos);
495 return Status::Failure;
496 }
497 }
498
499 // Check if item in inventory
500 if (SetItemInHand(client, item_name, Hand::Right) == Status::Failure)
501 {
502 return Status::Failure;
503 }
504
505 const int num_item_in_hand = inventory_manager->GetPlayerInventory()->GetSlot(Window::INVENTORY_HOTBAR_START + inventory_manager->GetIndexHotbarSelected()).GetItemCount();
506
507 // If cheating is not allowed, adjust the placing position to the block containing the face we're placing against
508 const Position placing_pos = (allow_midair_placing && midair_placing) ? pos : (pos + neighbour_offsets[static_cast<int>(face.value())]);
509
510 std::shared_ptr<ServerboundUseItemOnPacket> place_block_packet = std::make_shared<ServerboundUseItemOnPacket>();
511 place_block_packet->SetLocation(placing_pos.ToNetworkPosition());
512 place_block_packet->SetDirection(static_cast<int>(face.value()));
513 switch (face.value())
514 {
516 place_block_packet->SetCursorPositionX(0.5f);
517 place_block_packet->SetCursorPositionY(0.0f);
518 place_block_packet->SetCursorPositionZ(0.5f);
519 break;
521 place_block_packet->SetCursorPositionX(0.5f);
522 place_block_packet->SetCursorPositionY(1.0f);
523 place_block_packet->SetCursorPositionZ(0.5f);
524 break;
526 place_block_packet->SetCursorPositionX(0.5f);
527 place_block_packet->SetCursorPositionY(0.5f);
528 place_block_packet->SetCursorPositionZ(0.0f);
529 break;
531 place_block_packet->SetCursorPositionX(0.5f);
532 place_block_packet->SetCursorPositionY(0.5f);
533 place_block_packet->SetCursorPositionZ(1.0f);
534 break;
536 place_block_packet->SetCursorPositionX(1.0f);
537 place_block_packet->SetCursorPositionY(0.5f);
538 place_block_packet->SetCursorPositionZ(0.5f);
539 break;
541 place_block_packet->SetCursorPositionX(0.0f);
542 place_block_packet->SetCursorPositionY(0.5f);
543 place_block_packet->SetCursorPositionZ(0.5f);
544 break;
545 default:
546 break;
547 }
548#if PROTOCOL_VERSION > 452 /* > 1.13.2 */
549 place_block_packet->SetInside(false);
550#endif
551 place_block_packet->SetHand(static_cast<int>(Hand::Right));
552#if PROTOCOL_VERSION > 758 /* > 1.18.2 */
553 place_block_packet->SetSequence(world->GetNextWorldInteractionSequenceId());
554#endif
555
556
557 // Place the block
558 network_manager->Send(place_block_packet);
559
560 std::shared_ptr<ServerboundSwingPacket> swing = std::make_shared<ServerboundSwingPacket>();
561 swing->SetHand(static_cast<int>(Hand::Right));
562 network_manager->Send(swing);
563
564 if (!wait_confirmation)
565 {
566 return Status::Success;
567 }
568
569 bool is_block_ok = false;
570 bool is_slot_ok = true;
571 auto start = std::chrono::steady_clock::now();
572 const double ms_per_tick = client.GetPhysicsManager()->GetMsPerTick();
573 while (!is_block_ok || !is_slot_ok)
574 {
575 if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() >= 60.0 * ms_per_tick)
576 {
577 LOG_WARNING('[' << network_manager->GetMyName() << "] "
578 << "Something went wrong waiting block placement confirmation at " << pos << " (Timeout). "
579 << "Block ok: " << is_block_ok << " Slot ok: " << is_slot_ok
580 );
581 return Status::Failure;
582 }
583 if (!is_block_ok)
584 {
585 const Blockstate* block = world->GetBlock(pos);
586
587 if (block != nullptr && block->GetName() == item_name)
588 {
589 is_block_ok = true;
590 }
591 }
592 if (!is_slot_ok)
593 {
594 const int new_num_item_in_hand = inventory_manager->GetPlayerInventory()->GetSlot(Window::INVENTORY_HOTBAR_START + inventory_manager->GetIndexHotbarSelected()).GetItemCount();
595 is_slot_ok = new_num_item_in_hand == num_item_in_hand - 1;
596 }
597
598 if (is_block_ok && is_slot_ok)
599 {
600 return Status::Success;
601 }
602
603 client.Yield();
604 }
605
606 return Status::Success;
607 }
608
609 Status PlaceBlock(BehaviourClient& client, const std::string& item_name, const Position& pos, std::optional<PlayerDiggingFace> face, const bool wait_confirmation, const bool allow_midair_placing, const bool allow_pathfinding)
610 {
611 constexpr std::array variable_names = {
612 "PlaceBlock.item_name",
613 "PlaceBlock.pos",
614 "PlaceBlock.face",
615 "PlaceBlock.wait_confirmation",
616 "PlaceBlock.allow_midair_placing",
617 "PlaceBlock.allow_pathfinding",
618 };
619
620 Blackboard& blackboard = client.GetBlackboard();
621
622 blackboard.Set<std::string>(variable_names[0], item_name);
623 blackboard.Set<Position>(variable_names[1], pos);
624 blackboard.Set<std::optional<PlayerDiggingFace>>(variable_names[2], face);
625 blackboard.Set<bool>(variable_names[3], wait_confirmation);
626 blackboard.Set<bool>(variable_names[4], allow_midair_placing);
627 blackboard.Set<bool>(variable_names[5], allow_pathfinding);
628
629 return PlaceBlockImpl(client, item_name, pos, face, wait_confirmation, allow_midair_placing, allow_pathfinding);
630 }
631
633 {
634 constexpr std::array variable_names = {
635 "PlaceBlock.item_name",
636 "PlaceBlock.pos",
637 "PlaceBlock.face",
638 "PlaceBlock.wait_confirmation",
639 "PlaceBlock.allow_midair_placing",
640 "PlaceBlock.allow_pathfinding",
641 };
642
643 Blackboard& blackboard = client.GetBlackboard();
644
645 // Mandatory
646 const std::string& item_name = blackboard.Get<std::string>(variable_names[0]);
647 const Position& pos = blackboard.Get<Position>(variable_names[1]);
648
649 // Optional
650 const std::optional<PlayerDiggingFace> face = blackboard.Get<std::optional<PlayerDiggingFace>>(variable_names[2], PlayerDiggingFace::Up);
651 const bool wait_confirmation = blackboard.Get<bool>(variable_names[3], false);
652 const bool allow_midair_placing = blackboard.Get<bool>(variable_names[4], false);
653 const bool allow_pathfinding = blackboard.Get<bool>(variable_names[5], true);
654
655
656 return PlaceBlockImpl(client, item_name, pos, face, wait_confirmation, allow_midair_placing, allow_pathfinding);
657 }
658
659
660 Status EatImpl(BehaviourClient& client, const std::string& food_name, const bool wait_confirmation)
661 {
662 if (SetItemInHand(client, food_name, Hand::Left) == Status::Failure)
663 {
664 return Status::Failure;
665 }
666
667 std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
668 std::shared_ptr<NetworkManager> network_manager = client.GetNetworkManager();
669
670 const char current_stack_size = inventory_manager->GetOffHand().GetItemCount();
671 std::shared_ptr<ServerboundUseItemPacket> use_item_packet = std::make_shared<ServerboundUseItemPacket>();
672 use_item_packet->SetHand(static_cast<int>(Hand::Left));
673#if PROTOCOL_VERSION > 758 /* > 1.18.2 */
674 use_item_packet->SetSequence(client.GetWorld()->GetNextWorldInteractionSequenceId());
675#endif
676 network_manager->Send(use_item_packet);
677
678 if (!wait_confirmation)
679 {
680 return Status::Success;
681 }
682
683 auto start = std::chrono::steady_clock::now();
684 const double ms_per_tick = client.GetPhysicsManager()->GetMsPerTick();
685 while (inventory_manager->GetOffHand().GetItemCount() == current_stack_size)
686 {
687 if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() >= 60.0 * ms_per_tick)
688 {
689 LOG_WARNING("Something went wrong trying to eat (Timeout).");
690 return Status::Failure;
691 }
692 client.Yield();
693 }
694
695 return Status::Success;
696 }
697
698 Status Eat(BehaviourClient& client, const std::string& food_name, const bool wait_confirmation)
699 {
700 constexpr std::array variable_names = {
701 "Eat.food_name",
702 "Eat.wait_confirmation"
703 };
704
705 Blackboard& blackboard = client.GetBlackboard();
706
707 blackboard.Set<std::string>(variable_names[0], food_name);
708 blackboard.Set<bool>(variable_names[1], wait_confirmation);
709
710 return EatImpl(client, food_name, wait_confirmation);
711 }
712
714 {
715 constexpr std::array variable_names = {
716 "Eat.food_name",
717 "Eat.wait_confirmation"
718 };
719
720 Blackboard& blackboard = client.GetBlackboard();
721
722 // Mandatory
723 const std::string& food_name = blackboard.Get<std::string>(variable_names[0]);
724
725 // Optional
726 const bool wait_confirmation = blackboard.Get<bool>(variable_names[1], false);
727
728
729 return EatImpl(client, food_name, wait_confirmation);
730 }
731
732
734 {
735 // Open the container
737 {
738 return Status::Failure;
739 }
740
741 std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
742
743 // Wait for a window to be opened
744 auto start = std::chrono::steady_clock::now();
745 while (inventory_manager->GetFirstOpenedWindowId() == -1)
746 {
747 if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() >= 3000)
748 {
749 LOG_WARNING("Something went wrong trying to open container (Timeout).");
750 return Status::Failure;
751 }
752 client.Yield();
753 }
754
755 return Status::Success;
756 }
757
759 {
760 constexpr std::array variable_names = {
761 "OpenContainer.pos"
762 };
763
764 Blackboard& blackboard = client.GetBlackboard();
765
766 blackboard.Set<Position>(variable_names[0], pos);
767
768 return OpenContainerImpl(client, pos);
769 }
770
772 {
773 constexpr std::array variable_names = {
774 "OpenContainer.pos"
775 };
776
777 Blackboard& blackboard = client.GetBlackboard();
778
779 // Mandatory
780 const Position& pos = blackboard.Get<Position>(variable_names[0]);
781
782
783 return OpenContainerImpl(client, pos);
784 }
785
786
787 Status CloseContainerImpl(BehaviourClient& client, const short container_id)
788 {
789 std::shared_ptr<NetworkManager> network_manager = client.GetNetworkManager();
790 std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
791
792 std::shared_ptr<ServerboundContainerClosePacket> close_container_packet = std::make_shared<ServerboundContainerClosePacket>();
793 short true_container_id = container_id;
794 if (true_container_id < 0)
795 {
796 true_container_id = inventory_manager->GetFirstOpenedWindowId();
797 }
798 close_container_packet->SetContainerId(static_cast<unsigned char>(true_container_id));
799 network_manager->Send(close_container_packet);
800
801 // There is no confirmation from the server, so we
802 // can simply close the window here
803 inventory_manager->EraseInventory(true_container_id);
804
805 return Status::Success;
806 }
807
808 Status CloseContainer(BehaviourClient& client, const short container_id)
809 {
810 constexpr std::array variable_names = {
811 "CloseContainer.container_id"
812 };
813
814 Blackboard& blackboard = client.GetBlackboard();
815
816 blackboard.Set<short>(variable_names[0], container_id);
817
818 return CloseContainerImpl(client, container_id);
819 }
820
822 {
823 constexpr std::array variable_names = {
824 "CloseContainer.container_id"
825 };
826
827 Blackboard& blackboard = client.GetBlackboard();
828
829 // Optional
830 const short container_id = blackboard.Get<short>(variable_names[0], -1);
831
832
833 return CloseContainerImpl(client, container_id);
834 }
835
836
838 {
839 std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
840
841 std::stringstream output;
842 {
843 output << "Cursor --> " << inventory_manager->GetCursor().Serialize().Dump() << "\n";
844 auto slots = inventory_manager->GetPlayerInventory()->GetLockedSlots();
845 for (const auto& [id, slot] : *slots)
846 {
847 output << id << " --> " << slot.Serialize().Dump() << "\n";
848 }
849 }
850 LOG(output.str(), level);
851 return Status::Success;
852 }
853
855 {
856 constexpr std::array variable_names = {
857 "LogInventoryContent.level"
858 };
859
860 Blackboard& blackboard = client.GetBlackboard();
861
862 blackboard.Set<LogLevel>(variable_names[0], level);
863
864 return LogInventoryContentImpl(client, level);
865 }
866
868 {
869 constexpr std::array variable_names = {
870 "LogInventoryContent.level"
871 };
872
873 Blackboard& blackboard = client.GetBlackboard();
874
875 //Optional
876 const LogLevel level = blackboard.Get<LogLevel>(variable_names[0], LogLevel::Info);
877
878 return LogInventoryContentImpl(client, level);
879 }
880
881
882#if PROTOCOL_VERSION > 451 /* > 1.13.2 */
883 Status TradeImpl(BehaviourClient& client, const int item_id, const bool buy, const int trade_id)
884 {
885 std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
886
887 // Make sure a trading window is opened and
888 // possible trades are available
889 auto start = std::chrono::steady_clock::now();
890 size_t num_trades = 0;
891 do
892 {
893 if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() > 5000)
894 {
895 LOG_WARNING("Something went wrong waiting trade opening (Timeout).");
896 return Status::Failure;
897 }
898
899 num_trades = inventory_manager->GetAvailableMerchantOffers().size();
900 client.Yield();
901 } while (num_trades <= 0 || inventory_manager->GetFirstOpenedWindowId() == -1);
902
903 const short container_id = inventory_manager->GetFirstOpenedWindowId();
904 std::shared_ptr<Window> trading_container = inventory_manager->GetWindow(container_id);
905
906 if (trading_container == nullptr)
907 {
908 LOG_WARNING("Something went wrong during trade (window closed).");
909 return Status::Failure;
910 }
911
912 int trade_index = trade_id;
913 bool has_trade_second_item = false;
914 const std::vector<ProtocolCraft::MerchantOffer> trades = inventory_manager->GetAvailableMerchantOffers();
915
916 // Find which trade we want in the list
917 if (trade_id == -1)
918 {
919 for (int i = 0; i < trades.size(); ++i)
920 {
921 if ((buy && trades[i].GetOutputItem().GetItemId() == item_id)
922 || (!buy && trades[i].GetInputItem1().GetItemId() == item_id))
923 {
924 trade_index = i;
925 has_trade_second_item = trades[i].GetInputItem2().has_value();
926 break;
927 }
928 }
929 }
930
931 if (trade_index == -1)
932 {
933 LOG_WARNING("Failed trading (this villager does not sell/buy " << AssetsManager::getInstance().Items().at(item_id)->GetName() << ")");
934 return Status::Failure;
935 }
936
937 // Check that the trade is not locked
938 if (trades[trade_index].GetNumberOfTradesUses() >= trades[trade_index].GetMaximumNumberOfTradeUses())
939 {
940 LOG_WARNING("Failed trading (trade locked)");
941 return Status::Failure;
942 }
943
944 std::shared_ptr<NetworkManager> network_manager = client.GetNetworkManager();
945
946 // Select the trade in the list
947 std::shared_ptr<ServerboundSelectTradePacket> select_trade_packet = std::make_shared<ServerboundSelectTradePacket>();
948 select_trade_packet->SetItem(trade_index);
949
950 network_manager->Send(select_trade_packet);
951
952 start = std::chrono::steady_clock::now();
953 // Wait until the output/input is set with the correct item
954 bool correct_items = false;
955 do
956 {
957 if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() > 5000)
958 {
959 LOG_WARNING("Something went wrong waiting trade selection (Timeout). Maybe an item was missing?");
960 return Status::Failure;
961 }
962
963 correct_items = (buy && trading_container->GetSlot(2).GetItemId() == item_id) ||
964 (!buy && !trading_container->GetSlot(2).IsEmptySlot()
965 && (trading_container->GetSlot(0).GetItemId() == item_id || trading_container->GetSlot(1).GetItemId() == item_id));
966 client.Yield();
967 } while (!correct_items);
968
969 // Check we have at least one empty slot to get back input remainings + outputs
970 std::vector<short> empty_slots(has_trade_second_item ? 3 : 2);
971 int empty_slots_index = 0;
972 {
973 auto slots = trading_container->GetLockedSlots();
974 for (const auto& [id, slot] : *slots)
975 {
976 if (id < trading_container->GetFirstPlayerInventorySlot())
977 {
978 continue;
979 }
980
981 if (slot.IsEmptySlot())
982 {
983 empty_slots[empty_slots_index] = id;
984 empty_slots_index++;
985 if (empty_slots_index == empty_slots.size())
986 {
987 break;
988 }
989 }
990 }
991 }
992 if (empty_slots_index == 0)
993 {
994 LOG_WARNING("No free space in inventory for trading to happen.");
995 return Status::Failure;
996 }
997 else if (empty_slots_index < empty_slots.size())
998 {
999 LOG_WARNING("Not enough free space in inventory for trading. Input items may be lost");
1000 }
1001
1002 // Get a copy of the original input slots to see when they'll change
1003 const Slot input_slot_1 = trading_container->GetSlot(0);
1004 const Slot input_slot_2 = trading_container->GetSlot(1);
1005
1006 // Get the output in the inventory
1007 if (SwapItemsInContainer(client, container_id, empty_slots[0], 2) == Status::Failure)
1008 {
1009 LOG_WARNING("Failed to swap output slot during trading attempt");
1010 return Status::Failure;
1011 }
1012
1013 // Wait for the server to update the input slots
1014 start = std::chrono::steady_clock::now();
1015 while (true)
1016 {
1017 if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() > 5000)
1018 {
1019 LOG_WARNING("Something went wrong waiting trade input update (Timeout).");
1020 return Status::Failure;
1021 }
1022
1023 if ((input_slot_1.IsEmptySlot() || input_slot_1.GetItemCount() != trading_container->GetSlot(0).GetItemCount()) &&
1024 (input_slot_2.IsEmptySlot() || input_slot_2.GetItemCount() != trading_container->GetSlot(1).GetItemCount()))
1025 {
1026 break;
1027 }
1028 client.Yield();
1029 }
1030
1031 // Get back the input remainings in the inventory
1032 for (int i = 0; i < empty_slots_index - 1; ++i)
1033 {
1034 if (SwapItemsInContainer(client, container_id, empty_slots[i + 1], i) == Status::Failure)
1035 {
1036 LOG_WARNING("Failed to swap slots " << i << " after trading attempt");
1037 return Status::Failure;
1038 }
1039 }
1040
1041 // If we are here, everything is fine (or should be),
1042 // remove 1 to the possible trade counter on the villager
1043 inventory_manager->IncrementMerchantOfferUse(trade_index);
1044
1045 return Status::Success;
1046 }
1047
1048 Status Trade(BehaviourClient& client, const int item_id, const bool buy, const int trade_id)
1049 {
1050 constexpr std::array variable_names = {
1051 "Trade.item_id",
1052 "Trade.buy",
1053 "Trade.trade_id"
1054 };
1055
1056 Blackboard& blackboard = client.GetBlackboard();
1057
1058 blackboard.Set<int>(variable_names[0], item_id);
1059 blackboard.Set<bool>(variable_names[1], buy);
1060 blackboard.Set<int>(variable_names[2], trade_id);
1061
1062 return TradeImpl(client, item_id, buy, trade_id);
1063 }
1064
1066 {
1067 constexpr std::array variable_names = {
1068 "Trade.item_id",
1069 "Trade.buy",
1070 "Trade.trade_id"
1071 };
1072
1073 Blackboard& blackboard = client.GetBlackboard();
1074
1075 // Mandatory
1076 const int item_id = blackboard.Get<int>(variable_names[0]);
1077 const bool buy = blackboard.Get<bool>(variable_names[1]);
1078
1079 //Optional
1080 const int trade_id = blackboard.Get<int>(variable_names[2], -1);
1081
1082 return TradeImpl(client, item_id, buy, trade_id);
1083 }
1084
1085
1086 Status TradeNameImpl(BehaviourClient& client, const std::string& item_name, const bool buy, const int trade_id)
1087 {
1088 // Get item id corresponding to the name
1089 const int item_id = AssetsManager::getInstance().GetItemID(item_name);
1090 if (item_id < 0)
1091 {
1092 LOG_WARNING("Trying to trade an unknown item");
1093 return Status::Failure;
1094 }
1095
1096 return TradeImpl(client, item_id, buy, trade_id);
1097 }
1098
1099 Status TradeName(BehaviourClient& client, const std::string& item_name, const bool buy, const int trade_id)
1100 {
1101 constexpr std::array variable_names = {
1102 "TradeName.item_name",
1103 "TradeName.buy",
1104 "TradeName.trade_id"
1105 };
1106
1107 Blackboard& blackboard = client.GetBlackboard();
1108
1109 blackboard.Set<std::string>(variable_names[0], item_name);
1110 blackboard.Set<bool>(variable_names[1], buy);
1111 blackboard.Set<int>(variable_names[2], trade_id);
1112
1113 return TradeNameImpl(client, item_name, buy, trade_id);
1114 }
1115
1117 {
1118 constexpr std::array variable_names = {
1119 "TradeName.item_name",
1120 "TradeName.buy",
1121 "TradeName.trade_id"
1122 };
1123
1124 Blackboard& blackboard = client.GetBlackboard();
1125
1126 // Mandatory
1127 const std::string& item_name = blackboard.Get<std::string>(variable_names[0]);
1128 const bool buy = blackboard.Get<bool>(variable_names[1]);
1129
1130 // Optional
1131 const int trade_id = blackboard.Get<int>(variable_names[2], -1);
1132
1133 return TradeNameImpl(client, item_name, buy, trade_id);
1134 }
1135#endif
1136
1137 Status CraftImpl(BehaviourClient& client, const std::array<std::array<ItemId, 3>, 3>& inputs, const bool allow_inventory_craft)
1138 {
1139 std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
1140
1141 int min_x = 3;
1142 int max_x = -1;
1143 int min_y = 3;
1144 int max_y = -1;
1145 bool use_inventory_craft = false;
1146 if (!allow_inventory_craft)
1147 {
1148 use_inventory_craft = false;
1149 min_x = 0;
1150 max_x = 2;
1151 min_y = 0;
1152 max_y = 2;
1153 }
1154 else
1155 {
1156 for (int y = 0; y < 3; ++y)
1157 {
1158 for (int x = 0; x < 3; ++x)
1159 {
1160#if PROTOCOL_VERSION < 350 /* < 1.13 */
1161 if (inputs[y][x].first != -1)
1162#else
1163 if (inputs[y][x] != -1)
1164#endif
1165 {
1166 min_x = std::min(x, min_x);
1167 max_x = std::max(x, max_x);
1168 min_y = std::min(y, min_y);
1169 max_y = std::max(y, max_y);
1170 }
1171 }
1172 }
1173
1174 use_inventory_craft = (max_x - min_x) < 2 && (max_y - min_y) < 2;
1175 }
1176
1177 int crafting_container_id = -1;
1178 // If we need a crafting table, make sure one is open
1179 if (!use_inventory_craft)
1180 {
1181 auto start = std::chrono::steady_clock::now();
1182 do
1183 {
1184 if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() > 5000)
1185 {
1186 LOG_WARNING("Something went wrong waiting craft opening (Timeout).");
1187 return Status::Failure;
1188 }
1189 crafting_container_id = inventory_manager->GetFirstOpenedWindowId();
1190 client.Yield();
1191 } while (crafting_container_id == -1);
1192 }
1193 else
1194 {
1195 crafting_container_id = Window::PLAYER_INVENTORY_INDEX;
1196 }
1197
1198 std::shared_ptr<Window> crafting_container = inventory_manager->GetWindow(crafting_container_id);
1199
1200 if (crafting_container == nullptr)
1201 {
1202 LOG_WARNING("Something went wrong during craft (window closed).");
1203 return Status::Failure;
1204 }
1205
1206 Slot output_slot_before;
1207 // For each input slot
1208 for (int y = min_y; y < max_y + 1; ++y)
1209 {
1210 for (int x = min_x; x < max_x + 1; ++x)
1211 {
1212 // Save the output slot just before the last input is set
1213 if (y == min_y && x == min_x)
1214 {
1215 output_slot_before = crafting_container->GetSlot(0);
1216 }
1217
1218 const int destination_slot = use_inventory_craft ? (1 + x - min_x + (y - min_y) * 2) : (1 + x + 3 * y);
1219
1220 int source_slot = -1;
1221 int source_quantity = -1;
1222 // Search for the required item in inventory
1223 {
1224 auto slots = crafting_container->GetLockedSlots();
1225 for (const auto& [id, slot] : *slots)
1226 {
1227 if (id < crafting_container->GetFirstPlayerInventorySlot())
1228 {
1229 continue;
1230 }
1231#if PROTOCOL_VERSION < 350 /* < 1.13 */
1232 if (slot.GetBlockId() == inputs[y][x].first && slot.GetItemDamage() == inputs[y][x].second)
1233#else
1234 if (slot.GetItemId() == inputs[y][x])
1235#endif
1236 {
1237 source_slot = id;
1238 source_quantity = slot.GetItemCount();
1239 break;
1240 }
1241 }
1242 }
1243
1244 if (source_slot == -1)
1245 {
1246 LOG_WARNING("Not enough source item [" << AssetsManager::getInstance().Items().at(inputs[y][x])->GetName() << "] found in inventory for crafting.");
1247 return Status::Failure;
1248 }
1249
1250 if (ClickSlotInContainer(client, crafting_container_id, source_slot, 0, 0) == Status::Failure)
1251 {
1252 LOG_WARNING("Error trying to pick source item [" << AssetsManager::getInstance().Items().at(inputs[y][x])->GetName() << "] during crafting");
1253 return Status::Failure;
1254 }
1255
1256 // Right click in the destination slot
1257 if (ClickSlotInContainer(client, crafting_container_id, destination_slot, 0, 1) == Status::Failure)
1258 {
1259 LOG_WARNING("Error trying to place source item [" << AssetsManager::getInstance().Items().at(inputs[y][x])->GetName() << "] during crafting");
1260 return Status::Failure;
1261 }
1262
1263 // Put back the remaining items in the origin slot
1264 if (source_quantity > 1)
1265 {
1266 if (ClickSlotInContainer(client, crafting_container_id, source_slot, 0, 0) == Status::Failure)
1267 {
1268 LOG_WARNING("Error trying to place back source item [" << AssetsManager::getInstance().Items().at(inputs[y][x])->GetName() << "] during crafting");
1269 return Status::Failure;
1270 }
1271 }
1272 }
1273 }
1274
1275 // Wait for the server to send the output change
1276 // TODO: with the recipe book, we could know without waiting
1277 auto start = std::chrono::steady_clock::now();
1278 while (true)
1279 {
1280 if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() > 5000)
1281 {
1282 LOG_WARNING("Something went wrong waiting craft output update (Timeout).");
1283 return Status::Failure;
1284 }
1285 if (!crafting_container->GetSlot(0).SameItem(output_slot_before))
1286 {
1287 break;
1288 }
1289 client.Yield();
1290 }
1291
1292 // All inputs are in place, output is ready, click on output
1293 if (ClickSlotInContainer(client, crafting_container_id, 0, 0, 0) == Status::Failure)
1294 {
1295 LOG_WARNING("Error trying to click on output during crafting");
1296 return Status::Failure;
1297 }
1298
1299 // Find an empty slot in inventory to place the cursor content
1300 int destination_slot = -999;
1301 {
1302 auto slots = crafting_container->GetLockedSlots();
1303 for (const auto& [id, slot] : *slots)
1304 {
1305 if (id < (use_inventory_craft ? Window::INVENTORY_STORAGE_START : crafting_container->GetFirstPlayerInventorySlot()))
1306 {
1307 continue;
1308 }
1309
1310 // If it fits in a slot (empty or with the same item)
1311 if (slot.IsEmptySlot() ||
1312 (inventory_manager->GetCursor().GetItemId() == slot.GetItemId() &&
1313 slot.GetItemCount() < AssetsManager::getInstance().Items().at(slot.GetItemId())->GetStackSize() - 1)
1314 )
1315 {
1316 destination_slot = id;
1317 break;
1318 }
1319 }
1320 }
1321
1322 if (destination_slot == -999)
1323 {
1324 LOG_INFO("No available space for crafted item, will be thrown out");
1325 }
1326
1327 if (ClickSlotInContainer(client, crafting_container_id, destination_slot, 0, 0) == Status::Failure)
1328 {
1329 LOG_WARNING("Error trying to put back output during crafting");
1330 return Status::Failure;
1331 }
1332
1333 return Status::Success;
1334 }
1335
1336#if PROTOCOL_VERSION < 350 /* < 1.13 */
1337 Status Craft(BehaviourClient& client, const std::array<std::array<std::pair<int, unsigned char>, 3>, 3>& inputs, const bool allow_inventory_craft)
1338#else
1339 Status Craft(BehaviourClient& client, const std::array<std::array<int, 3>, 3>& inputs, const bool allow_inventory_craft)
1340#endif
1341 {
1342 constexpr std::array variable_names = {
1343 "Craft.inputs",
1344 "Craft.allow_inventory_craft"
1345 };
1346
1347 Blackboard& blackboard = client.GetBlackboard();
1348
1349#if PROTOCOL_VERSION < 350 /* < 1.13 */
1350 blackboard.Set<std::array<std::array<std::pair<int, unsigned char>, 3>, 3>>(variable_names[0], inputs);
1351#else
1352 blackboard.Set<std::array<std::array<int, 3>, 3>>(variable_names[0], inputs);
1353#endif
1354 blackboard.Set<bool>(variable_names[1], allow_inventory_craft);
1355
1356 return CraftImpl(client, inputs, allow_inventory_craft);
1357 }
1358
1360 {
1361 constexpr std::array variable_names = {
1362 "Craft.inputs",
1363 "Craft.allow_inventory_craft"
1364 };
1365
1366 Blackboard& blackboard = client.GetBlackboard();
1367
1368 // Mandatory
1369#if PROTOCOL_VERSION < 350 /* < 1.13 */
1370 const std::array<std::array<std::pair<int, unsigned char>, 3>, 3>& inputs = blackboard.Get<std::array<std::array<std::pair<int, unsigned char>, 3>, 3>>(variable_names[0]);
1371#else
1372 const std::array<std::array<int, 3>, 3>& inputs = blackboard.Get<std::array<std::array<int, 3>, 3>>(variable_names[0]);
1373#endif
1374
1375 // Optional
1376 const bool allow_inventory_craft = blackboard.Get<bool>(variable_names[1], true);
1377
1378 return CraftImpl(client, inputs, allow_inventory_craft);
1379 }
1380
1381
1382 Status CraftNamedImpl(BehaviourClient& client, const std::array<std::array<std::string, 3>, 3>& inputs, const bool allow_inventory_craft)
1383 {
1384 const AssetsManager& assets_manager = AssetsManager::getInstance();
1385#if PROTOCOL_VERSION < 350 /* < 1.13 */
1386 std::array<std::array<std::pair<int, unsigned char>, 3>, 3> inputs_ids;
1387#else
1388 std::array<std::array<int, 3>, 3> inputs_ids;
1389#endif
1390 for (size_t i = 0; i < 3; ++i)
1391 {
1392 for (size_t j = 0; j < 3; ++j)
1393 {
1394#if PROTOCOL_VERSION < 350 /* < 1.13 */
1395 inputs_ids[i][j] = inputs[i][j] == "" ? std::pair<int, unsigned char>{ -1, 0 } : assets_manager.GetItemID(inputs[i][j]);
1396#else
1397 inputs_ids[i][j] = inputs[i][j] == "" ? -1 : assets_manager.GetItemID(inputs[i][j]);
1398#endif
1399 }
1400 }
1401 return Craft(client, inputs_ids, allow_inventory_craft);
1402 }
1403
1404 Status CraftNamed(BehaviourClient& client, const std::array<std::array<std::string, 3>, 3>& inputs, const bool allow_inventory_craft)
1405 {
1406 constexpr std::array variable_names = {
1407 "CraftNamed.inputs",
1408 "CraftNamed.allow_inventory_craft"
1409 };
1410
1411 Blackboard& blackboard = client.GetBlackboard();
1412
1413 blackboard.Set<std::array<std::array<std::string, 3>, 3>>(variable_names[0], inputs);
1414 blackboard.Set<bool>(variable_names[1], allow_inventory_craft);
1415
1416 return CraftNamedImpl(client, inputs, allow_inventory_craft);
1417 }
1418
1420 {
1421 constexpr std::array variable_names = {
1422 "CraftNamed.inputs",
1423 "CraftNamed.allow_inventory_craft"
1424 };
1425
1426 Blackboard& blackboard = client.GetBlackboard();
1427
1428 // Mandatory
1429 const std::array<std::array<std::string, 3>, 3>& inputs = blackboard.Get<std::array<std::array<std::string, 3>, 3>>(variable_names[0]);
1430
1431 // Optional
1432 const bool allow_inventory_craft = blackboard.Get<bool>(variable_names[1], true);
1433
1434 return CraftNamedImpl(client, inputs, allow_inventory_craft);
1435 }
1436
1437
1438 Status HasItemInInventoryImpl(BehaviourClient& client, const ItemId item_id, const int quantity)
1439 {
1440 std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
1441
1442 int quantity_sum = 0;
1443 {
1444 auto slots = inventory_manager->GetPlayerInventory()->GetLockedSlots();
1445 for (const auto& [id, slot] : *slots)
1446 {
1448 {
1449 continue;
1450 }
1451
1452 if (!slot.IsEmptySlot() && item_id == slot.GetItemId())
1453 {
1454 quantity_sum += slot.GetItemCount();
1455 }
1456
1457 if (quantity_sum >= quantity)
1458 {
1459 return Status::Success;
1460 }
1461 }
1462 }
1463
1464 return Status::Failure;
1465 }
1466
1467 Status HasItemIdInInventory(BehaviourClient& client, const ItemId item_id, const int quantity)
1468 {
1469 constexpr std::array variable_names = {
1470 "HasItemIdInInventory.item_id",
1471 "HasItemIdInInventory.quantity"
1472 };
1473
1474 Blackboard& blackboard = client.GetBlackboard();
1475 blackboard.Set<ItemId>(variable_names[0], item_id);
1476 blackboard.Set<int>(variable_names[1], quantity);
1477
1478 return HasItemInInventoryImpl(client, item_id, quantity);
1479 }
1480
1482 {
1483 constexpr std::array variable_names = {
1484 "HasItemIdInInventory.item_id",
1485 "HasItemIdInInventory.quantity"
1486 };
1487
1488 Blackboard& blackboard = client.GetBlackboard();
1489
1490 // Mandatory
1491 const ItemId item_id = blackboard.Get<ItemId>(variable_names[0]);
1492
1493 // Optional
1494 const int quantity = blackboard.Get<int>(variable_names[1], 1);
1495
1496 return HasItemInInventoryImpl(client, item_id, quantity);
1497 }
1498
1499 Status HasItemInInventory(BehaviourClient& client, const std::string& item_name, const int quantity)
1500 {
1501 constexpr std::array variable_names = {
1502 "HasItemInInventory.item_name",
1503 "HasItemInInventory.quantity"
1504 };
1505
1506 Blackboard& blackboard = client.GetBlackboard();
1507 blackboard.Set<std::string>(variable_names[0], item_name);
1508 blackboard.Set<int>(variable_names[1], quantity);
1509
1510 const auto item_id = AssetsManager::getInstance().GetItemID(item_name);
1511
1512 return HasItemInInventoryImpl(client, item_id, quantity);
1513 }
1514
1516 {
1517 constexpr std::array variable_names = {
1518 "HasItemInInventory.item_name",
1519 "HasItemInInventory.quantity"
1520 };
1521
1522 Blackboard& blackboard = client.GetBlackboard();
1523
1524 // Mandatory
1525 const std::string& item_name = blackboard.Get<std::string>(variable_names[0]);
1526
1527 // Optional
1528 const int quantity = blackboard.Get<int>(variable_names[1], 1);
1529
1530 return HasItemInInventoryImpl(client, AssetsManager::getInstance().GetItemID(item_name), quantity);
1531 }
1532
1533
1535 {
1536 std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
1537 std::shared_ptr<Window> player_inventory = inventory_manager->GetPlayerInventory();
1538
1539 while (true)
1540 {
1541 short src_index = -1;
1542 short dst_index = -1;
1544 {
1545 const Slot dst_slot = player_inventory->GetSlot(i);
1546 if (dst_slot.IsEmptySlot())
1547 {
1548 continue;
1549 }
1550 // If this slot is not empty, and not full,
1551 // check if "upper" slot with same items that
1552 // could fit in it
1553 const int available_space = AssetsManager::getInstance().Items().at(dst_slot.GetItemId())->GetStackSize() - dst_slot.GetItemCount();
1554 if (available_space == 0)
1555 {
1556 continue;
1557 }
1558
1559 for (short j = i + 1; j < Window::INVENTORY_OFFHAND_INDEX + 1; ++j)
1560 {
1561 const Slot src_slot = player_inventory->GetSlot(j);
1562 if (!src_slot.IsEmptySlot()
1563 && dst_slot.SameItem(src_slot)
1564 && src_slot.GetItemCount() <= available_space)
1565 {
1566 src_index = j;
1567 break;
1568 }
1569 }
1570
1571 if (src_index != -1)
1572 {
1573 dst_index = i;
1574 break;
1575 }
1576 }
1577
1578 // Nothing to do
1579 if (src_index == -1 && dst_index == -1)
1580 {
1581 break;
1582 }
1583
1584 // Pick slot src, put it in dst
1586 {
1587 LOG_WARNING("Error trying to pick up slot during inventory sorting");
1588 return Status::Failure;
1589 }
1590
1592 {
1593 LOG_WARNING("Error trying to put down slot during inventory sorting");
1594 return Status::Failure;
1595 }
1596 }
1597
1598 return Status::Success;
1599 }
1600}
#define LOG_WARNING(osstream)
Definition Logger.hpp:44
#define LOG_INFO(osstream)
Definition Logger.hpp:43
#define LOG(osstream, level)
Definition Logger.hpp:28
const std::unordered_map< ItemId, std::unique_ptr< Item > > & Items() const
static AssetsManager & getInstance()
ItemId GetItemID(const std::string &item_name) const
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.
const std::string & GetName() const
std::shared_ptr< NetworkManager > GetNetworkManager() const
std::shared_ptr< EntityManager > GetEntityManager() const
std::shared_ptr< PhysicsManager > GetPhysicsManager() const
std::shared_ptr< InventoryManager > GetInventoryManager() const
int SendInventoryTransaction(const std::shared_ptr< ProtocolCraft::ServerboundContainerClickPacket > &transaction)
std::shared_ptr< World > GetWorld() const
static constexpr short INVENTORY_HOTBAR_START
Definition Window.hpp:26
static constexpr short INVENTORY_STORAGE_START
Definition Window.hpp:25
static constexpr short PLAYER_INVENTORY_INDEX
Definition Window.hpp:16
static constexpr short INVENTORY_OFFHAND_INDEX
Definition Window.hpp:27
bool IsEmptySlot() const
Definition Slot.hpp:92
bool SameItem(const Slot &s) const
Definition Slot.hpp:68
Status CraftNamedImpl(BehaviourClient &client, const std::array< std::array< std::string, 3 >, 3 > &inputs, const bool allow_inventory_craft)
Status OpenContainerImpl(BehaviourClient &client, const Position &pos)
Status PutOneItemInContainerSlot(BehaviourClient &client, const short container_id, const short source_slot, const short destination_slot)
Take one item from source_slot, and put it on destination_slot.
Status ClickSlotInContainerImpl(BehaviourClient &client, const short container_id, const short slot_id, const int click_type, const char button_num)
Status HasItemInInventoryBlackboard(BehaviourClient &client)
Same thing as HasItemInInventory, but reads its parameters from the blackboard.
Status Craft(BehaviourClient &client, const std::array< std::array< ItemId, 3 >, 3 > &inputs, const bool allow_inventory_craft=true)
Put item in a crafting container and click on the output, storing it in the inventory.
Status TradeNameBlackboard(BehaviourClient &client)
Same thing as TradeName, but reads its parameters from the blackboard.
Status HasItemIdInInventory(BehaviourClient &client, const ItemId item_id, const int quantity=1)
Check if item_id is present in inventory.
Status TradeBlackboard(BehaviourClient &client)
Same thing as Trade, but reads its parameters from the blackboard.
Status SetItemIdInHandBlackboard(BehaviourClient &client)
Same thing as SetItemIdInHand, but reads its parameters from the blackboard.
Status DropItemsFromContainer(BehaviourClient &client, const short container_id, const short slot_id, const short num_to_keep=0)
Drop item out of inventory.
Status EatBlackboard(BehaviourClient &client)
Same thing as Eat, but reads its parameters from the blackboard.
Status CloseContainer(BehaviourClient &client, const short container_id=-1)
Close an opened container.
Status SetItemInHandBlackboard(BehaviourClient &client)
Same thing as SetItemInHand, but reads its parameters from the blackboard.
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 HasItemInInventory(BehaviourClient &client, const std::string &item_name, const int quantity=1)
Check if item_name is present in inventory.
Status PlaceBlockImpl(BehaviourClient &client, const std::string &item_name, const Position &pos, std::optional< PlayerDiggingFace > face, const bool wait_confirmation, const bool allow_midair_placing, const bool allow_pathfinding)
Status LogInventoryContent(BehaviourClient &client, const LogLevel level=LogLevel::Info)
Log all the inventory content at given log level.
Status SwapItemsInContainer(BehaviourClient &client, const short container_id, const short first_slot, const short second_slot)
Swap two slots in a given container.
Status CraftNamed(BehaviourClient &client, const std::array< std::array< std::string, 3 >, 3 > &inputs, const bool allow_inventory_craft=true)
Put item in a crafting container and click on the output, storing it in the inventory.
Status SetItemInHandImpl(BehaviourClient &client, const ItemId item_id, const Hand hand)
Status CraftBlackboard(BehaviourClient &client)
Same thing as Craft, but reads its parameters from the blackboard.
Status ClickSlotInContainerBlackboard(BehaviourClient &client)
Same thing as ClickSlotInContainer, but reads its parameters from the blackboard.
Status HasItemIdInInventoryBlackboard(BehaviourClient &client)
Same thing as HasItemIdInInventory, but reads its parameters from the blackboard.
Status PutOneItemInContainerSlotBlackboard(BehaviourClient &client)
Same thing as PutOneItemInContainerSlot, but reads its parameters from the blackboard.
Vector3< int > Position
Definition Vector3.hpp:282
Status LookAt(BehaviourClient &client, const Vector3< double > &target, const bool set_pitch=true, const bool sync_to_server=true)
Turn the camera to look at a given target and send the new rotation to the server.
Status InteractWithBlock(BehaviourClient &client, const Position &pos, const PlayerDiggingFace face=PlayerDiggingFace::Up, const bool animation=true)
Interact (right click) with the block at the given location.
Status PlaceBlock(BehaviourClient &client, const std::string &item_name, const Position &pos, std::optional< PlayerDiggingFace > face=std::nullopt, const bool wait_confirmation=false, const bool allow_midair_placing=false, const bool allow_pathfinding=true)
Try to place the item at given pos.
Status SortInventory(BehaviourClient &client)
Clean the inventory stacking same items together.
Status HasItemInInventoryImpl(BehaviourClient &client, const ItemId item_id, const int quantity)
int ItemId
Definition Item.hpp:15
Status DropItemsFromContainerImpl(BehaviourClient &client, const short container_id, const short slot_id, const short num_to_keep)
Status TradeImpl(BehaviourClient &client, const int item_id, const bool buy, const int trade_id)
Status SetItemInHand(BehaviourClient &client, const std::string &item_name, const Hand hand=Hand::Right)
Try to set a given item in the given hand.
Status CraftImpl(BehaviourClient &client, const std::array< std::array< ItemId, 3 >, 3 > &inputs, const bool allow_inventory_craft)
Status CloseContainerImpl(BehaviourClient &client, const short container_id)
Status OpenContainer(BehaviourClient &client, const Position &pos)
Open a container at a given position.
Status Eat(BehaviourClient &client, const std::string &food_name, const bool wait_confirmation=true)
Search for food item in the inventory and eat it.
Status ClickSlotInContainer(BehaviourClient &client, const short container_id, const short slot_id, const int click_type, const char button_num)
Perform a click action on a container.
Status DropItemsFromContainerBlackboard(BehaviourClient &client)
Same thing as DropItemsFromContainer, but reads its parameters from the blackboard.
Status LogInventoryContentBlackboard(BehaviourClient &client)
Same thing as LogInventoryContent, but reads its parameters from the blackboard.
Status PlaceBlockBlackboard(BehaviourClient &client)
Same thing as PlaceBlock, but reads its parameters from the blackboard.
Status CloseContainerBlackboard(BehaviourClient &client)
Same thing as CloseContainer, but reads its parameters from the blackboard.
Status LogInventoryContentImpl(BehaviourClient &client, const LogLevel level)
Status OpenContainerBlackboard(BehaviourClient &client)
Same thing as OpenContainer, but reads its parameters from the blackboard.
Status EatImpl(BehaviourClient &client, const std::string &food_name, const bool wait_confirmation)
Status SwapItemsInContainerBlackboard(BehaviourClient &client)
Same thing as SwapItemsInContainer, but reads its parameters from the blackboard.
Status Trade(BehaviourClient &client, const int item_id, const bool buy, const int trade_id=-1)
Buy or sell an item, assuming a trading window is currently opened.
Status TradeNameImpl(BehaviourClient &client, const std::string &item_name, const bool buy, const int trade_id)
Status SwapItemsInContainerImpl(BehaviourClient &client, const short container_id, const short first_slot, const short second_slot)
Status TradeName(BehaviourClient &client, const std::string &item_name, const bool buy, const int trade_id=-1)
Buy or sell an item, assuming a trading window is currently opened.
Status SetItemIdInHand(BehaviourClient &client, const ItemId item_id, const Hand hand=Hand::Right)
Try to set a given item in the given hand.
Status CraftNamedBlackboard(BehaviourClient &client)
Same thing as CraftNamed, but reads its parameters from the blackboard.
Status PutOneItemInContainerSlotImpl(BehaviourClient &client, const short container_id, const short source_slot, const short destination_slot)
double SqrDist(const Vector3 &v) const
Definition Vector3.hpp:192
ProtocolCraft::NetworkPosition ToNetworkPosition() const
Definition Vector3.hpp:272