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)]
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 { slot: Option<u16> },
34 Right { slot: Option<u16> },
38 LeftOutside,
40 RightOutside,
42}
43impl From<PickupClick> for ClickOperation {
44 fn from(click: PickupClick) -> Self {
45 ClickOperation::Pickup(click)
46 }
47}
48
49#[derive(Debug, Clone)]
51pub enum QuickMoveClick {
52 Left { slot: u16 },
54 Right { slot: u16 },
56}
57impl From<QuickMoveClick> for ClickOperation {
58 fn from(click: QuickMoveClick) -> Self {
59 ClickOperation::QuickMove(click)
60 }
61}
62
63#[derive(Debug, Clone)]
65pub struct SwapClick {
66 pub source_slot: u16,
67 pub target_slot: u8,
70}
71
72impl From<SwapClick> for ClickOperation {
73 fn from(click: SwapClick) -> Self {
74 ClickOperation::Swap(click)
75 }
76}
77#[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 Single { slot: u16 },
92 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 Start,
115 Add,
117 End,
119}
120#[derive(Debug, Clone, Eq, PartialEq)]
121pub enum QuickCraftStatus {
122 Start,
124 Add { slot: u16 },
126 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#[derive(Debug, Clone)]
141pub struct PickupAllClick {
142 pub slot: u16,
147 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 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 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 if l == PlayerMenuLocation::Inventory {
305 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 pub fn may_place(&self, _target_slot_index: usize, _item: &ItemStackData) -> bool {
621 true
622 }
623
624 pub fn may_pickup(&self, _source_slot_index: usize) -> bool {
628 true
629 }
630
631 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 item.as_present()
640 .is_some_and(|item| self.may_place(target_slot_index, item))
641 }
642
643 pub fn max_stack_size(&self, _target_slot_index: usize) -> i32 {
645 64
646 }
647
648 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 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 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 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 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 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}