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> },
33 Right { slot: Option<u16> },
36 LeftOutside,
38 RightOutside,
40}
41impl From<PickupClick> for ClickOperation {
42 fn from(click: PickupClick) -> Self {
43 ClickOperation::Pickup(click)
44 }
45}
46
47#[derive(Debug, Clone)]
49pub enum QuickMoveClick {
50 Left { slot: u16 },
52 Right { slot: u16 },
54}
55impl From<QuickMoveClick> for ClickOperation {
56 fn from(click: QuickMoveClick) -> Self {
57 ClickOperation::QuickMove(click)
58 }
59}
60
61#[derive(Debug, Clone)]
63pub struct SwapClick {
64 pub source_slot: u16,
65 pub target_slot: u8,
68}
69
70impl From<SwapClick> for ClickOperation {
71 fn from(click: SwapClick) -> Self {
72 ClickOperation::Swap(click)
73 }
74}
75#[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 Single { slot: u16 },
90 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 Start,
113 Add,
115 End,
117}
118#[derive(Debug, Clone, Eq, PartialEq)]
119pub enum QuickCraftStatus {
120 Start,
122 Add { slot: u16 },
124 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#[derive(Debug, Clone)]
139pub struct PickupAllClick {
140 pub slot: u16,
143 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 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 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 if l == PlayerMenuLocation::Inventory {
301 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 pub fn may_place(&self, _target_slot_index: usize, _item: &ItemStackData) -> bool {
617 true
618 }
619
620 pub fn may_pickup(&self, _source_slot_index: usize) -> bool {
624 true
625 }
626
627 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 item.as_present()
636 .is_some_and(|item| self.may_place(target_slot_index, item))
637 }
638
639 pub fn max_stack_size(&self, _target_slot_index: usize) -> i32 {
641 64
642 }
643
644 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 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 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 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 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 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}