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