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