Botcraft 26.3
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#if PROTOCOL_VERSION < 777 /* < 26.3 */
618 std::shared_ptr<ServerboundSwingPacket> swing = std::make_shared<ServerboundSwingPacket>();
619 swing->SetHand(static_cast<int>(Hand::Right));
620 network_manager->Send(swing);
621#endif
622
623 if (!wait_confirmation)
624 {
625 return Status::Success;
626 }
627
628 bool is_block_ok = false;
629 bool is_slot_ok = true;
630 auto start = std::chrono::steady_clock::now();
631 const double ms_per_tick = client.GetPhysicsManager()->GetMsPerTick();
632 while (!is_block_ok || !is_slot_ok)
633 {
634 if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() >= 60.0 * ms_per_tick)
635 {
636 LOG_WARNING('[' << network_manager->GetMyName() << "] "
637 << "Something went wrong waiting block placement confirmation at " << pos << " (Timeout). "
638 << "Block ok: " << is_block_ok << " Slot ok: " << is_slot_ok
639 );
640 return Status::Failure;
641 }
642 if (!is_block_ok)
643 {
644 const Blockstate* block = world->GetBlock(pos);
645
646 if (block != nullptr && block->GetName() == item_name)
647 {
648 is_block_ok = true;
649 }
650 }
651 if (!is_slot_ok)
652 {
653 const int new_num_item_in_hand = inventory_manager->GetPlayerInventory()->GetSlot(Window::INVENTORY_HOTBAR_START + inventory_manager->GetIndexHotbarSelected()).GetItemCount();
654 is_slot_ok = new_num_item_in_hand == num_item_in_hand - 1;
655 }
656
657 if (is_block_ok && is_slot_ok)
658 {
659 return Status::Success;
660 }
661
662 client.Yield();
663 }
664
665 return Status::Success;
666 }
667
668 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)
669 {
670 constexpr std::array variable_names = {
671 "PlaceBlock.item_name",
672 "PlaceBlock.pos",
673 "PlaceBlock.face",
674 "PlaceBlock.wait_confirmation",
675 "PlaceBlock.allow_midair_placing",
676 "PlaceBlock.allow_pathfinding",
677 };
678
679 Blackboard& blackboard = client.GetBlackboard();
680
681 blackboard.Set<std::string>(variable_names[0], item_name);
682 blackboard.Set<Position>(variable_names[1], pos);
683 blackboard.Set<std::optional<PlayerDiggingFace>>(variable_names[2], face);
684 blackboard.Set<bool>(variable_names[3], wait_confirmation);
685 blackboard.Set<bool>(variable_names[4], allow_midair_placing);
686 blackboard.Set<bool>(variable_names[5], allow_pathfinding);
687
688 return PlaceBlockImpl(client, item_name, pos, face, wait_confirmation, allow_midair_placing, allow_pathfinding);
689 }
690
692 {
693 constexpr std::array variable_names = {
694 "PlaceBlock.item_name",
695 "PlaceBlock.pos",
696 "PlaceBlock.face",
697 "PlaceBlock.wait_confirmation",
698 "PlaceBlock.allow_midair_placing",
699 "PlaceBlock.allow_pathfinding",
700 };
701
702 Blackboard& blackboard = client.GetBlackboard();
703
704 // Mandatory
705 const std::string& item_name = blackboard.Get<std::string>(variable_names[0]);
706 const Position& pos = blackboard.Get<Position>(variable_names[1]);
707
708 // Optional
709 const std::optional<PlayerDiggingFace> face = blackboard.Get<std::optional<PlayerDiggingFace>>(variable_names[2], PlayerDiggingFace::Up);
710 const bool wait_confirmation = blackboard.Get<bool>(variable_names[3], false);
711 const bool allow_midair_placing = blackboard.Get<bool>(variable_names[4], false);
712 const bool allow_pathfinding = blackboard.Get<bool>(variable_names[5], true);
713
714
715 return PlaceBlockImpl(client, item_name, pos, face, wait_confirmation, allow_midair_placing, allow_pathfinding);
716 }
717
718
719 Status EatImpl(BehaviourClient& client, const std::string& food_name, const bool wait_confirmation)
720 {
721 if (SetItemInHand(client, food_name, Hand::Left) == Status::Failure)
722 {
723 return Status::Failure;
724 }
725
726 std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
727 std::shared_ptr<NetworkManager> network_manager = client.GetNetworkManager();
728
729 const char current_stack_size = inventory_manager->GetOffHand().GetItemCount();
730 std::shared_ptr<ServerboundUseItemPacket> use_item_packet = std::make_shared<ServerboundUseItemPacket>();
731 use_item_packet->SetHand(static_cast<int>(Hand::Left));
732#if PROTOCOL_VERSION > 758 /* > 1.18.2 */
733 use_item_packet->SetSequence(client.GetWorld()->GetNextWorldInteractionSequenceId());
734#endif
735 network_manager->Send(use_item_packet);
736
737 if (!wait_confirmation)
738 {
739 return Status::Success;
740 }
741
742 auto start = std::chrono::steady_clock::now();
743 const double ms_per_tick = client.GetPhysicsManager()->GetMsPerTick();
744 while (inventory_manager->GetOffHand().GetItemCount() == current_stack_size)
745 {
746 if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() >= 60.0 * ms_per_tick)
747 {
748 LOG_WARNING("Something went wrong trying to eat (Timeout).");
749 return Status::Failure;
750 }
751 client.Yield();
752 }
753
754 return Status::Success;
755 }
756
757 Status Eat(BehaviourClient& client, const std::string& food_name, const bool wait_confirmation)
758 {
759 constexpr std::array variable_names = {
760 "Eat.food_name",
761 "Eat.wait_confirmation"
762 };
763
764 Blackboard& blackboard = client.GetBlackboard();
765
766 blackboard.Set<std::string>(variable_names[0], food_name);
767 blackboard.Set<bool>(variable_names[1], wait_confirmation);
768
769 return EatImpl(client, food_name, wait_confirmation);
770 }
771
773 {
774 constexpr std::array variable_names = {
775 "Eat.food_name",
776 "Eat.wait_confirmation"
777 };
778
779 Blackboard& blackboard = client.GetBlackboard();
780
781 // Mandatory
782 const std::string& food_name = blackboard.Get<std::string>(variable_names[0]);
783
784 // Optional
785 const bool wait_confirmation = blackboard.Get<bool>(variable_names[1], false);
786
787
788 return EatImpl(client, food_name, wait_confirmation);
789 }
790
791
793 {
794 // Open the container
796 {
797 return Status::Failure;
798 }
799
800 std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
801
802 // Wait for a window to be opened
803 auto start = std::chrono::steady_clock::now();
804 while (inventory_manager->GetFirstOpenedWindowId() == -1)
805 {
806 if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() >= 3000)
807 {
808 LOG_WARNING("Something went wrong trying to open container (Timeout).");
809 return Status::Failure;
810 }
811 client.Yield();
812 }
813
814 return Status::Success;
815 }
816
818 {
819 constexpr std::array variable_names = {
820 "OpenContainer.pos"
821 };
822
823 Blackboard& blackboard = client.GetBlackboard();
824
825 blackboard.Set<Position>(variable_names[0], pos);
826
827 return OpenContainerImpl(client, pos);
828 }
829
831 {
832 constexpr std::array variable_names = {
833 "OpenContainer.pos"
834 };
835
836 Blackboard& blackboard = client.GetBlackboard();
837
838 // Mandatory
839 const Position& pos = blackboard.Get<Position>(variable_names[0]);
840
841
842 return OpenContainerImpl(client, pos);
843 }
844
845
846 Status CloseContainerImpl(BehaviourClient& client, const short container_id)
847 {
848 std::shared_ptr<NetworkManager> network_manager = client.GetNetworkManager();
849 std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
850
851 std::shared_ptr<ServerboundContainerClosePacket> close_container_packet = std::make_shared<ServerboundContainerClosePacket>();
852 short true_container_id = container_id;
853 if (true_container_id < 0)
854 {
855 true_container_id = inventory_manager->GetFirstOpenedWindowId();
856 }
857 close_container_packet->SetContainerId(static_cast<unsigned char>(true_container_id));
858 network_manager->Send(close_container_packet);
859
860 // There is no confirmation from the server, so we
861 // can simply close the window here
862 inventory_manager->EraseInventory(true_container_id);
863
864 return Status::Success;
865 }
866
867 Status CloseContainer(BehaviourClient& client, const short container_id)
868 {
869 constexpr std::array variable_names = {
870 "CloseContainer.container_id"
871 };
872
873 Blackboard& blackboard = client.GetBlackboard();
874
875 blackboard.Set<short>(variable_names[0], container_id);
876
877 return CloseContainerImpl(client, container_id);
878 }
879
881 {
882 constexpr std::array variable_names = {
883 "CloseContainer.container_id"
884 };
885
886 Blackboard& blackboard = client.GetBlackboard();
887
888 // Optional
889 const short container_id = blackboard.Get<short>(variable_names[0], -1);
890
891
892 return CloseContainerImpl(client, container_id);
893 }
894
895
897 {
898 std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
899
900 std::stringstream output;
901 {
902 output << "Cursor --> " << inventory_manager->GetCursor().Serialize().Dump() << "\n";
903 auto slots = inventory_manager->GetPlayerInventory()->GetLockedSlots();
904 for (const auto& [id, slot] : *slots)
905 {
906 output << id << " --> " << slot.Serialize().Dump() << "\n";
907 }
908 }
909 LOG(output.str(), level);
910 return Status::Success;
911 }
912
914 {
915 constexpr std::array variable_names = {
916 "LogInventoryContent.level"
917 };
918
919 Blackboard& blackboard = client.GetBlackboard();
920
921 blackboard.Set<LogLevel>(variable_names[0], level);
922
923 return LogInventoryContentImpl(client, level);
924 }
925
927 {
928 constexpr std::array variable_names = {
929 "LogInventoryContent.level"
930 };
931
932 Blackboard& blackboard = client.GetBlackboard();
933
934 //Optional
935 const LogLevel level = blackboard.Get<LogLevel>(variable_names[0], LogLevel::Info);
936
937 return LogInventoryContentImpl(client, level);
938 }
939
940
941#if PROTOCOL_VERSION > 451 /* > 1.13.2 */
942 Status TradeImpl(BehaviourClient& client, const int item_id, const bool buy, const int trade_id)
943 {
944 std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
945
946 // Make sure a trading window is opened and
947 // possible trades are available
948 auto start = std::chrono::steady_clock::now();
949 size_t num_trades = 0;
950 do
951 {
952 if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() > 5000)
953 {
954 LOG_WARNING("Something went wrong waiting trade opening (Timeout).");
955 return Status::Failure;
956 }
957
958 num_trades = inventory_manager->GetAvailableMerchantOffers().size();
959 client.Yield();
960 } while (num_trades <= 0 || inventory_manager->GetFirstOpenedWindowId() == -1);
961
962 const short container_id = inventory_manager->GetFirstOpenedWindowId();
963 std::shared_ptr<Window> trading_container = inventory_manager->GetWindow(container_id);
964
965 if (trading_container == nullptr)
966 {
967 LOG_WARNING("Something went wrong during trade (window closed).");
968 return Status::Failure;
969 }
970
971 int trade_index = trade_id;
972 bool has_trade_second_item = false;
973 const std::vector<ProtocolCraft::MerchantOffer> trades = inventory_manager->GetAvailableMerchantOffers();
974
975 // Find which trade we want in the list
976 if (trade_id == -1)
977 {
978 for (int i = 0; i < trades.size(); ++i)
979 {
980 if ((buy && trades[i].GetOutputItem().GetItemId() == item_id)
981 || (!buy && trades[i].GetInputItem1().GetItemId() == item_id))
982 {
983 trade_index = i;
984 has_trade_second_item = trades[i].GetInputItem2().has_value();
985 break;
986 }
987 }
988 }
989
990 if (trade_index == -1)
991 {
992 LOG_WARNING("Failed trading (this villager does not sell/buy " << AssetsManager::getInstance().Items().at(item_id)->GetName() << ")");
993 return Status::Failure;
994 }
995
996 // Check that the trade is not locked
997 if (trades[trade_index].GetNumberOfTradesUses() >= trades[trade_index].GetMaximumNumberOfTradeUses())
998 {
999 LOG_WARNING("Failed trading (trade locked)");
1000 return Status::Failure;
1001 }
1002
1003 std::shared_ptr<NetworkManager> network_manager = client.GetNetworkManager();
1004
1005 // Select the trade in the list
1006 std::shared_ptr<ServerboundSelectTradePacket> select_trade_packet = std::make_shared<ServerboundSelectTradePacket>();
1007 select_trade_packet->SetItem(trade_index);
1008
1009 network_manager->Send(select_trade_packet);
1010
1011 start = std::chrono::steady_clock::now();
1012 // Wait until the output/input is set with the correct item
1013 bool correct_items = false;
1014 do
1015 {
1016 if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() > 5000)
1017 {
1018 LOG_WARNING("Something went wrong waiting trade selection (Timeout). Maybe an item was missing?");
1019 return Status::Failure;
1020 }
1021
1022 correct_items = (buy && trading_container->GetSlot(2).GetItemId() == item_id) ||
1023 (!buy && !trading_container->GetSlot(2).IsEmptySlot()
1024 && (trading_container->GetSlot(0).GetItemId() == item_id || trading_container->GetSlot(1).GetItemId() == item_id));
1025 client.Yield();
1026 } while (!correct_items);
1027
1028 // Check we have at least one empty slot to get back input remainings + outputs
1029 std::vector<short> empty_slots(has_trade_second_item ? 3 : 2);
1030 int empty_slots_index = 0;
1031 {
1032 auto slots = trading_container->GetLockedSlots();
1033 for (const auto& [id, slot] : *slots)
1034 {
1035 if (id < trading_container->GetFirstPlayerInventorySlot())
1036 {
1037 continue;
1038 }
1039
1040 if (slot.IsEmptySlot())
1041 {
1042 empty_slots[empty_slots_index] = id;
1043 empty_slots_index++;
1044 if (empty_slots_index == empty_slots.size())
1045 {
1046 break;
1047 }
1048 }
1049 }
1050 }
1051 if (empty_slots_index == 0)
1052 {
1053 LOG_WARNING("No free space in inventory for trading to happen.");
1054 return Status::Failure;
1055 }
1056 else if (empty_slots_index < empty_slots.size())
1057 {
1058 LOG_WARNING("Not enough free space in inventory for trading. Input items may be lost");
1059 }
1060
1061 // Get a copy of the original input slots to see when they'll change
1062 const Slot input_slot_1 = trading_container->GetSlot(0);
1063 const Slot input_slot_2 = trading_container->GetSlot(1);
1064
1065 // Get the output in the inventory
1066 if (SwapItemsInContainer(client, container_id, empty_slots[0], 2) == Status::Failure)
1067 {
1068 LOG_WARNING("Failed to swap output slot during trading attempt");
1069 return Status::Failure;
1070 }
1071
1072 // Wait for the server to update the input slots
1073 start = std::chrono::steady_clock::now();
1074 while (true)
1075 {
1076 if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() > 5000)
1077 {
1078 LOG_WARNING("Something went wrong waiting trade input update (Timeout).");
1079 return Status::Failure;
1080 }
1081
1082 if ((input_slot_1.IsEmptySlot() || input_slot_1.GetItemCount() != trading_container->GetSlot(0).GetItemCount()) &&
1083 (input_slot_2.IsEmptySlot() || input_slot_2.GetItemCount() != trading_container->GetSlot(1).GetItemCount()))
1084 {
1085 break;
1086 }
1087 client.Yield();
1088 }
1089
1090 // Get back the input remainings in the inventory
1091 for (int i = 0; i < empty_slots_index - 1; ++i)
1092 {
1093 if (SwapItemsInContainer(client, container_id, empty_slots[i + 1], i) == Status::Failure)
1094 {
1095 LOG_WARNING("Failed to swap slots " << i << " after trading attempt");
1096 return Status::Failure;
1097 }
1098 }
1099
1100 // If we are here, everything is fine (or should be),
1101 // remove 1 to the possible trade counter on the villager
1102 inventory_manager->IncrementMerchantOfferUse(trade_index);
1103
1104 return Status::Success;
1105 }
1106
1107 Status Trade(BehaviourClient& client, const int item_id, const bool buy, const int trade_id)
1108 {
1109 constexpr std::array variable_names = {
1110 "Trade.item_id",
1111 "Trade.buy",
1112 "Trade.trade_id"
1113 };
1114
1115 Blackboard& blackboard = client.GetBlackboard();
1116
1117 blackboard.Set<int>(variable_names[0], item_id);
1118 blackboard.Set<bool>(variable_names[1], buy);
1119 blackboard.Set<int>(variable_names[2], trade_id);
1120
1121 return TradeImpl(client, item_id, buy, trade_id);
1122 }
1123
1125 {
1126 constexpr std::array variable_names = {
1127 "Trade.item_id",
1128 "Trade.buy",
1129 "Trade.trade_id"
1130 };
1131
1132 Blackboard& blackboard = client.GetBlackboard();
1133
1134 // Mandatory
1135 const int item_id = blackboard.Get<int>(variable_names[0]);
1136 const bool buy = blackboard.Get<bool>(variable_names[1]);
1137
1138 //Optional
1139 const int trade_id = blackboard.Get<int>(variable_names[2], -1);
1140
1141 return TradeImpl(client, item_id, buy, trade_id);
1142 }
1143
1144
1145 Status TradeNameImpl(BehaviourClient& client, const std::string& item_name, const bool buy, const int trade_id)
1146 {
1147 // Get item id corresponding to the name
1148 const int item_id = AssetsManager::getInstance().GetItemID(item_name);
1149 if (item_id < 0)
1150 {
1151 LOG_WARNING("Trying to trade an unknown item");
1152 return Status::Failure;
1153 }
1154
1155 return TradeImpl(client, item_id, buy, trade_id);
1156 }
1157
1158 Status TradeName(BehaviourClient& client, const std::string& item_name, const bool buy, const int trade_id)
1159 {
1160 constexpr std::array variable_names = {
1161 "TradeName.item_name",
1162 "TradeName.buy",
1163 "TradeName.trade_id"
1164 };
1165
1166 Blackboard& blackboard = client.GetBlackboard();
1167
1168 blackboard.Set<std::string>(variable_names[0], item_name);
1169 blackboard.Set<bool>(variable_names[1], buy);
1170 blackboard.Set<int>(variable_names[2], trade_id);
1171
1172 return TradeNameImpl(client, item_name, buy, trade_id);
1173 }
1174
1176 {
1177 constexpr std::array variable_names = {
1178 "TradeName.item_name",
1179 "TradeName.buy",
1180 "TradeName.trade_id"
1181 };
1182
1183 Blackboard& blackboard = client.GetBlackboard();
1184
1185 // Mandatory
1186 const std::string& item_name = blackboard.Get<std::string>(variable_names[0]);
1187 const bool buy = blackboard.Get<bool>(variable_names[1]);
1188
1189 // Optional
1190 const int trade_id = blackboard.Get<int>(variable_names[2], -1);
1191
1192 return TradeNameImpl(client, item_name, buy, trade_id);
1193 }
1194#endif
1195
1196 Status CraftImpl(BehaviourClient& client, const std::array<std::array<ItemId, 3>, 3>& inputs, const bool allow_inventory_craft)
1197 {
1198 std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
1199 const ItemId air_id = AssetsManager::getInstance().GetItemID("minecraft:air");
1200
1201 int min_x = 3;
1202 int max_x = -1;
1203 int min_y = 3;
1204 int max_y = -1;
1205 bool use_inventory_craft = false;
1206 if (!allow_inventory_craft)
1207 {
1208 use_inventory_craft = false;
1209 min_x = 0;
1210 max_x = 2;
1211 min_y = 0;
1212 max_y = 2;
1213 }
1214 else
1215 {
1216 for (int y = 0; y < 3; ++y)
1217 {
1218 for (int x = 0; x < 3; ++x)
1219 {
1220 // Ignore undefined or air item ids
1221#if PROTOCOL_VERSION < 350 /* < 1.13 */
1222 if (inputs[y][x].first != -1 && inputs[y][x] != air_id)
1223#else
1224 if (inputs[y][x] != -1 && inputs[y][x] != air_id)
1225#endif
1226 {
1227 min_x = std::min(x, min_x);
1228 max_x = std::max(x, max_x);
1229 min_y = std::min(y, min_y);
1230 max_y = std::max(y, max_y);
1231 }
1232 }
1233 }
1234
1235 use_inventory_craft = (max_x - min_x) < 2 && (max_y - min_y) < 2;
1236 }
1237
1238 int crafting_container_id = -1;
1239 // If we need a crafting table, make sure one is open
1240 if (!use_inventory_craft)
1241 {
1242 auto start = std::chrono::steady_clock::now();
1243 do
1244 {
1245 if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() > 5000)
1246 {
1247 LOG_WARNING("Something went wrong waiting craft opening (Timeout).");
1248 return Status::Failure;
1249 }
1250 crafting_container_id = inventory_manager->GetFirstOpenedWindowId();
1251 client.Yield();
1252 } while (crafting_container_id == -1);
1253 }
1254 else
1255 {
1256 crafting_container_id = Window::PLAYER_INVENTORY_INDEX;
1257 }
1258
1259 std::shared_ptr<Window> crafting_container = inventory_manager->GetWindow(crafting_container_id);
1260
1261 if (crafting_container == nullptr)
1262 {
1263 LOG_WARNING("Something went wrong during craft (window closed).");
1264 return Status::Failure;
1265 }
1266
1267 Slot output_slot_before;
1268 // TODO: do we need to also empty the air slots? We only place items one by one so it shouldn't happen
1269 // when using CraftImpl, but some users may do it manually.
1270 // For each input slot
1271 for (int y = min_y; y < max_y + 1; ++y)
1272 {
1273 for (int x = min_x; x < max_x + 1; ++x)
1274 {
1275 const int destination_slot = use_inventory_craft ? (1 + x - min_x + (y - min_y) * 2) : (1 + x + 3 * y);
1276
1277 // Skip undefined or air item ids
1278#if PROTOCOL_VERSION < 350 /* < 1.13 */
1279 if (inputs[y][x].first == -1 || inputs[y][x] == air_id)
1280#else
1281 if (inputs[y][x] == -1 || inputs[y][x] == air_id)
1282#endif
1283 {
1284 continue;
1285 }
1286
1287 // Save the output slot before adding the input
1288 // so we know when the server sends the output update
1289 output_slot_before = crafting_container->GetSlot(0);
1290
1291 int source_slot = -1;
1292 int source_quantity = -1;
1293 // Search for the required item in inventory
1294 {
1295 auto slots = crafting_container->GetLockedSlots();
1296 for (const auto& [id, slot] : *slots)
1297 {
1298 if (id < crafting_container->GetFirstPlayerInventorySlot())
1299 {
1300 continue;
1301 }
1302#if PROTOCOL_VERSION < 350 /* < 1.13 */
1303 if (slot.GetBlockId() == inputs[y][x].first && slot.GetItemDamage() == inputs[y][x].second)
1304#else
1305 if (slot.GetItemId() == inputs[y][x])
1306#endif
1307 {
1308 source_slot = id;
1309 source_quantity = slot.GetItemCount();
1310 break;
1311 }
1312 }
1313 }
1314
1315 if (source_slot == -1)
1316 {
1317 LOG_WARNING("Not enough source item [" << AssetsManager::getInstance().Items().at(inputs[y][x])->GetName() << "] found in inventory for crafting.");
1318 return Status::Failure;
1319 }
1320
1321 if (ClickSlotInContainer(client, crafting_container_id, source_slot, 0, 0) == Status::Failure)
1322 {
1323 LOG_WARNING("Error trying to pick source item [" << AssetsManager::getInstance().Items().at(inputs[y][x])->GetName() << "] during crafting");
1324 return Status::Failure;
1325 }
1326
1327 // Right click in the destination slot
1328 if (ClickSlotInContainer(client, crafting_container_id, destination_slot, 0, 1) == Status::Failure)
1329 {
1330 LOG_WARNING("Error trying to place source item [" << AssetsManager::getInstance().Items().at(inputs[y][x])->GetName() << "] during crafting");
1331 return Status::Failure;
1332 }
1333
1334 // Put back the remaining items in the origin slot
1335 if (source_quantity > 1)
1336 {
1337 if (ClickSlotInContainer(client, crafting_container_id, source_slot, 0, 0) == Status::Failure)
1338 {
1339 LOG_WARNING("Error trying to place back source item [" << AssetsManager::getInstance().Items().at(inputs[y][x])->GetName() << "] during crafting");
1340 return Status::Failure;
1341 }
1342 }
1343 }
1344 }
1345
1346 // Wait for the server to send the output change
1347 // TODO: with the recipe book, we could know without waiting
1348 auto start = std::chrono::steady_clock::now();
1349 while (true)
1350 {
1351 if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count() > 5000)
1352 {
1353 LOG_WARNING("Something went wrong waiting craft output update (Timeout).");
1354 return Status::Failure;
1355 }
1356 if (!crafting_container->GetSlot(0).SameItem(output_slot_before))
1357 {
1358 break;
1359 }
1360 client.Yield();
1361 }
1362
1363 // All inputs are in place, output is ready, click on output
1364 if (ClickSlotInContainer(client, crafting_container_id, 0, 0, 0) == Status::Failure)
1365 {
1366 LOG_WARNING("Error trying to click on output during crafting");
1367 return Status::Failure;
1368 }
1369
1370 // Find an empty slot in inventory to place the cursor content
1371 int destination_slot = -999;
1372 {
1373 auto slots = crafting_container->GetLockedSlots();
1374 for (const auto& [id, slot] : *slots)
1375 {
1376 if (id < (use_inventory_craft ? Window::INVENTORY_STORAGE_START : crafting_container->GetFirstPlayerInventorySlot()))
1377 {
1378 continue;
1379 }
1380
1381 // If it fits in a slot (empty or with the same item)
1382 if (slot.IsEmptySlot() ||
1383 (inventory_manager->GetCursor().GetItemId() == slot.GetItemId() &&
1384 slot.GetItemCount() < AssetsManager::getInstance().Items().at(slot.GetItemId())->GetStackSize() - 1)
1385 )
1386 {
1387 destination_slot = id;
1388 break;
1389 }
1390 }
1391 }
1392
1393 if (destination_slot == -999)
1394 {
1395 LOG_INFO("No available space for crafted item, will be thrown out");
1396 }
1397
1398 if (ClickSlotInContainer(client, crafting_container_id, destination_slot, 0, 0) == Status::Failure)
1399 {
1400 LOG_WARNING("Error trying to put back output during crafting");
1401 return Status::Failure;
1402 }
1403
1404 return Status::Success;
1405 }
1406
1407 Status Craft(BehaviourClient& client, const std::array<std::array<ItemId, 3>, 3>& inputs, const bool allow_inventory_craft)
1408 {
1409 constexpr std::array variable_names = {
1410 "Craft.inputs",
1411 "Craft.allow_inventory_craft"
1412 };
1413
1414 Blackboard& blackboard = client.GetBlackboard();
1415
1416 blackboard.Set<std::array<std::array<ItemId, 3>, 3>>(variable_names[0], inputs);
1417 blackboard.Set<bool>(variable_names[1], allow_inventory_craft);
1418
1419 return CraftImpl(client, inputs, allow_inventory_craft);
1420 }
1421
1423 {
1424 constexpr std::array variable_names = {
1425 "Craft.inputs",
1426 "Craft.allow_inventory_craft"
1427 };
1428
1429 Blackboard& blackboard = client.GetBlackboard();
1430
1431 // Mandatory
1432 const std::array<std::array<ItemId, 3>, 3>& inputs = blackboard.Get<std::array<std::array<ItemId, 3>, 3>>(variable_names[0]);
1433
1434 // Optional
1435 const bool allow_inventory_craft = blackboard.Get<bool>(variable_names[1], true);
1436
1437 return CraftImpl(client, inputs, allow_inventory_craft);
1438 }
1439
1440
1441 Status CraftNamedImpl(BehaviourClient& client, const std::array<std::array<std::string, 3>, 3>& inputs, const bool allow_inventory_craft)
1442 {
1443 const AssetsManager& assets_manager = AssetsManager::getInstance();
1444 std::array<std::array<ItemId, 3>, 3> inputs_ids;
1445 for (size_t i = 0; i < 3; ++i)
1446 {
1447 for (size_t j = 0; j < 3; ++j)
1448 {
1449#if PROTOCOL_VERSION < 350 /* < 1.13 */
1450 inputs_ids[i][j] = inputs[i][j] == "" ? std::pair<int, unsigned char>{ -1, 0 } : assets_manager.GetItemID(inputs[i][j]);
1451#else
1452 inputs_ids[i][j] = inputs[i][j] == "" ? -1 : assets_manager.GetItemID(inputs[i][j]);
1453#endif
1454 }
1455 }
1456 return CraftImpl(client, inputs_ids, allow_inventory_craft);
1457 }
1458
1459 Status CraftNamed(BehaviourClient& client, const std::array<std::array<std::string, 3>, 3>& inputs, const bool allow_inventory_craft)
1460 {
1461 constexpr std::array variable_names = {
1462 "CraftNamed.inputs",
1463 "CraftNamed.allow_inventory_craft"
1464 };
1465
1466 Blackboard& blackboard = client.GetBlackboard();
1467
1468 blackboard.Set<std::array<std::array<std::string, 3>, 3>>(variable_names[0], inputs);
1469 blackboard.Set<bool>(variable_names[1], allow_inventory_craft);
1470
1471 return CraftNamedImpl(client, inputs, allow_inventory_craft);
1472 }
1473
1475 {
1476 constexpr std::array variable_names = {
1477 "CraftNamed.inputs",
1478 "CraftNamed.allow_inventory_craft"
1479 };
1480
1481 Blackboard& blackboard = client.GetBlackboard();
1482
1483 // Mandatory
1484 const std::array<std::array<std::string, 3>, 3>& inputs = blackboard.Get<std::array<std::array<std::string, 3>, 3>>(variable_names[0]);
1485
1486 // Optional
1487 const bool allow_inventory_craft = blackboard.Get<bool>(variable_names[1], true);
1488
1489 return CraftNamedImpl(client, inputs, allow_inventory_craft);
1490 }
1491
1492
1493 Status HasItemInInventoryImpl(BehaviourClient& client, const ItemId item_id, const int quantity)
1494 {
1495 std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
1496
1497 int quantity_sum = 0;
1498 {
1499 auto slots = inventory_manager->GetPlayerInventory()->GetLockedSlots();
1500 for (const auto& [id, slot] : *slots)
1501 {
1503 {
1504 continue;
1505 }
1506
1507 if (!slot.IsEmptySlot() && item_id == slot.GetItemId())
1508 {
1509 quantity_sum += slot.GetItemCount();
1510 }
1511
1512 if (quantity_sum >= quantity)
1513 {
1514 return Status::Success;
1515 }
1516 }
1517 }
1518
1519 return Status::Failure;
1520 }
1521
1522 Status HasItemIdInInventory(BehaviourClient& client, const ItemId item_id, const int quantity)
1523 {
1524 constexpr std::array variable_names = {
1525 "HasItemIdInInventory.item_id",
1526 "HasItemIdInInventory.quantity"
1527 };
1528
1529 Blackboard& blackboard = client.GetBlackboard();
1530 blackboard.Set<ItemId>(variable_names[0], item_id);
1531 blackboard.Set<int>(variable_names[1], quantity);
1532
1533 return HasItemInInventoryImpl(client, item_id, quantity);
1534 }
1535
1537 {
1538 constexpr std::array variable_names = {
1539 "HasItemIdInInventory.item_id",
1540 "HasItemIdInInventory.quantity"
1541 };
1542
1543 Blackboard& blackboard = client.GetBlackboard();
1544
1545 // Mandatory
1546 const ItemId item_id = blackboard.Get<ItemId>(variable_names[0]);
1547
1548 // Optional
1549 const int quantity = blackboard.Get<int>(variable_names[1], 1);
1550
1551 return HasItemInInventoryImpl(client, item_id, quantity);
1552 }
1553
1554 Status HasItemInInventory(BehaviourClient& client, const std::string& item_name, const int quantity)
1555 {
1556 constexpr std::array variable_names = {
1557 "HasItemInInventory.item_name",
1558 "HasItemInInventory.quantity"
1559 };
1560
1561 Blackboard& blackboard = client.GetBlackboard();
1562 blackboard.Set<std::string>(variable_names[0], item_name);
1563 blackboard.Set<int>(variable_names[1], quantity);
1564
1565 const auto item_id = AssetsManager::getInstance().GetItemID(item_name);
1566
1567 return HasItemInInventoryImpl(client, item_id, quantity);
1568 }
1569
1571 {
1572 constexpr std::array variable_names = {
1573 "HasItemInInventory.item_name",
1574 "HasItemInInventory.quantity"
1575 };
1576
1577 Blackboard& blackboard = client.GetBlackboard();
1578
1579 // Mandatory
1580 const std::string& item_name = blackboard.Get<std::string>(variable_names[0]);
1581
1582 // Optional
1583 const int quantity = blackboard.Get<int>(variable_names[1], 1);
1584
1585 return HasItemInInventoryImpl(client, AssetsManager::getInstance().GetItemID(item_name), quantity);
1586 }
1587
1588
1590 {
1591 std::shared_ptr<InventoryManager> inventory_manager = client.GetInventoryManager();
1592 std::shared_ptr<Window> player_inventory = inventory_manager->GetPlayerInventory();
1593
1594 while (true)
1595 {
1596 short src_index = -1;
1597 short dst_index = -1;
1599 {
1600 const Slot dst_slot = player_inventory->GetSlot(i);
1601 if (dst_slot.IsEmptySlot())
1602 {
1603 continue;
1604 }
1605 // If this slot is not empty, and not full,
1606 // check if "upper" slot with same items that
1607 // could fit in it
1608 const int available_space = AssetsManager::getInstance().Items().at(dst_slot.GetItemId())->GetStackSize() - dst_slot.GetItemCount();
1609 if (available_space == 0)
1610 {
1611 continue;
1612 }
1613
1614 for (short j = i + 1; j < Window::INVENTORY_OFFHAND_INDEX + 1; ++j)
1615 {
1616 const Slot src_slot = player_inventory->GetSlot(j);
1617 if (!src_slot.IsEmptySlot()
1618 && dst_slot.SameItem(src_slot)
1619 && src_slot.GetItemCount() <= available_space)
1620 {
1621 src_index = j;
1622 break;
1623 }
1624 }
1625
1626 if (src_index != -1)
1627 {
1628 dst_index = i;
1629 break;
1630 }
1631 }
1632
1633 // Nothing to do
1634 if (src_index == -1 && dst_index == -1)
1635 {
1636 break;
1637 }
1638
1639 // Pick slot src, put it in dst
1641 {
1642 LOG_WARNING("Error trying to pick up slot during inventory sorting");
1643 return Status::Failure;
1644 }
1645
1647 {
1648 LOG_WARNING("Error trying to put down slot during inventory sorting");
1649 return Status::Failure;
1650 }
1651 }
1652
1653 return Status::Success;
1654 }
1655}
#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