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