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