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