Botcraft 1.21.5
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 (transaction->GetClickType() != 0 && transaction->GetClickType() != 1)
268 {
269 LOG_ERROR("Transaction type '" << transaction->GetClickType() << "' not implemented.");
270 throw std::runtime_error("Non supported transaction type created");
271 }
272
273 if (transaction->GetButtonNum() != 0 && transaction->GetButtonNum() != 1)
274 {
275 LOG_ERROR("Transaction button num '" << transaction->GetButtonNum() << "' not supported.");
276 throw std::runtime_error("Non supported transaction button created");
277 }
278
279 const Slot clicked_slot = window->GetSlot(transaction->GetSlotNum());
280 // If cursor is not empty, we can't click if the items are not the same,
281 if (!cursor.IsEmptySlot() && !cursor.SameItem(clicked_slot))
282 {
283 carried_item = cursor;
284 }
285 // We can't click if the crafted items don't fit in the stack
286 else if (!cursor.IsEmptySlot() && (cursor.GetItemCount() + clicked_slot.GetItemCount()) > AssetsManager::getInstance().Items().at(cursor.GetItemId())->GetStackSize())
287 {
288 carried_item = cursor;
289 }
290 else
291 {
292 carried_item = clicked_slot;
293 carried_item.SetItemCount(cursor.GetItemCount() + clicked_slot.GetItemCount());
294 changed_slots[0] = Slot();
295 for (int i = 1; i < (window->GetType() == InventoryType::Crafting ? 10 : 5); ++i)
296 {
297 Slot cloned_slot = window->GetSlot(i);
298 cloned_slot.SetItemCount(cloned_slot.GetItemCount() - 1);
299 changed_slots[i] = cloned_slot;
300 }
301 }
302 }
303 // Drop item
304 else if (transaction->GetSlotNum() == -999)
305 {
306 switch (transaction->GetClickType())
307 {
308 case 0:
309 {
310 switch (transaction->GetButtonNum())
311 {
312 case 0:
313 {
314 carried_item = Slot();
315 break;
316 }
317 case 1:
318 {
319 carried_item = cursor;
320 carried_item.SetItemCount(cursor.GetItemCount() - 1);
321 break;
322 }
323 default:
324 {
325 break;
326 }
327 }
328 break;
329 }
330 default:
331 {
332 LOG_ERROR("Transaction type '" << transaction->GetClickType() << "' not implemented.");
333 throw std::runtime_error("Non supported transaction type created");
334 break;
335 }
336 }
337 }
338 // Normal case
339 else
340 {
341 switch (transaction->GetClickType())
342 {
343 case 0:
344 {
345 const Slot clicked_slot = window->GetSlot(transaction->GetSlotNum());
346 switch (transaction->GetButtonNum())
347 {
348
349 case 0: // "Left click"
350 {
351 // Special case: left click with same item
352 if (!cursor.IsEmptySlot() && !clicked_slot.IsEmptySlot() && cursor.SameItem(clicked_slot))
353 {
354 const int sum_count = cursor.GetItemCount() + clicked_slot.GetItemCount();
355 const int max_stack_size = AssetsManager::getInstance().Items().at(cursor.GetItemId())->GetStackSize();
356 // The cursor becomes the clicked slot
357 carried_item = clicked_slot;
358 carried_item.SetItemCount(std::max(0, sum_count - max_stack_size));
359 // The content of the clicked slot becomes the cursor
360 changed_slots = { {transaction->GetSlotNum(), cursor} };
361 changed_slots.at(transaction->GetSlotNum()).SetItemCount(std::min(max_stack_size, sum_count));
362 }
363 else
364 {
365 // The cursor becomes the clicked slot
366 carried_item = clicked_slot;
367 // The content of the clicked slot becomes the cursor
368 changed_slots = { {transaction->GetSlotNum(), cursor} };
369 }
370 break;
371 }
372 case 1: // "Right Click"
373 {
374 // If cursor is empty, take half the stack
375 if (cursor.IsEmptySlot())
376 {
377 const int new_quantity = clicked_slot.GetItemCount() / 2;
378 carried_item = clicked_slot;
379 carried_item.SetItemCount(clicked_slot.GetItemCount() - new_quantity);
380 changed_slots = { {transaction->GetSlotNum(), clicked_slot} };
381 changed_slots.at(transaction->GetSlotNum()).SetItemCount(new_quantity);
382 }
383 // If clicked is empty, put one item in the slot
384 else if (clicked_slot.IsEmptySlot())
385 {
386 // Cursor is the same, minus one item
387 carried_item = cursor;
388 carried_item.SetItemCount(cursor.GetItemCount() - 1);
389 // Dest slot it the same, plus one item
390 changed_slots = { {transaction->GetSlotNum(), cursor} };
391 changed_slots.at(transaction->GetSlotNum()).SetItemCount(1);
392 }
393 // If same items in both
394 else if (cursor.SameItem(clicked_slot))
395 {
396 const int max_stack_size = AssetsManager::getInstance().Items().at(cursor.GetItemId())->GetStackSize();
397 const bool transfer = clicked_slot.GetItemCount() < max_stack_size;
398 // The cursor loses 1 item if possible
399 carried_item = clicked_slot;
400 carried_item.SetItemCount(transfer ? cursor.GetItemCount() - 1 : cursor.GetItemCount());
401 // The content of the clicked slot gains one item if possible
402 changed_slots = { {transaction->GetSlotNum(), cursor} };
403 changed_slots.at(transaction->GetSlotNum()).SetItemCount(transfer ? clicked_slot.GetItemCount() + 1 : clicked_slot.GetItemCount());
404 }
405 // Else just switch like a left click
406 else
407 {
408 // The cursor becomes the clicked slot
409 carried_item = clicked_slot;
410 // The content of the clicked slot becomes the cursor
411 changed_slots = { {transaction->GetSlotNum(), cursor} };
412 }
413 break;
414 }
415 default:
416 break;
417 }
418 break;
419 }
420 default:
421 {
422 LOG_ERROR("Transaction type '" << transaction->GetClickType() << "' not implemented.");
423 throw std::runtime_error("Non supported transaction type created");
424 break;
425 }
426 }
427 }
428
429#if PROTOCOL_VERSION < 755 /* < 1.17 */
430 transaction->SetCarriedItem(window->GetSlot(transaction->GetSlotNum()));
431 output.changed_slots = changed_slots;
432 output.carried_item = carried_item;
433
434 transaction->SetUid(window->GetNextTransactionId());
435#elif PROTOCOL_VERSION < 770 /* < 1.21.5 */
436 transaction->SetCarriedItem(carried_item);
437 transaction->SetChangedSlots(changed_slots);
438#if PROTOCOL_VERSION > 755 /* > 1.17 */
439 transaction->SetStateId(window->GetStateId());
440#endif
441#else
442 transaction->SetCarriedItem(static_cast<HashedSlot>(carried_item));
443 std::map<short, HashedSlot> hashed_changed_slots;
444 for (const auto& [k, v] : changed_slots)
445 {
446 hashed_changed_slots[k] = static_cast<HashedSlot>(v);
447 }
448 transaction->SetChangedSlots(hashed_changed_slots);
449 transaction->SetStateId(window->GetStateId());
450 // Store the real slots with the non-hashed components
451 output.changed_slots = changed_slots;
452 output.carried_item = carried_item;
453#endif
454 return output;
455 }
456
458 {
459#if PROTOCOL_VERSION < 755 /* < 1.17 */ || PROTOCOL_VERSION > 769 /* > 1.21.4 */
460 const std::map<short, Slot>& modified_slots = transaction.changed_slots;
461 cursor = transaction.carried_item;
462#else
463 const std::map<short, Slot>& modified_slots = transaction.packet->GetChangedSlots();
464 cursor = transaction.packet->GetCarriedItem();
465#endif
466
467 // Get the container
468 std::shared_ptr<Window> window = inventories[transaction.packet->GetContainerId()];
469 for (const auto& p : modified_slots)
470 {
471 window->SetSlot(p.first, p.second);
472 }
473 }
474
476 {
477 std::scoped_lock<std::shared_mutex> lock(inventory_manager_mutex);
478 ApplyTransactionImpl(transaction);
479 }
480
481#if PROTOCOL_VERSION > 451 /* > 1.13.2 */
482 std::vector<MerchantOffer> InventoryManager::GetAvailableMerchantOffers() const
483 {
484 std::shared_lock<std::shared_mutex> lock(inventory_manager_mutex);
485 return available_trades;
486 }
487
489 {
490 std::scoped_lock<std::shared_mutex> lock(inventory_manager_mutex);
491 if (index < 0 || index > available_trades.size())
492 {
493 LOG_WARNING("Trying to update trade use of an invalid trade (" << index << "<" << available_trades.size() << ")");
494 return;
495 }
496 MerchantOffer& trade = available_trades[index];
497 trade.SetNumberOfTradesUses(trade.GetNumberOfTradesUses() + 1);
498 }
499#endif
500
501
503 {
504 if (packet.GetContainerId() == -1 && packet.GetSlot() == -1)
505 {
506 SetCursor(packet.GetItemStack());
507 }
508 // Slot index is starting in the hotbar, THEN in the main storage in this case :)
509 else if (packet.GetContainerId() == -2)
510 {
512 {
513 SetSlot(Window::PLAYER_INVENTORY_INDEX, Window::INVENTORY_HOTBAR_START + packet.GetSlot(), packet.GetItemStack());
514 }
515 else
516 {
518 }
519 }
520 else if (packet.GetContainerId() >= 0)
521 {
522 SetSlot(packet.GetContainerId(), packet.GetSlot(), packet.GetItemStack());
523#if PROTOCOL_VERSION > 755 /* > 1.17 */
524 SetStateId(packet.GetContainerId(), packet.GetStateId());
525#endif
526 }
527 else
528 {
529 LOG_WARNING("Unknown window called during ClientboundContainerSetSlotPacket Handle : " << packet.GetContainerId() << ", " << packet.GetSlot());
530 }
531 }
532
534 {
535 std::shared_ptr<Window> window = GetWindow(packet.GetContainerId());
536 if (window != nullptr)
537 {
538 window->SetContent(packet.GetItems());
539 }
540
541#if PROTOCOL_VERSION > 755 /* > 1.17 */
542 if (packet.GetContainerId() >= 0)
543 {
544 SetStateId(packet.GetContainerId(), packet.GetStateId());
545 }
546#endif
547 }
548
550 {
551#if PROTOCOL_VERSION < 452 /* < 1.14 */
553 if (packet.GetType() == "minecraft:chest")
554 {
555 switch (packet.GetNumberOfSlots())
556 {
557 case 9*3:
559 break;
560 case 9*6:
562 break;
563 default:
564 LOG_ERROR("Not implemented chest type : " << packet.GetType());
565 break;
566 }
567 }
568 else if (packet.GetType() == "minecraft:crafting_table")
569 {
571 }
572 else
573 {
574 LOG_ERROR("Not implemented container type : " << packet.GetType());
575 }
576 AddInventory(packet.GetContainerId(), type);
577#else
578 AddInventory(packet.GetContainerId(), static_cast<InventoryType>(packet.GetType()));
579#endif
580 }
581
582#if PROTOCOL_VERSION < 768 /* < 1.21.2 */
583 void InventoryManager::Handle(ClientboundSetCarriedItemPacket& packet)
584#else
586#endif
587 {
588 SetHotbarSelected(packet.GetSlot());
589 }
590
591#if PROTOCOL_VERSION < 755 /* < 1.17 */
592 void InventoryManager::Handle(ClientboundContainerAckPacket& packet)
593 {
594 std::scoped_lock<std::shared_mutex> lock(inventory_manager_mutex);
595
596 // Update the new state of the transaction
597 auto it_container = transaction_states.find(packet.GetContainerId());
598 if (it_container == transaction_states.end())
599 {
600 transaction_states[packet.GetContainerId()] = std::map<short, TransactionState>();
601 it_container = transaction_states.find(packet.GetContainerId());
602 }
603 it_container->second[packet.GetUid()] = packet.GetAccepted() ? TransactionState::Accepted : TransactionState::Refused;
604
605 auto container_transactions = pending_transactions.find(packet.GetContainerId());
606
607 if (container_transactions == pending_transactions.end())
608 {
609 LOG_WARNING("The server accepted a transaction for an unknown container");
610 return;
611 }
612
613 auto transaction = container_transactions->second.find(packet.GetUid());
614
615 // Get the corresponding transaction
616 if (transaction == container_transactions->second.end())
617 {
618 LOG_WARNING("Server accepted an unknown transaction Uid");
619 return;
620 }
621
622 if (packet.GetAccepted())
623 {
624 ApplyTransactionImpl(transaction->second);
625 }
626
627 // Remove the transaction from the waiting state
628 container_transactions->second.erase(transaction);
629 }
630#endif
631
632#if PROTOCOL_VERSION > 451 /* > 1.13.2 */
634 {
635 std::scoped_lock<std::shared_mutex> lock(inventory_manager_mutex);
636 trading_container_id = packet.GetContainerId();
637 available_trades = packet.GetOffers();
638 }
639#endif
640
642 {
643 EraseInventory(static_cast<short>(packet.GetContainerId()));
644 }
645
646#if PROTOCOL_VERSION > 767 /* > 1.21.1 */
648 {
649 // Not sure about this one, I can't figure out when it's sent by the server
651 }
652
654 {
655 SetSlot(Window::PLAYER_INVENTORY_INDEX, static_cast<short>(packet.GetSlot()), packet.GetContents());
656 }
657#endif
658
659} //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:217
std::shared_ptr< ProtocolCraft::ServerboundContainerClickPacket > packet
std::map< short, ProtocolCraft::Slot > changed_slots