azalea_inventory/
operations.rs

1use std::ops::RangeInclusive;
2
3use azalea_buf::AzBuf;
4
5use crate::{
6    AnvilMenuLocation, BeaconMenuLocation, BlastFurnaceMenuLocation, BrewingStandMenuLocation,
7    CartographyTableMenuLocation, Crafter3x3MenuLocation, CraftingMenuLocation,
8    EnchantmentMenuLocation, FurnaceMenuLocation, Generic3x3MenuLocation, Generic9x1MenuLocation,
9    Generic9x2MenuLocation, Generic9x3MenuLocation, Generic9x4MenuLocation, Generic9x5MenuLocation,
10    Generic9x6MenuLocation, GrindstoneMenuLocation, HopperMenuLocation, ItemStack, ItemStackData,
11    LecternMenuLocation, LoomMenuLocation, Menu, MenuLocation, MerchantMenuLocation, Player,
12    PlayerMenuLocation, ShulkerBoxMenuLocation, SmithingMenuLocation, SmokerMenuLocation,
13    StonecutterMenuLocation, item::MaxStackSizeExt,
14};
15
16#[derive(Debug, Clone)]
17pub enum ClickOperation {
18    Pickup(PickupClick),
19    QuickMove(QuickMoveClick),
20    Swap(SwapClick),
21    Clone(CloneClick),
22    Throw(ThrowClick),
23    QuickCraft(QuickCraftClick),
24    PickupAll(PickupAllClick),
25}
26
27#[derive(Debug, Clone)]
28pub enum PickupClick {
29    /// Left mouse click. Note that in the protocol, None is represented as
30    /// -999.
31    Left { slot: Option<u16> },
32    /// Right mouse click. Note that in the protocol, None is represented as
33    /// -999.
34    Right { slot: Option<u16> },
35    /// Drop cursor stack.
36    LeftOutside,
37    /// Drop cursor single item.
38    RightOutside,
39}
40impl From<PickupClick> for ClickOperation {
41    fn from(click: PickupClick) -> Self {
42        ClickOperation::Pickup(click)
43    }
44}
45
46/// Shift click
47#[derive(Debug, Clone)]
48pub enum QuickMoveClick {
49    /// Shift + left mouse click
50    Left { slot: u16 },
51    /// Shift + right mouse click (identical behavior)
52    Right { slot: u16 },
53}
54impl From<QuickMoveClick> for ClickOperation {
55    fn from(click: QuickMoveClick) -> Self {
56        ClickOperation::QuickMove(click)
57    }
58}
59
60/// Used when you press number keys or F in an inventory.
61#[derive(Debug, Clone)]
62pub struct SwapClick {
63    pub source_slot: u16,
64    /// 0-8 for hotbar slots, 40 for offhand, everything else is treated as a
65    /// slot index.
66    pub target_slot: u8,
67}
68
69impl From<SwapClick> for ClickOperation {
70    fn from(click: SwapClick) -> Self {
71        ClickOperation::Swap(click)
72    }
73}
74/// Middle click, only defined for creative players in non-player
75/// inventories.
76#[derive(Debug, Clone)]
77pub struct CloneClick {
78    pub slot: u16,
79}
80impl From<CloneClick> for ClickOperation {
81    fn from(click: CloneClick) -> Self {
82        ClickOperation::Clone(click)
83    }
84}
85#[derive(Debug, Clone)]
86pub enum ThrowClick {
87    /// Drop key (Q)
88    Single { slot: u16 },
89    /// Ctrl + drop key (Q)
90    All { slot: u16 },
91}
92impl From<ThrowClick> for ClickOperation {
93    fn from(click: ThrowClick) -> Self {
94        ClickOperation::Throw(click)
95    }
96}
97#[derive(Debug, Clone, Eq, PartialEq)]
98pub struct QuickCraftClick {
99    pub kind: QuickCraftKind,
100    pub status: QuickCraftStatus,
101}
102#[derive(Debug, Clone, Eq, PartialEq)]
103pub enum QuickCraftKind {
104    Left,
105    Right,
106    Middle,
107}
108#[derive(Debug, Clone, Eq, PartialEq)]
109pub enum QuickCraftStatusKind {
110    /// Starting drag
111    Start,
112    /// Add slot
113    Add,
114    /// Ending drag
115    End,
116}
117#[derive(Debug, Clone, Eq, PartialEq)]
118pub enum QuickCraftStatus {
119    /// Starting drag
120    Start,
121    /// Add a slot.
122    Add { slot: u16 },
123    /// Ending drag
124    End,
125}
126impl From<QuickCraftStatus> for QuickCraftStatusKind {
127    fn from(status: QuickCraftStatus) -> Self {
128        match status {
129            QuickCraftStatus::Start => QuickCraftStatusKind::Start,
130            QuickCraftStatus::Add { .. } => QuickCraftStatusKind::Add,
131            QuickCraftStatus::End => QuickCraftStatusKind::End,
132        }
133    }
134}
135
136/// Double click
137#[derive(Debug, Clone)]
138pub struct PickupAllClick {
139    /// The slot that we're double clicking on. It should be empty or at least
140    /// not pickup-able (since the carried item is used as the filter).
141    pub slot: u16,
142    /// Impossible in vanilla clients.
143    pub reversed: bool,
144}
145impl From<PickupAllClick> for ClickOperation {
146    fn from(click: PickupAllClick) -> Self {
147        ClickOperation::PickupAll(click)
148    }
149}
150
151impl ClickOperation {
152    /// Return the slot number that this operation is acting on, if any.
153    ///
154    /// Note that in the protocol, "None" is represented as -999.
155    pub fn slot_num(&self) -> Option<u16> {
156        match self {
157            ClickOperation::Pickup(pickup) => match pickup {
158                PickupClick::Left { slot } => *slot,
159                PickupClick::Right { slot } => *slot,
160                PickupClick::LeftOutside => None,
161                PickupClick::RightOutside => None,
162            },
163            ClickOperation::QuickMove(quick_move) => match quick_move {
164                QuickMoveClick::Left { slot } => Some(*slot),
165                QuickMoveClick::Right { slot } => Some(*slot),
166            },
167            ClickOperation::Swap(swap) => Some(swap.source_slot),
168            ClickOperation::Clone(clone) => Some(clone.slot),
169            ClickOperation::Throw(throw) => match throw {
170                ThrowClick::Single { slot } => Some(*slot),
171                ThrowClick::All { slot } => Some(*slot),
172            },
173            ClickOperation::QuickCraft(quick_craft) => match quick_craft.status {
174                QuickCraftStatus::Start => None,
175                QuickCraftStatus::Add { slot } => Some(slot),
176                QuickCraftStatus::End => None,
177            },
178            ClickOperation::PickupAll(pickup_all) => Some(pickup_all.slot),
179        }
180    }
181
182    pub fn button_num(&self) -> u8 {
183        match self {
184            ClickOperation::Pickup(pickup) => match pickup {
185                PickupClick::Left { .. } => 0,
186                PickupClick::Right { .. } => 1,
187                PickupClick::LeftOutside => 0,
188                PickupClick::RightOutside => 1,
189            },
190            ClickOperation::QuickMove(quick_move) => match quick_move {
191                QuickMoveClick::Left { .. } => 0,
192                QuickMoveClick::Right { .. } => 1,
193            },
194            ClickOperation::Swap(swap) => swap.target_slot,
195            ClickOperation::Clone(_) => 2,
196            ClickOperation::Throw(throw) => match throw {
197                ThrowClick::Single { .. } => 0,
198                ThrowClick::All { .. } => 1,
199            },
200            ClickOperation::QuickCraft(quick_craft) => match quick_craft {
201                QuickCraftClick {
202                    kind: QuickCraftKind::Left,
203                    status: QuickCraftStatus::Start,
204                } => 0,
205                QuickCraftClick {
206                    kind: QuickCraftKind::Right,
207                    status: QuickCraftStatus::Start,
208                } => 4,
209                QuickCraftClick {
210                    kind: QuickCraftKind::Middle,
211                    status: QuickCraftStatus::Start,
212                } => 8,
213                QuickCraftClick {
214                    kind: QuickCraftKind::Left,
215                    status: QuickCraftStatus::Add { .. },
216                } => 1,
217                QuickCraftClick {
218                    kind: QuickCraftKind::Right,
219                    status: QuickCraftStatus::Add { .. },
220                } => 5,
221                QuickCraftClick {
222                    kind: QuickCraftKind::Middle,
223                    status: QuickCraftStatus::Add { .. },
224                } => 9,
225                QuickCraftClick {
226                    kind: QuickCraftKind::Left,
227                    status: QuickCraftStatus::End,
228                } => 2,
229                QuickCraftClick {
230                    kind: QuickCraftKind::Right,
231                    status: QuickCraftStatus::End,
232                } => 6,
233                QuickCraftClick {
234                    kind: QuickCraftKind::Middle,
235                    status: QuickCraftStatus::End,
236                } => 10,
237            },
238            ClickOperation::PickupAll(_) => 0,
239        }
240    }
241
242    pub fn click_type(&self) -> ClickType {
243        match self {
244            ClickOperation::Pickup(_) => ClickType::Pickup,
245            ClickOperation::QuickMove(_) => ClickType::QuickMove,
246            ClickOperation::Swap(_) => ClickType::Swap,
247            ClickOperation::Clone(_) => ClickType::Clone,
248            ClickOperation::Throw(_) => ClickType::Throw,
249            ClickOperation::QuickCraft(_) => ClickType::QuickCraft,
250            ClickOperation::PickupAll(_) => ClickType::PickupAll,
251        }
252    }
253}
254
255#[derive(AzBuf, Clone, Copy, Debug)]
256pub enum ClickType {
257    Pickup = 0,
258    QuickMove = 1,
259    Swap = 2,
260    Clone = 3,
261    Throw = 4,
262    QuickCraft = 5,
263    PickupAll = 6,
264}
265
266impl Menu {
267    /// Shift-click a slot in this menu.
268    ///
269    /// Keep in mind that this doesn't send any packets to the server, it just
270    /// mutates this specific `Menu`.
271    pub fn quick_move_stack(&mut self, slot_index: usize) -> ItemStack {
272        let slot = self.slot(slot_index);
273        if slot.is_none() {
274            return ItemStack::Empty;
275        };
276
277        let slot_location = self
278            .location_for_slot(slot_index)
279            .expect("we just checked to make sure the slot is Some above, so this shouldn't be able to error");
280        match slot_location {
281            MenuLocation::Player(l) => match l {
282                PlayerMenuLocation::CraftResult => {
283                    self.try_move_item_to_slots(slot_index, Player::INVENTORY_SLOTS);
284                }
285                PlayerMenuLocation::Craft => {
286                    self.try_move_item_to_slots(slot_index, Player::INVENTORY_SLOTS);
287                }
288                PlayerMenuLocation::Armor => {
289                    self.try_move_item_to_slots(slot_index, Player::INVENTORY_SLOTS);
290                }
291                _ => {
292                    // TODO: armor handling (see quickMoveStack in
293                    // InventoryMenu.java)
294
295                    // if slot.kind().is_armor() &&
296
297                    // also offhand handling
298
299                    if l == PlayerMenuLocation::Inventory {
300                        // shift-clicking in hotbar moves to inventory, and vice versa
301                        if Player::is_hotbar_slot(slot_index) {
302                            self.try_move_item_to_slots(
303                                slot_index,
304                                Player::INVENTORY_WITHOUT_HOTBAR_SLOTS,
305                            );
306                        } else {
307                            self.try_move_item_to_slots(slot_index, Player::HOTBAR_SLOTS);
308                        }
309                    } else {
310                        self.try_move_item_to_slots(slot_index, self.player_slots_range());
311                    }
312                }
313            },
314            MenuLocation::Generic9x1(l) => match l {
315                Generic9x1MenuLocation::Contents => {
316                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
317                }
318                Generic9x1MenuLocation::Player => {
319                    self.try_move_item_to_slots_or_toggle_hotbar(
320                        slot_index,
321                        Menu::GENERIC9X1_CONTENTS_SLOTS,
322                    );
323                }
324            },
325            MenuLocation::Generic9x2(l) => match l {
326                Generic9x2MenuLocation::Contents => {
327                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
328                }
329                Generic9x2MenuLocation::Player => {
330                    self.try_move_item_to_slots_or_toggle_hotbar(
331                        slot_index,
332                        Menu::GENERIC9X2_CONTENTS_SLOTS,
333                    );
334                }
335            },
336            MenuLocation::Generic9x3(l) => match l {
337                Generic9x3MenuLocation::Contents => {
338                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
339                }
340                Generic9x3MenuLocation::Player => {
341                    self.try_move_item_to_slots_or_toggle_hotbar(
342                        slot_index,
343                        Menu::GENERIC9X3_CONTENTS_SLOTS,
344                    );
345                }
346            },
347            MenuLocation::Generic9x4(l) => match l {
348                Generic9x4MenuLocation::Contents => {
349                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
350                }
351                Generic9x4MenuLocation::Player => {
352                    self.try_move_item_to_slots_or_toggle_hotbar(
353                        slot_index,
354                        Menu::GENERIC9X4_CONTENTS_SLOTS,
355                    );
356                }
357            },
358            MenuLocation::Generic9x5(l) => match l {
359                Generic9x5MenuLocation::Contents => {
360                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
361                }
362                Generic9x5MenuLocation::Player => {
363                    self.try_move_item_to_slots_or_toggle_hotbar(
364                        slot_index,
365                        Menu::GENERIC9X5_CONTENTS_SLOTS,
366                    );
367                }
368            },
369            MenuLocation::Generic9x6(l) => match l {
370                Generic9x6MenuLocation::Contents => {
371                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
372                }
373                Generic9x6MenuLocation::Player => {
374                    self.try_move_item_to_slots_or_toggle_hotbar(
375                        slot_index,
376                        Menu::GENERIC9X6_CONTENTS_SLOTS,
377                    );
378                }
379            },
380            MenuLocation::Generic3x3(l) => match l {
381                Generic3x3MenuLocation::Contents => {
382                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
383                }
384                Generic3x3MenuLocation::Player => {
385                    self.try_move_item_to_slots_or_toggle_hotbar(
386                        slot_index,
387                        Menu::GENERIC3X3_CONTENTS_SLOTS,
388                    );
389                }
390            },
391            MenuLocation::Crafter3x3(l) => match l {
392                Crafter3x3MenuLocation::Contents => {
393                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
394                }
395                Crafter3x3MenuLocation::Player => {
396                    self.try_move_item_to_slots_or_toggle_hotbar(
397                        slot_index,
398                        Menu::GENERIC3X3_CONTENTS_SLOTS,
399                    );
400                }
401            },
402            MenuLocation::Anvil(l) => match l {
403                AnvilMenuLocation::Player => {
404                    self.try_move_item_to_slots_or_toggle_hotbar(
405                        slot_index,
406                        Menu::ANVIL_FIRST_SLOT..=Menu::ANVIL_SECOND_SLOT,
407                    );
408                }
409                _ => {
410                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
411                }
412            },
413            MenuLocation::Beacon(l) => match l {
414                BeaconMenuLocation::Payment => {
415                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
416                }
417                BeaconMenuLocation::Player => {
418                    self.try_move_item_to_slots(
419                        slot_index,
420                        Menu::BEACON_PAYMENT_SLOT..=Menu::BEACON_PAYMENT_SLOT,
421                    );
422                }
423            },
424            MenuLocation::BlastFurnace(l) => match l {
425                BlastFurnaceMenuLocation::Player => {
426                    self.try_move_item_to_slots(
427                        slot_index,
428                        Menu::BLAST_FURNACE_INGREDIENT_SLOT..=Menu::BLAST_FURNACE_FUEL_SLOT,
429                    );
430                }
431                _ => {
432                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
433                }
434            },
435            MenuLocation::BrewingStand(l) => match l {
436                BrewingStandMenuLocation::Player => {
437                    self.try_move_item_to_slots(
438                        slot_index,
439                        *Menu::BREWING_STAND_BOTTLES_SLOTS.start()
440                            ..=Menu::BREWING_STAND_INGREDIENT_SLOT,
441                    );
442                }
443                _ => {
444                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
445                }
446            },
447            MenuLocation::Crafting(l) => match l {
448                CraftingMenuLocation::Player => {
449                    self.try_move_item_to_slots_or_toggle_hotbar(
450                        slot_index,
451                        Menu::CRAFTING_GRID_SLOTS,
452                    );
453                }
454                _ => {
455                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
456                }
457            },
458            MenuLocation::Enchantment(l) => match l {
459                EnchantmentMenuLocation::Player => {
460                    self.try_move_item_to_slots_or_toggle_hotbar(
461                        slot_index,
462                        Menu::ENCHANTMENT_ITEM_SLOT..=Menu::ENCHANTMENT_LAPIS_SLOT,
463                    );
464                }
465                _ => {
466                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
467                }
468            },
469            MenuLocation::Furnace(l) => match l {
470                FurnaceMenuLocation::Player => {
471                    self.try_move_item_to_slots(
472                        slot_index,
473                        Menu::FURNACE_INGREDIENT_SLOT..=Menu::FURNACE_FUEL_SLOT,
474                    );
475                }
476                _ => {
477                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
478                }
479            },
480            MenuLocation::Grindstone(l) => match l {
481                GrindstoneMenuLocation::Player => {
482                    self.try_move_item_to_slots_or_toggle_hotbar(
483                        slot_index,
484                        Menu::GRINDSTONE_INPUT_SLOT..=Menu::GRINDSTONE_ADDITIONAL_SLOT,
485                    );
486                }
487                _ => {
488                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
489                }
490            },
491            MenuLocation::Hopper(l) => match l {
492                HopperMenuLocation::Player => {
493                    self.try_move_item_to_slots_or_toggle_hotbar(
494                        slot_index,
495                        Menu::HOPPER_CONTENTS_SLOTS,
496                    );
497                }
498                _ => {
499                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
500                }
501            },
502            MenuLocation::Lectern(l) => match l {
503                LecternMenuLocation::Player => {
504                    self.try_move_item_to_slots_or_toggle_hotbar(
505                        slot_index,
506                        Menu::LECTERN_BOOK_SLOT..=Menu::LECTERN_BOOK_SLOT,
507                    );
508                }
509                _ => {
510                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
511                }
512            },
513            MenuLocation::Loom(l) => match l {
514                LoomMenuLocation::Player => {
515                    self.try_move_item_to_slots_or_toggle_hotbar(
516                        slot_index,
517                        Menu::LOOM_BANNER_SLOT..=Menu::LOOM_PATTERN_SLOT,
518                    );
519                }
520                _ => {
521                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
522                }
523            },
524            MenuLocation::Merchant(l) => match l {
525                MerchantMenuLocation::Player => {
526                    self.try_move_item_to_slots_or_toggle_hotbar(
527                        slot_index,
528                        Menu::MERCHANT_PAYMENTS_SLOTS,
529                    );
530                }
531                _ => {
532                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
533                }
534            },
535            MenuLocation::ShulkerBox(l) => match l {
536                ShulkerBoxMenuLocation::Player => {
537                    self.try_move_item_to_slots_or_toggle_hotbar(
538                        slot_index,
539                        Menu::SHULKER_BOX_CONTENTS_SLOTS,
540                    );
541                }
542                _ => {
543                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
544                }
545            },
546            MenuLocation::Smithing(l) => match l {
547                SmithingMenuLocation::Player => {
548                    self.try_move_item_to_slots_or_toggle_hotbar(
549                        slot_index,
550                        Menu::SMITHING_TEMPLATE_SLOT..=Menu::SMITHING_ADDITIONAL_SLOT,
551                    );
552                }
553                _ => {
554                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
555                }
556            },
557            MenuLocation::Smoker(l) => match l {
558                SmokerMenuLocation::Player => {
559                    self.try_move_item_to_slots(
560                        slot_index,
561                        Menu::SMOKER_INGREDIENT_SLOT..=Menu::SMOKER_FUEL_SLOT,
562                    );
563                }
564                _ => {
565                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
566                }
567            },
568            MenuLocation::CartographyTable(l) => match l {
569                CartographyTableMenuLocation::Player => {
570                    self.try_move_item_to_slots_or_toggle_hotbar(
571                        slot_index,
572                        Menu::CARTOGRAPHY_TABLE_MAP_SLOT..=Menu::CARTOGRAPHY_TABLE_ADDITIONAL_SLOT,
573                    );
574                }
575                _ => {
576                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
577                }
578            },
579            MenuLocation::Stonecutter(l) => match l {
580                StonecutterMenuLocation::Player => {
581                    self.try_move_item_to_slots_or_toggle_hotbar(
582                        slot_index,
583                        Menu::STONECUTTER_INPUT_SLOT..=Menu::STONECUTTER_INPUT_SLOT,
584                    );
585                }
586                _ => {
587                    self.try_move_item_to_slots(slot_index, self.player_slots_range());
588                }
589            },
590        }
591
592        ItemStack::Empty
593    }
594
595    fn try_move_item_to_slots_or_toggle_hotbar(
596        &mut self,
597        slot_index: usize,
598        target_slot_indexes: RangeInclusive<usize>,
599    ) {
600        if !self.try_move_item_to_slots(slot_index, target_slot_indexes) {
601            self.try_move_item_to_slots(
602                slot_index,
603                if self.is_hotbar_slot(slot_index) {
604                    self.player_slots_without_hotbar_range()
605                } else {
606                    self.hotbar_slots_range()
607                },
608            );
609        }
610    }
611
612    /// Whether the given item could be placed in this menu.
613    ///
614    /// TODO: right now this always returns true
615    pub fn may_place(&self, _target_slot_index: usize, _item: &ItemStackData) -> bool {
616        true
617    }
618
619    /// Whether the item in the given slot could be clicked and picked up.
620    ///
621    /// TODO: right now this always returns true
622    pub fn may_pickup(&self, _source_slot_index: usize) -> bool {
623        true
624    }
625
626    /// Whether the item in the slot can be picked up and placed.
627    pub fn allow_modification(&self, target_slot_index: usize) -> bool {
628        if !self.may_pickup(target_slot_index) {
629            return false;
630        }
631        let item = self.slot(target_slot_index).unwrap();
632        // the default here probably doesn't matter since we should only be calling this
633        // if we already checked that the slot isn't empty
634        item.as_present()
635            .is_some_and(|item| self.may_place(target_slot_index, item))
636    }
637
638    /// Get the maximum number of items that can be placed in this slot.
639    pub fn max_stack_size(&self, _target_slot_index: usize) -> i32 {
640        64
641    }
642
643    /// Try moving an item to a set of slots in this menu.
644    ///
645    /// Returns the updated item slot.
646    fn try_move_item_to_slots(
647        &mut self,
648        item_slot_index: usize,
649        target_slot_indexes: RangeInclusive<usize>,
650    ) -> bool {
651        let mut item_slot = self.slot(item_slot_index).unwrap().clone();
652
653        // first see if we can stack it with another item
654        if item_slot.kind().stackable() {
655            for target_slot_index in target_slot_indexes.clone() {
656                self.move_item_to_slot_if_stackable(&mut item_slot, target_slot_index);
657                if item_slot.is_empty() {
658                    break;
659                }
660            }
661        }
662
663        // and if not then just try putting it in an empty slot
664        if item_slot.is_present() {
665            for target_slot_index in target_slot_indexes {
666                self.move_item_to_slot_if_empty(&mut item_slot, target_slot_index);
667                if item_slot.is_empty() {
668                    break;
669                }
670            }
671        }
672
673        let is_source_slot_now_empty = item_slot.is_empty();
674
675        *self.slot_mut(item_slot_index).unwrap() = item_slot;
676        is_source_slot_now_empty
677    }
678
679    /// Merge this item slot into the target item slot, only if the target item
680    /// slot is present and the same item.
681    fn move_item_to_slot_if_stackable(
682        &mut self,
683        item_slot: &mut ItemStack,
684        target_slot_index: usize,
685    ) {
686        let ItemStack::Present(item) = item_slot else {
687            return;
688        };
689        let target_slot = self.slot(target_slot_index).unwrap();
690        if let ItemStack::Present(target_item) = target_slot {
691            // the target slot is empty, so we can just move the item there
692            if self.may_place(target_slot_index, item)
693                && target_item.is_same_item_and_components(item)
694            {
695                let slot_item_limit = self.max_stack_size(target_slot_index);
696                let new_target_slot_data = item.split(i32::min(slot_item_limit, item.count) as u32);
697
698                // get the target slot again but mut this time so we can update it
699                let target_slot = self.slot_mut(target_slot_index).unwrap();
700                *target_slot = ItemStack::Present(new_target_slot_data);
701
702                item_slot.update_empty();
703            }
704        }
705    }
706
707    fn move_item_to_slot_if_empty(
708        &mut self,
709        source_item: &mut ItemStack,
710        target_slot_index: usize,
711    ) {
712        let ItemStack::Present(source_item_data) = source_item else {
713            return;
714        };
715        let target_slot = self.slot(target_slot_index).unwrap();
716        if target_slot.is_empty() && self.may_place(target_slot_index, source_item_data) {
717            let slot_item_limit = self.max_stack_size(target_slot_index);
718            let new_target_slot_data =
719                source_item_data.split(i32::min(slot_item_limit, source_item_data.count) as u32);
720            source_item.update_empty();
721
722            let target_slot = self.slot_mut(target_slot_index).unwrap();
723            *target_slot = new_target_slot_data.into();
724        }
725    }
726}