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 { slot: Option<u16> },
32 Right { slot: Option<u16> },
35 LeftOutside,
37 RightOutside,
39}
40impl From<PickupClick> for ClickOperation {
41 fn from(click: PickupClick) -> Self {
42 ClickOperation::Pickup(click)
43 }
44}
45
46#[derive(Debug, Clone)]
48pub enum QuickMoveClick {
49 Left { slot: u16 },
51 Right { slot: u16 },
53}
54impl From<QuickMoveClick> for ClickOperation {
55 fn from(click: QuickMoveClick) -> Self {
56 ClickOperation::QuickMove(click)
57 }
58}
59
60#[derive(Debug, Clone)]
62pub struct SwapClick {
63 pub source_slot: u16,
64 pub target_slot: u8,
67}
68
69impl From<SwapClick> for ClickOperation {
70 fn from(click: SwapClick) -> Self {
71 ClickOperation::Swap(click)
72 }
73}
74#[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 Single { slot: u16 },
89 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 Start,
112 Add,
114 End,
116}
117#[derive(Debug, Clone, Eq, PartialEq)]
118pub enum QuickCraftStatus {
119 Start,
121 Add { slot: u16 },
123 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#[derive(Debug, Clone)]
138pub struct PickupAllClick {
139 pub slot: u16,
142 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 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 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 if l == PlayerMenuLocation::Inventory {
300 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 pub fn may_place(&self, _target_slot_index: usize, _item: &ItemStackData) -> bool {
616 true
617 }
618
619 pub fn may_pickup(&self, _source_slot_index: usize) -> bool {
623 true
624 }
625
626 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 item.as_present()
635 .is_some_and(|item| self.may_place(target_slot_index, item))
636 }
637
638 pub fn max_stack_size(&self, _target_slot_index: usize) -> i32 {
640 64
641 }
642
643 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 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 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 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 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 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}