1use std::ops::RangeInclusive;
2
3use azalea_buf::AzBuf;
4
5use crate::{
6 item::MaxStackSizeExt, AnvilMenuLocation, BeaconMenuLocation, BlastFurnaceMenuLocation,
7 BrewingStandMenuLocation, CartographyTableMenuLocation, Crafter3x3MenuLocation,
8 CraftingMenuLocation, EnchantmentMenuLocation, FurnaceMenuLocation, Generic3x3MenuLocation,
9 Generic9x1MenuLocation, Generic9x2MenuLocation, Generic9x3MenuLocation, Generic9x4MenuLocation,
10 Generic9x5MenuLocation, Generic9x6MenuLocation, GrindstoneMenuLocation, HopperMenuLocation,
11 ItemStack, ItemStackData, LecternMenuLocation, LoomMenuLocation, Menu, MenuLocation,
12 MerchantMenuLocation, Player, PlayerMenuLocation, ShulkerBoxMenuLocation, SmithingMenuLocation,
13 SmokerMenuLocation, StonecutterMenuLocation,
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,
65}
66
67impl From<SwapClick> for ClickOperation {
68 fn from(click: SwapClick) -> Self {
69 ClickOperation::Swap(click)
70 }
71}
72#[derive(Debug, Clone)]
75pub struct CloneClick {
76 pub slot: u16,
77}
78impl From<CloneClick> for ClickOperation {
79 fn from(click: CloneClick) -> Self {
80 ClickOperation::Clone(click)
81 }
82}
83#[derive(Debug, Clone)]
84pub enum ThrowClick {
85 Single { slot: u16 },
87 All { slot: u16 },
89}
90impl From<ThrowClick> for ClickOperation {
91 fn from(click: ThrowClick) -> Self {
92 ClickOperation::Throw(click)
93 }
94}
95#[derive(Debug, Clone, Eq, PartialEq)]
96pub struct QuickCraftClick {
97 pub kind: QuickCraftKind,
98 pub status: QuickCraftStatus,
99}
100#[derive(Debug, Clone, Eq, PartialEq)]
101pub enum QuickCraftKind {
102 Left,
103 Right,
104 Middle,
105}
106#[derive(Debug, Clone, Eq, PartialEq)]
107pub enum QuickCraftStatusKind {
108 Start,
110 Add,
112 End,
114}
115#[derive(Debug, Clone, Eq, PartialEq)]
116pub enum QuickCraftStatus {
117 Start,
119 Add { slot: u16 },
121 End,
123}
124impl From<QuickCraftStatus> for QuickCraftStatusKind {
125 fn from(status: QuickCraftStatus) -> Self {
126 match status {
127 QuickCraftStatus::Start => QuickCraftStatusKind::Start,
128 QuickCraftStatus::Add { .. } => QuickCraftStatusKind::Add,
129 QuickCraftStatus::End => QuickCraftStatusKind::End,
130 }
131 }
132}
133
134#[derive(Debug, Clone)]
136pub struct PickupAllClick {
137 pub slot: u16,
140 pub reversed: bool,
142}
143impl From<PickupAllClick> for ClickOperation {
144 fn from(click: PickupAllClick) -> Self {
145 ClickOperation::PickupAll(click)
146 }
147}
148
149impl ClickOperation {
150 pub fn slot_num(&self) -> Option<u16> {
154 match self {
155 ClickOperation::Pickup(pickup) => match pickup {
156 PickupClick::Left { slot } => *slot,
157 PickupClick::Right { slot } => *slot,
158 PickupClick::LeftOutside => None,
159 PickupClick::RightOutside => None,
160 },
161 ClickOperation::QuickMove(quick_move) => match quick_move {
162 QuickMoveClick::Left { slot } => Some(*slot),
163 QuickMoveClick::Right { slot } => Some(*slot),
164 },
165 ClickOperation::Swap(swap) => Some(swap.source_slot),
166 ClickOperation::Clone(clone) => Some(clone.slot),
167 ClickOperation::Throw(throw) => match throw {
168 ThrowClick::Single { slot } => Some(*slot),
169 ThrowClick::All { slot } => Some(*slot),
170 },
171 ClickOperation::QuickCraft(quick_craft) => match quick_craft.status {
172 QuickCraftStatus::Start => None,
173 QuickCraftStatus::Add { slot } => Some(slot),
174 QuickCraftStatus::End => None,
175 },
176 ClickOperation::PickupAll(pickup_all) => Some(pickup_all.slot),
177 }
178 }
179
180 pub fn button_num(&self) -> u8 {
181 match self {
182 ClickOperation::Pickup(pickup) => match pickup {
183 PickupClick::Left { .. } => 0,
184 PickupClick::Right { .. } => 1,
185 PickupClick::LeftOutside => 0,
186 PickupClick::RightOutside => 1,
187 },
188 ClickOperation::QuickMove(quick_move) => match quick_move {
189 QuickMoveClick::Left { .. } => 0,
190 QuickMoveClick::Right { .. } => 1,
191 },
192 ClickOperation::Swap(swap) => swap.target_slot,
193 ClickOperation::Clone(_) => 2,
194 ClickOperation::Throw(throw) => match throw {
195 ThrowClick::Single { .. } => 0,
196 ThrowClick::All { .. } => 1,
197 },
198 ClickOperation::QuickCraft(quick_craft) => match quick_craft {
199 QuickCraftClick {
200 kind: QuickCraftKind::Left,
201 status: QuickCraftStatus::Start,
202 } => 0,
203 QuickCraftClick {
204 kind: QuickCraftKind::Right,
205 status: QuickCraftStatus::Start,
206 } => 4,
207 QuickCraftClick {
208 kind: QuickCraftKind::Middle,
209 status: QuickCraftStatus::Start,
210 } => 8,
211 QuickCraftClick {
212 kind: QuickCraftKind::Left,
213 status: QuickCraftStatus::Add { .. },
214 } => 1,
215 QuickCraftClick {
216 kind: QuickCraftKind::Right,
217 status: QuickCraftStatus::Add { .. },
218 } => 5,
219 QuickCraftClick {
220 kind: QuickCraftKind::Middle,
221 status: QuickCraftStatus::Add { .. },
222 } => 9,
223 QuickCraftClick {
224 kind: QuickCraftKind::Left,
225 status: QuickCraftStatus::End,
226 } => 2,
227 QuickCraftClick {
228 kind: QuickCraftKind::Right,
229 status: QuickCraftStatus::End,
230 } => 6,
231 QuickCraftClick {
232 kind: QuickCraftKind::Middle,
233 status: QuickCraftStatus::End,
234 } => 10,
235 },
236 ClickOperation::PickupAll(_) => 0,
237 }
238 }
239
240 pub fn click_type(&self) -> ClickType {
241 match self {
242 ClickOperation::Pickup(_) => ClickType::Pickup,
243 ClickOperation::QuickMove(_) => ClickType::QuickMove,
244 ClickOperation::Swap(_) => ClickType::Swap,
245 ClickOperation::Clone(_) => ClickType::Clone,
246 ClickOperation::Throw(_) => ClickType::Throw,
247 ClickOperation::QuickCraft(_) => ClickType::QuickCraft,
248 ClickOperation::PickupAll(_) => ClickType::PickupAll,
249 }
250 }
251}
252
253#[derive(AzBuf, Clone, Copy, Debug)]
254pub enum ClickType {
255 Pickup = 0,
256 QuickMove = 1,
257 Swap = 2,
258 Clone = 3,
259 Throw = 4,
260 QuickCraft = 5,
261 PickupAll = 6,
262}
263
264impl Menu {
265 pub fn quick_move_stack(&mut self, slot_index: usize) -> ItemStack {
270 let slot = self.slot(slot_index);
271 if slot.is_none() {
272 return ItemStack::Empty;
273 };
274
275 let slot_location = self
276 .location_for_slot(slot_index)
277 .expect("we just checked to make sure the slot is Some above, so this shouldn't be able to error");
278 match slot_location {
279 MenuLocation::Player(l) => match l {
280 PlayerMenuLocation::CraftResult => {
281 self.try_move_item_to_slots(slot_index, Player::INVENTORY_SLOTS);
282 }
283 PlayerMenuLocation::Craft => {
284 self.try_move_item_to_slots(slot_index, Player::INVENTORY_SLOTS);
285 }
286 PlayerMenuLocation::Armor => {
287 self.try_move_item_to_slots(slot_index, Player::INVENTORY_SLOTS);
288 }
289 _ => {
290 if l == PlayerMenuLocation::Inventory {
298 if Player::is_hotbar_slot(slot_index) {
300 self.try_move_item_to_slots(
301 slot_index,
302 Player::INVENTORY_WITHOUT_HOTBAR_SLOTS,
303 );
304 } else {
305 self.try_move_item_to_slots(slot_index, Player::HOTBAR_SLOTS);
306 }
307 } else {
308 self.try_move_item_to_slots(slot_index, self.player_slots_range());
309 }
310 }
311 },
312 MenuLocation::Generic9x1(l) => match l {
313 Generic9x1MenuLocation::Contents => {
314 self.try_move_item_to_slots(slot_index, self.player_slots_range());
315 }
316 Generic9x1MenuLocation::Player => {
317 self.try_move_item_to_slots_or_toggle_hotbar(
318 slot_index,
319 Menu::GENERIC9X1_CONTENTS_SLOTS,
320 );
321 }
322 },
323 MenuLocation::Generic9x2(l) => match l {
324 Generic9x2MenuLocation::Contents => {
325 self.try_move_item_to_slots(slot_index, self.player_slots_range());
326 }
327 Generic9x2MenuLocation::Player => {
328 self.try_move_item_to_slots_or_toggle_hotbar(
329 slot_index,
330 Menu::GENERIC9X2_CONTENTS_SLOTS,
331 );
332 }
333 },
334 MenuLocation::Generic9x3(l) => match l {
335 Generic9x3MenuLocation::Contents => {
336 self.try_move_item_to_slots(slot_index, self.player_slots_range());
337 }
338 Generic9x3MenuLocation::Player => {
339 self.try_move_item_to_slots_or_toggle_hotbar(
340 slot_index,
341 Menu::GENERIC9X3_CONTENTS_SLOTS,
342 );
343 }
344 },
345 MenuLocation::Generic9x4(l) => match l {
346 Generic9x4MenuLocation::Contents => {
347 self.try_move_item_to_slots(slot_index, self.player_slots_range());
348 }
349 Generic9x4MenuLocation::Player => {
350 self.try_move_item_to_slots_or_toggle_hotbar(
351 slot_index,
352 Menu::GENERIC9X4_CONTENTS_SLOTS,
353 );
354 }
355 },
356 MenuLocation::Generic9x5(l) => match l {
357 Generic9x5MenuLocation::Contents => {
358 self.try_move_item_to_slots(slot_index, self.player_slots_range());
359 }
360 Generic9x5MenuLocation::Player => {
361 self.try_move_item_to_slots_or_toggle_hotbar(
362 slot_index,
363 Menu::GENERIC9X5_CONTENTS_SLOTS,
364 );
365 }
366 },
367 MenuLocation::Generic9x6(l) => match l {
368 Generic9x6MenuLocation::Contents => {
369 self.try_move_item_to_slots(slot_index, self.player_slots_range());
370 }
371 Generic9x6MenuLocation::Player => {
372 self.try_move_item_to_slots_or_toggle_hotbar(
373 slot_index,
374 Menu::GENERIC9X6_CONTENTS_SLOTS,
375 );
376 }
377 },
378 MenuLocation::Generic3x3(l) => match l {
379 Generic3x3MenuLocation::Contents => {
380 self.try_move_item_to_slots(slot_index, self.player_slots_range());
381 }
382 Generic3x3MenuLocation::Player => {
383 self.try_move_item_to_slots_or_toggle_hotbar(
384 slot_index,
385 Menu::GENERIC3X3_CONTENTS_SLOTS,
386 );
387 }
388 },
389 MenuLocation::Crafter3x3(l) => match l {
390 Crafter3x3MenuLocation::Contents => {
391 self.try_move_item_to_slots(slot_index, self.player_slots_range());
392 }
393 Crafter3x3MenuLocation::Player => {
394 self.try_move_item_to_slots_or_toggle_hotbar(
395 slot_index,
396 Menu::GENERIC3X3_CONTENTS_SLOTS,
397 );
398 }
399 },
400 MenuLocation::Anvil(l) => match l {
401 AnvilMenuLocation::Player => {
402 self.try_move_item_to_slots_or_toggle_hotbar(
403 slot_index,
404 Menu::ANVIL_FIRST_SLOT..=Menu::ANVIL_SECOND_SLOT,
405 );
406 }
407 _ => {
408 self.try_move_item_to_slots(slot_index, self.player_slots_range());
409 }
410 },
411 MenuLocation::Beacon(l) => match l {
412 BeaconMenuLocation::Payment => {
413 self.try_move_item_to_slots(slot_index, self.player_slots_range());
414 }
415 BeaconMenuLocation::Player => {
416 self.try_move_item_to_slots(
417 slot_index,
418 Menu::BEACON_PAYMENT_SLOT..=Menu::BEACON_PAYMENT_SLOT,
419 );
420 }
421 },
422 MenuLocation::BlastFurnace(l) => match l {
423 BlastFurnaceMenuLocation::Player => {
424 self.try_move_item_to_slots(
425 slot_index,
426 Menu::BLAST_FURNACE_INGREDIENT_SLOT..=Menu::BLAST_FURNACE_FUEL_SLOT,
427 );
428 }
429 _ => {
430 self.try_move_item_to_slots(slot_index, self.player_slots_range());
431 }
432 },
433 MenuLocation::BrewingStand(l) => match l {
434 BrewingStandMenuLocation::Player => {
435 self.try_move_item_to_slots(
436 slot_index,
437 *Menu::BREWING_STAND_BOTTLES_SLOTS.start()
438 ..=Menu::BREWING_STAND_INGREDIENT_SLOT,
439 );
440 }
441 _ => {
442 self.try_move_item_to_slots(slot_index, self.player_slots_range());
443 }
444 },
445 MenuLocation::Crafting(l) => match l {
446 CraftingMenuLocation::Player => {
447 self.try_move_item_to_slots_or_toggle_hotbar(
448 slot_index,
449 Menu::CRAFTING_GRID_SLOTS,
450 );
451 }
452 _ => {
453 self.try_move_item_to_slots(slot_index, self.player_slots_range());
454 }
455 },
456 MenuLocation::Enchantment(l) => match l {
457 EnchantmentMenuLocation::Player => {
458 self.try_move_item_to_slots_or_toggle_hotbar(
459 slot_index,
460 Menu::ENCHANTMENT_ITEM_SLOT..=Menu::ENCHANTMENT_LAPIS_SLOT,
461 );
462 }
463 _ => {
464 self.try_move_item_to_slots(slot_index, self.player_slots_range());
465 }
466 },
467 MenuLocation::Furnace(l) => match l {
468 FurnaceMenuLocation::Player => {
469 self.try_move_item_to_slots(
470 slot_index,
471 Menu::FURNACE_INGREDIENT_SLOT..=Menu::FURNACE_FUEL_SLOT,
472 );
473 }
474 _ => {
475 self.try_move_item_to_slots(slot_index, self.player_slots_range());
476 }
477 },
478 MenuLocation::Grindstone(l) => match l {
479 GrindstoneMenuLocation::Player => {
480 self.try_move_item_to_slots_or_toggle_hotbar(
481 slot_index,
482 Menu::GRINDSTONE_INPUT_SLOT..=Menu::GRINDSTONE_ADDITIONAL_SLOT,
483 );
484 }
485 _ => {
486 self.try_move_item_to_slots(slot_index, self.player_slots_range());
487 }
488 },
489 MenuLocation::Hopper(l) => match l {
490 HopperMenuLocation::Player => {
491 self.try_move_item_to_slots_or_toggle_hotbar(
492 slot_index,
493 Menu::HOPPER_CONTENTS_SLOTS,
494 );
495 }
496 _ => {
497 self.try_move_item_to_slots(slot_index, self.player_slots_range());
498 }
499 },
500 MenuLocation::Lectern(l) => match l {
501 LecternMenuLocation::Player => {
502 self.try_move_item_to_slots_or_toggle_hotbar(
503 slot_index,
504 Menu::LECTERN_BOOK_SLOT..=Menu::LECTERN_BOOK_SLOT,
505 );
506 }
507 _ => {
508 self.try_move_item_to_slots(slot_index, self.player_slots_range());
509 }
510 },
511 MenuLocation::Loom(l) => match l {
512 LoomMenuLocation::Player => {
513 self.try_move_item_to_slots_or_toggle_hotbar(
514 slot_index,
515 Menu::LOOM_BANNER_SLOT..=Menu::LOOM_PATTERN_SLOT,
516 );
517 }
518 _ => {
519 self.try_move_item_to_slots(slot_index, self.player_slots_range());
520 }
521 },
522 MenuLocation::Merchant(l) => match l {
523 MerchantMenuLocation::Player => {
524 self.try_move_item_to_slots_or_toggle_hotbar(
525 slot_index,
526 Menu::MERCHANT_PAYMENTS_SLOTS,
527 );
528 }
529 _ => {
530 self.try_move_item_to_slots(slot_index, self.player_slots_range());
531 }
532 },
533 MenuLocation::ShulkerBox(l) => match l {
534 ShulkerBoxMenuLocation::Player => {
535 self.try_move_item_to_slots_or_toggle_hotbar(
536 slot_index,
537 Menu::SHULKER_BOX_CONTENTS_SLOTS,
538 );
539 }
540 _ => {
541 self.try_move_item_to_slots(slot_index, self.player_slots_range());
542 }
543 },
544 MenuLocation::Smithing(l) => match l {
545 SmithingMenuLocation::Player => {
546 self.try_move_item_to_slots_or_toggle_hotbar(
547 slot_index,
548 Menu::SMITHING_TEMPLATE_SLOT..=Menu::SMITHING_ADDITIONAL_SLOT,
549 );
550 }
551 _ => {
552 self.try_move_item_to_slots(slot_index, self.player_slots_range());
553 }
554 },
555 MenuLocation::Smoker(l) => match l {
556 SmokerMenuLocation::Player => {
557 self.try_move_item_to_slots(
558 slot_index,
559 Menu::SMOKER_INGREDIENT_SLOT..=Menu::SMOKER_FUEL_SLOT,
560 );
561 }
562 _ => {
563 self.try_move_item_to_slots(slot_index, self.player_slots_range());
564 }
565 },
566 MenuLocation::CartographyTable(l) => match l {
567 CartographyTableMenuLocation::Player => {
568 self.try_move_item_to_slots_or_toggle_hotbar(
569 slot_index,
570 Menu::CARTOGRAPHY_TABLE_MAP_SLOT..=Menu::CARTOGRAPHY_TABLE_ADDITIONAL_SLOT,
571 );
572 }
573 _ => {
574 self.try_move_item_to_slots(slot_index, self.player_slots_range());
575 }
576 },
577 MenuLocation::Stonecutter(l) => match l {
578 StonecutterMenuLocation::Player => {
579 self.try_move_item_to_slots_or_toggle_hotbar(
580 slot_index,
581 Menu::STONECUTTER_INPUT_SLOT..=Menu::STONECUTTER_INPUT_SLOT,
582 );
583 }
584 _ => {
585 self.try_move_item_to_slots(slot_index, self.player_slots_range());
586 }
587 },
588 }
589
590 ItemStack::Empty
591 }
592
593 fn try_move_item_to_slots_or_toggle_hotbar(
594 &mut self,
595 slot_index: usize,
596 target_slot_indexes: RangeInclusive<usize>,
597 ) {
598 if !self.try_move_item_to_slots(slot_index, target_slot_indexes) {
599 self.try_move_item_to_slots(
600 slot_index,
601 if self.is_hotbar_slot(slot_index) {
602 self.player_slots_without_hotbar_range()
603 } else {
604 self.hotbar_slots_range()
605 },
606 );
607 }
608 }
609
610 pub fn may_place(&self, _target_slot_index: usize, _item: &ItemStackData) -> bool {
614 true
615 }
616
617 pub fn may_pickup(&self, _source_slot_index: usize) -> bool {
620 true
621 }
622
623 pub fn max_stack_size(&self, _target_slot_index: usize) -> u32 {
625 64
626 }
627
628 fn try_move_item_to_slots(
632 &mut self,
633 item_slot_index: usize,
634 target_slot_indexes: RangeInclusive<usize>,
635 ) -> bool {
636 let mut item_slot = self.slot(item_slot_index).unwrap().clone();
637
638 if item_slot.kind().stackable() {
640 for target_slot_index in target_slot_indexes.clone() {
641 self.move_item_to_slot_if_stackable(&mut item_slot, target_slot_index);
642 if item_slot.is_empty() {
643 break;
644 }
645 }
646 }
647
648 if item_slot.is_present() {
650 for target_slot_index in target_slot_indexes {
651 self.move_item_to_slot_if_empty(&mut item_slot, target_slot_index);
652 if item_slot.is_empty() {
653 break;
654 }
655 }
656 }
657
658 item_slot.is_empty()
659 }
660
661 fn move_item_to_slot_if_stackable(
664 &mut self,
665 item_slot: &mut ItemStack,
666 target_slot_index: usize,
667 ) {
668 let ItemStack::Present(item) = item_slot else {
669 return;
670 };
671 let target_slot = self.slot(target_slot_index).unwrap();
672 if let ItemStack::Present(target_item) = target_slot {
673 if self.may_place(target_slot_index, item)
675 && target_item.is_same_item_and_components(item)
676 {
677 let slot_item_limit = self.max_stack_size(target_slot_index);
678 let new_target_slot_data = item.split(u32::min(slot_item_limit, item.count as u32));
679
680 let target_slot = self.slot_mut(target_slot_index).unwrap();
682 *target_slot = ItemStack::Present(new_target_slot_data);
683
684 item_slot.update_empty();
685 }
686 }
687 }
688
689 fn move_item_to_slot_if_empty(&mut self, item_slot: &mut ItemStack, target_slot_index: usize) {
690 let ItemStack::Present(item) = item_slot else {
691 return;
692 };
693 let target_slot = self.slot(target_slot_index).unwrap();
694 if target_slot.is_empty() && self.may_place(target_slot_index, item) {
695 let slot_item_limit = self.max_stack_size(target_slot_index);
696 let new_target_slot_data = item.split(u32::min(slot_item_limit, item.count as u32));
697
698 let target_slot = self.slot_mut(target_slot_index).unwrap();
699 *target_slot = ItemStack::Present(new_target_slot_data);
700 item_slot.update_empty();
701 }
702 }
703}