Botcraft 26.1.2
Loading...
Searching...
No Matches
InventoryManager.cpp
Go to the documentation of this file.
5
6using namespace ProtocolCraft;
7
8namespace Botcraft
9{
11 {
12 std::scoped_lock<std::shared_mutex> lock(inventory_manager_mutex);
14 cursor = Slot();
16#if PROTOCOL_VERSION > 451 /* > 1.13.2 */
18#endif
19 }
20
21
22 void InventoryManager::SetSlot(const short window_id, const short index, const Slot& slot)
23 {
24 std::shared_ptr<Window> window = nullptr;
25
26 {
27 std::shared_lock<std::shared_mutex> lock(inventory_manager_mutex);
28 auto it = inventories.find(window_id);
29 if (it != inventories.end())
30 {
31 window = it->second;
32 }
33 }
34
35 if (window == nullptr)
36 {
37 // In 1.17+ we don't wait for any server confirmation, so this can potentially happen very often.
38#if PROTOCOL_VERSION < 755 /* < 1.17 */
39 LOG_WARNING("Trying to add item in an unknown window with id : " << window_id);
40#endif
41 }
42 else
43 {
44 window->SetSlot(index, slot);
45 }
46 }
47
48 std::shared_ptr<Window> InventoryManager::GetWindow(const short window_id) const
49 {
50 std::shared_lock<std::shared_mutex> lock(inventory_manager_mutex);
51 auto it = inventories.find(window_id);
52 if (it == inventories.end())
53 {
54 return nullptr;
55 }
56 return it->second;
57 }
58
59 std::shared_ptr<Window> InventoryManager::GetPlayerInventory() const
60 {
62 }
63
65 {
66 std::shared_lock<std::shared_mutex> lock(inventory_manager_mutex);
68 }
69
71 {
72 std::shared_lock<std::shared_mutex> lock(inventory_manager_mutex);
73 for (const auto& [id, ptr] : inventories)
74 {
76 ptr->GetSlots().size() > 0)
77 {
78 return id;
79 }
80 }
81
82 return -1;
83 }
84
86 {
87 std::shared_ptr<Window> inventory = GetPlayerInventory();
88
89 if (inventory == nullptr)
90 {
91 return Slot();
92 }
93
94 return inventory->GetSlot(Window::INVENTORY_HOTBAR_START + index_hotbar_selected);
95 }
96
98 {
99 std::shared_ptr<Window> inventory = GetPlayerInventory();
100
101 if (inventory == nullptr)
102 {
103 return Slot();
104 }
105
106 return inventory->GetSlot(Window::INVENTORY_OFFHAND_INDEX);
107 }
108
109 void InventoryManager::EraseInventory(const short window_id)
110 {
111 std::scoped_lock<std::shared_mutex> lock(inventory_manager_mutex);
112#if PROTOCOL_VERSION < 755 /* < 1.17 */
113 pending_transactions.erase(window_id);
114 transaction_states.erase(window_id);
115#else
116 // In versions > 1.17 we have to synchronize the player inventory
117 // when a container is closed, as the server does not send info
119#endif
120 // Some non-vanilla server sends a CloseContainer packet on death
121 // Player inventory should not be removed from the map
122 if (window_id == Window::PLAYER_INVENTORY_INDEX)
123 {
125 }
126 else
127 {
128 inventories.erase(window_id);
129 }
130
131#if PROTOCOL_VERSION > 451 /* > 1.13.2 */
132 if (window_id == trading_container_id)
133 {
135 available_trades.clear();
136 }
137#endif
138 }
139
140#if PROTOCOL_VERSION < 755 /* < 1.17 */
141 TransactionState InventoryManager::GetTransactionState(const short window_id, const int transaction_id) const
142 {
143 std::shared_lock<std::shared_mutex> lock(inventory_manager_mutex);
144
145 auto it = transaction_states.find(window_id);
146 if (it == transaction_states.end())
147 {
148 LOG_ERROR("Asking state of a transaction for a closed window");
149 return TransactionState::Refused;
150 }
151
152 auto it2 = it->second.find(transaction_id);
153 if (it2 == it->second.end())
154 {
155 LOG_ERROR("Asking state of an unknown transaction");
156 return TransactionState::Refused;
157 }
158
159 return it2->second;
160 }
161
162 void InventoryManager::AddPendingTransaction(const InventoryTransaction& transaction)
163 {
164 std::scoped_lock<std::shared_mutex> lock(inventory_manager_mutex);
165
166 pending_transactions[transaction.packet->GetContainerId()].insert(std::make_pair(transaction.packet->GetUid(), transaction));
167 transaction_states[transaction.packet->GetContainerId()].insert(std::make_pair(transaction.packet->GetUid(), TransactionState::Waiting));
168
169 // Clean oldest transaction to avoid infinite growing map
170 for (auto it = transaction_states[transaction.packet->GetContainerId()].begin(); it != transaction_states[transaction.packet->GetContainerId()].end(); )
171 {
172 if (std::abs(it->first - transaction.packet->GetUid()) > 25)
173 {
174 it = transaction_states[transaction.packet->GetContainerId()].erase(it);
175 }
176 else
177 {
178 ++it;
179 }
180 }
181 }
182#else
184 {
185 // No lock as this is called from already scoped lock functions
186 if (window_id == Window::PLAYER_INVENTORY_INDEX)
187 {
188 return;
189 }
190
191 auto it = inventories.find(window_id);
192 if (it == inventories.end())
193 {
194 LOG_WARNING("Trying to synchronize inventory with a non existing container");
195 return;
196 }
197
198 short player_inventory_first_slot = it->second->GetFirstPlayerInventorySlot();
199 std::shared_ptr<Window> player_inventory = inventories[Window::PLAYER_INVENTORY_INDEX];
200
201 for (int i = 0; i < Window::INVENTORY_HOTBAR_START; ++i)
202 {
203 player_inventory->SetSlot(Window::INVENTORY_STORAGE_START + i, it->second->GetSlot(player_inventory_first_slot + i));
204 }
205 }
206#endif
207
208#if PROTOCOL_VERSION > 755 /* > 1.17 */
209 void InventoryManager::SetStateId(const short window_id, const int state_id)
210 {
211 std::shared_lock<std::shared_mutex> lock(inventory_manager_mutex);
212 auto it = inventories.find(window_id);
213
214 if (it != inventories.end())
215 {
216 it->second->SetStateId(state_id);
217 }
218 }
219#endif
220
221 void InventoryManager::AddInventory(const short window_id, const InventoryType window_type)
222 {
223 std::scoped_lock<std::shared_mutex> lock(inventory_manager_mutex);
224 inventories[window_id] = std::make_shared<Window>(window_type);
225#if PROTOCOL_VERSION < 755 /* < 1.17 */
226 pending_transactions[window_id] = std::map<short, InventoryTransaction >();
227 transaction_states[window_id] = std::map<short, TransactionState>();
228#endif
229 }
230
232 {
233 std::scoped_lock<std::shared_mutex> lock(inventory_manager_mutex);
234 index_hotbar_selected = index;
235 }
236
238 {
239 std::shared_lock<std::shared_mutex> lock(inventory_manager_mutex);
240 return cursor;
241 }
242
244 {
245 std::scoped_lock<std::shared_mutex> lock(inventory_manager_mutex);
246 cursor = c;
247 }
248
249 InventoryTransaction InventoryManager::PrepareTransaction(const std::shared_ptr<ServerboundContainerClickPacket>& transaction)
250 {
251 // Get the container
252 std::shared_ptr<Window> window = GetWindow(transaction->GetContainerId());
253
254 // Lock because we need read access to cursor
255 std::shared_lock<std::shared_mutex> lock(inventory_manager_mutex);
256
257 InventoryTransaction output{ transaction };
258 std::map<short, Slot> changed_slots;
259 Slot carried_item;
260
261 // Process the transaction
262
263 // Click on the output of a crafting container
264 if ((window->GetType() == InventoryType::PlayerInventory || window->GetType() == InventoryType::Crafting) &&
265 transaction->GetSlotNum() == 0)
266 {
267#if PROTOCOL_VERSION < 775 /* 26.1 */
268 if (transaction->GetClickType() != 0 && transaction->GetClickType() != 1)
269 {
270 LOG_ERROR("Transaction type '" << transaction->GetClickType() << "' not implemented.");
271 throw std::runtime_error("Non supported transaction type created");
272 }
273#else
274 if (transaction->GetContainerInput() != 0 && transaction->GetContainerInput() != 1)
275 {
276 LOG_ERROR("Transaction type '" << transaction->GetContainerInput() << "' not implemented.");
277 throw std::runtime_error("Non supported transaction type created");
278 }
279#endif
280
281 if (transaction->GetButtonNum() != 0 && transaction->GetButtonNum() != 1)
282 {
283 LOG_ERROR("Transaction button num '" << transaction->GetButtonNum() << "' not supported.");
284 throw std::runtime_error("Non supported transaction button created");
285 }
286
287 const Slot clicked_slot = window->GetSlot(transaction->GetSlotNum());
288 // If cursor is not empty, we can't click if the items are not the same,
289 if (!cursor.IsEmptySlot() && !cursor.SameItem(clicked_slot))
290 {
291 carried_item = cursor;
292 }
293 // We can't click if the crafted items don't fit in the stack
294 else if (!cursor.IsEmptySlot() && (cursor.GetItemCount() + clicked_slot.GetItemCount()) > AssetsManager::getInstance().Items().at(cursor.GetItemId())->GetStackSize())
295 {
296 carried_item = cursor;
297 }
298 else
299 {
300 carried_item = clicked_slot;
301 carried_item.SetItemCount(cursor.GetItemCount() + clicked_slot.GetItemCount());
302 changed_slots[0] = Slot();
303 for (int i = 1; i < (window->GetType() == InventoryType::Crafting ? 10 : 5); ++i)
304 {
305 Slot cloned_slot = window->GetSlot(i);
306 cloned_slot.SetItemCount(cloned_slot.GetItemCount() - 1);
307 changed_slots[i] = cloned_slot;
308 }
309 }
310 }
311 // Drop item
312 else if (transaction->GetSlotNum() == -999)
313 {
314#if PROTOCOL_VERSION < 775 /* 26.1 */
315 switch (transaction->GetClickType())
316#else
317 switch (transaction->GetContainerInput())
318#endif
319 {
320 case 0:
321 {
322 switch (transaction->GetButtonNum())
323 {
324 case 0:
325 {
326 carried_item = Slot();
327 break;
328 }
329 case 1:
330 {
331 carried_item = cursor;
332 carried_item.SetItemCount(cursor.GetItemCount() - 1);
333 break;
334 }
335 default:
336 {
337 break;
338 }
339 }
340 break;
341 }
342 default:
343 {
344#if PROTOCOL_VERSION < 775 /* 26.1 */
345 LOG_ERROR("Transaction type '" << transaction->GetClickType() << "' not implemented.");
346#else
347 LOG_ERROR("Transaction type '" << transaction->GetContainerInput() << "' not implemented.");
348#endif
349 throw std::runtime_error("Non supported transaction type created");
350 break;
351 }
352 }
353 }
354 // Normal case
355 else
356 {
357#if PROTOCOL_VERSION < 775 /* 26.1 */
358 switch (transaction->GetClickType())
359#else
360 switch (transaction->GetContainerInput())
361#endif
362 {
363 case 0:
364 {
365 const Slot clicked_slot = window->GetSlot(transaction->GetSlotNum());
366 switch (transaction->GetButtonNum())
367 {
368
369 case 0: // "Left click"
370 {
371 // Special case: left click with same item
372 if (!cursor.IsEmptySlot() && !clicked_slot.IsEmptySlot() && cursor.SameItem(clicked_slot))
373 {
374 const int sum_count = cursor.GetItemCount() + clicked_slot.GetItemCount();
375 const int max_stack_size = AssetsManager::getInstance().Items().at(cursor.GetItemId())->GetStackSize();
376 // The cursor becomes the clicked slot
377 carried_item = clicked_slot;
378 carried_item.SetItemCount(std::max(0, sum_count - max_stack_size));
379 // The content of the clicked slot becomes the cursor
380 changed_slots = { {transaction->GetSlotNum(), cursor} };
381 changed_slots.at(transaction->GetSlotNum()).SetItemCount(std::min(max_stack_size, sum_count));
382 }
383 else
384 {
385 // The cursor becomes the clicked slot
386 carried_item = clicked_slot;
387 // The content of the clicked slot becomes the cursor
388 changed_slots = { {transaction->GetSlotNum(), cursor} };
389 }
390 break;
391 }
392 case 1: // "Right Click"
393 {
394 // If cursor is empty, take half the stack
395 if (cursor.IsEmptySlot())
396 {
397 const int new_quantity = clicked_slot.GetItemCount() / 2;
398 carried_item = clicked_slot;
399 carried_item.SetItemCount(clicked_slot.GetItemCount() - new_quantity);
400 changed_slots = { {transaction->GetSlotNum(), clicked_slot} };
401 changed_slots.at(transaction->GetSlotNum()).SetItemCount(new_quantity);
402 }
403 // If clicked is empty, put one item in the slot
404 else if (clicked_slot.IsEmptySlot())
405 {
406 // Cursor is the same, minus one item
407 carried_item = cursor;
408 carried_item.SetItemCount(cursor.GetItemCount() - 1);
409 // Dest slot it the same, plus one item
410 changed_slots = { {transaction->GetSlotNum(), cursor} };
411 changed_slots.at(transaction->GetSlotNum()).SetItemCount(1);
412 }
413 // If same items in both
414 else if (cursor.SameItem(clicked_slot))
415 {
416 const int max_stack_size = AssetsManager::getInstance().Items().at(cursor.GetItemId())->GetStackSize();
417 const bool transfer = clicked_slot.GetItemCount() < max_stack_size;
418 // The cursor loses 1 item if possible
419 carried_item = clicked_slot;
420 carried_item.SetItemCount(transfer ? cursor.GetItemCount() - 1 : cursor.GetItemCount());
421 // The content of the clicked slot gains one item if possible
422 changed_slots = { {transaction->GetSlotNum(), cursor} };
423 changed_slots.at(transaction->GetSlotNum()).SetItemCount(transfer ? clicked_slot.GetItemCount() + 1 : clicked_slot.GetItemCount());
424 }
425 // Else just switch like a left click
426 else
427 {
428 // The cursor becomes the clicked slot
429 carried_item = clicked_slot;
430 // The content of the clicked slot becomes the cursor
431 changed_slots = { {transaction->GetSlotNum(), cursor} };
432 }
433 break;
434 }
435 default:
436 break;
437 }
438 break;
439 }
440 default:
441 {
442#if PROTOCOL_VERSION < 775 /* 26.1 */
443 LOG_ERROR("Transaction type '" << transaction->GetClickType() << "' not implemented.");
444#else
445 LOG_ERROR("Transaction type '" << transaction->GetContainerInput() << "' not implemented.");
446#endif
447 throw std::runtime_error("Non supported transaction type created");
448 break;
449 }
450 }
451 }
452
453#if PROTOCOL_VERSION < 755 /* < 1.17 */
454 transaction->SetCarriedItem(window->GetSlot(transaction->GetSlotNum()));
455 output.changed_slots = changed_slots;
456 output.carried_item = carried_item;
457
458 transaction->SetUid(window->GetNextTransactionId());
459#elif PROTOCOL_VERSION < 770 /* < 1.21.5 */
460 transaction->SetCarriedItem(carried_item);
461 transaction->SetChangedSlots(changed_slots);
462#if PROTOCOL_VERSION > 755 /* > 1.17 */
463 transaction->SetStateId(window->GetStateId());
464#endif
465#else
466 transaction->SetCarriedItem(static_cast<HashedSlot>(carried_item));
467 std::map<short, HashedSlot> hashed_changed_slots;
468 for (const auto& [k, v] : changed_slots)
469 {
470 hashed_changed_slots[k] = static_cast<HashedSlot>(v);
471 }
472 transaction->SetChangedSlots(hashed_changed_slots);
473 transaction->SetStateId(window->GetStateId());
474 // Store the real slots with the non-hashed components
475 output.changed_slots = changed_slots;
476 output.carried_item = carried_item;
477#endif
478 return output;
479 }
480
482 {
483#if PROTOCOL_VERSION < 755 /* < 1.17 */ || PROTOCOL_VERSION > 769 /* > 1.21.4 */
484 const std::map<short, Slot>& modified_slots = transaction.changed_slots;
485 cursor = transaction.carried_item;
486#else
487 const std::map<short, Slot>& modified_slots = transaction.packet->GetChangedSlots();
488 cursor = transaction.packet->GetCarriedItem();
489#endif
490
491 // Get the container
492 std::shared_ptr<Window> window = inventories[transaction.packet->GetContainerId()];
493 for (const auto& p : modified_slots)
494 {
495 window->SetSlot(p.first, p.second);
496 }
497 }
498
500 {
501 std::scoped_lock<std::shared_mutex> lock(inventory_manager_mutex);
502 ApplyTransactionImpl(transaction);
503 }
504
505#if PROTOCOL_VERSION > 451 /* > 1.13.2 */
506 std::vector<MerchantOffer> InventoryManager::GetAvailableMerchantOffers() const
507 {
508 std::shared_lock<std::shared_mutex> lock(inventory_manager_mutex);
509 return available_trades;
510 }
511
513 {
514 std::scoped_lock<std::shared_mutex> lock(inventory_manager_mutex);
515 if (index < 0 || index > available_trades.size())
516 {
517 LOG_WARNING("Trying to update trade use of an invalid trade (" << index << "<" << available_trades.size() << ")");
518 return;
519 }
520 MerchantOffer& trade = available_trades[index];
521 trade.SetNumberOfTradesUses(trade.GetNumberOfTradesUses() + 1);
522 }
523#endif
524
525
527 {
528 if (packet.GetContainerId() == -1 && packet.GetSlot() == -1)
529 {
530 SetCursor(packet.GetItemStack());
531 }
532 // Slot index is starting in the hotbar, THEN in the main storage in this case :)
533 else if (packet.GetContainerId() == -2)
534 {
536 {
537 SetSlot(Window::PLAYER_INVENTORY_INDEX, Window::INVENTORY_HOTBAR_START + packet.GetSlot(), packet.GetItemStack());
538 }
539 else
540 {
542 }
543 }
544 else if (packet.GetContainerId() >= 0)
545 {
546 SetSlot(packet.GetContainerId(), packet.GetSlot(), packet.GetItemStack());
547#if PROTOCOL_VERSION > 755 /* > 1.17 */
548 SetStateId(packet.GetContainerId(), packet.GetStateId());
549#endif
550 }
551 else
552 {
553 LOG_WARNING("Unknown window called during ClientboundContainerSetSlotPacket Handle : " << packet.GetContainerId() << ", " << packet.GetSlot());
554 }
555 }
556
558 {
559 std::shared_ptr<Window> window = GetWindow(packet.GetContainerId());
560 if (window != nullptr)
561 {
562 window->SetContent(packet.GetItems());
563 }
564
565#if PROTOCOL_VERSION > 755 /* > 1.17 */
566 if (packet.GetContainerId() >= 0)
567 {
568 SetStateId(packet.GetContainerId(), packet.GetStateId());
569 }
570#endif
571 }
572
574 {
575#if PROTOCOL_VERSION < 452 /* < 1.14 */
577 if (packet.GetType() == "minecraft:chest")
578 {
579 switch (packet.GetNumberOfSlots())
580 {
581 case 9*3:
583 break;
584 case 9*6:
586 break;
587 default:
588 LOG_ERROR("Not implemented chest type : " << packet.GetType());
589 break;
590 }
591 }
592 else if (packet.GetType() == "minecraft:crafting_table")
593 {
595 }
596 else
597 {
598 LOG_ERROR("Not implemented container type : " << packet.GetType());
599 }
600 AddInventory(packet.GetContainerId(), type);
601#else
602 AddInventory(packet.GetContainerId(), static_cast<InventoryType>(packet.GetType()));
603#endif
604 }
605
606#if PROTOCOL_VERSION < 768 /* < 1.21.2 */
607 void InventoryManager::Handle(ClientboundSetCarriedItemPacket& packet)
608#else
610#endif
611 {
612 SetHotbarSelected(packet.GetSlot());
613 }
614
615#if PROTOCOL_VERSION < 755 /* < 1.17 */
616 void InventoryManager::Handle(ClientboundContainerAckPacket& packet)
617 {
618 std::scoped_lock<std::shared_mutex> lock(inventory_manager_mutex);
619
620 // Update the new state of the transaction
621 auto it_container = transaction_states.find(packet.GetContainerId());
622 if (it_container == transaction_states.end())
623 {
624 transaction_states[packet.GetContainerId()] = std::map<short, TransactionState>();
625 it_container = transaction_states.find(packet.GetContainerId());
626 }
627 it_container->second[packet.GetUid()] = packet.GetAccepted() ? TransactionState::Accepted : TransactionState::Refused;
628
629 auto container_transactions = pending_transactions.find(packet.GetContainerId());
630
631 if (container_transactions == pending_transactions.end())
632 {
633 LOG_WARNING("The server accepted a transaction for an unknown container");
634 return;
635 }
636
637 auto transaction = container_transactions->second.find(packet.GetUid());
638
639 // Get the corresponding transaction
640 if (transaction == container_transactions->second.end())
641 {
642 LOG_WARNING("Server accepted an unknown transaction Uid");
643 return;
644 }
645
646 if (packet.GetAccepted())
647 {
648 ApplyTransactionImpl(transaction->second);
649 }
650
651 // Remove the transaction from the waiting state
652 container_transactions->second.erase(transaction);
653 }
654#endif
655
656#if PROTOCOL_VERSION > 451 /* > 1.13.2 */
658 {
659 std::scoped_lock<std::shared_mutex> lock(inventory_manager_mutex);
660 trading_container_id = packet.GetContainerId();
661 available_trades = packet.GetOffers();
662 }
663#endif
664
666 {
667 EraseInventory(static_cast<short>(packet.GetContainerId()));
668 }
669
670#if PROTOCOL_VERSION > 767 /* > 1.21.1 */
672 {
673 // Not sure about this one, I can't figure out when it's sent by the server
675 }
676
678 {
679 SetSlot(Window::PLAYER_INVENTORY_INDEX, static_cast<short>(packet.GetSlot()), packet.GetContents());
680 }
681#endif
682
683} //Botcraft
#define LOG_ERROR(osstream)
Definition Logger.hpp:45
#define LOG_WARNING(osstream)
Definition Logger.hpp:44
const std::unordered_map< ItemId, std::unique_ptr< Item > > & Items() const
static AssetsManager & getInstance()
ProtocolCraft::Slot GetOffHand() const
void SetStateId(const short window_id, const int state_id)
void SetHotbarSelected(const short index)
void AddInventory(const short window_id, const InventoryType window_type)
std::map< short, std::shared_ptr< Window > > inventories
void SynchronizeContainerPlayerInventory(const short window_id)
void SetSlot(const short window_id, const short index, const ProtocolCraft::Slot &slot)
std::vector< ProtocolCraft::MerchantOffer > available_trades
virtual void Handle(ProtocolCraft::ClientboundContainerSetSlotPacket &packet) override
std::vector< ProtocolCraft::MerchantOffer > GetAvailableMerchantOffers() const
std::shared_mutex inventory_manager_mutex
InventoryTransaction PrepareTransaction(const std::shared_ptr< ProtocolCraft::ServerboundContainerClickPacket > &transaction)
"think" about the changes made by this transaction, filling in the necessary values in the packet
void ApplyTransaction(const InventoryTransaction &transaction)
Apply a given transaction to a container.
std::shared_ptr< Window > GetWindow(const short window_id) const
ProtocolCraft::Slot GetHotbarSelected() const
void SetCursor(const ProtocolCraft::Slot &c)
void ApplyTransactionImpl(const InventoryTransaction &transaction)
void EraseInventory(const short window_id)
ProtocolCraft::Slot GetCursor() const
std::shared_ptr< Window > GetPlayerInventory() const
void IncrementMerchantOfferUse(const int index)
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
InventoryType
Definition Enums.hpp:223
std::shared_ptr< ProtocolCraft::ServerboundContainerClickPacket > packet
std::map< short, ProtocolCraft::Slot > changed_slots