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