1use crate::components::BlockPosition;
4use crate::container::{ContainerBacking, InventoryType};
5use crate::world::block_entity::BlockEntity;
6use basalt_types::Slot;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum CloseReason {
15 Manual,
17 Disconnect,
19 ForceClose,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum WindowSlotKind {
26 CraftOutput,
28 CraftGrid,
30 Armor,
32 MainInventory,
34 Hotbar,
36 Offhand,
38 Container,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum DragType {
45 LeftDrag,
47 RightDrag,
49 MiddleDrag,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum ContainerClickType {
59 LeftClick,
61 RightClick,
63 ShiftClick,
65 DoubleClick,
67 DropSlot {
69 drop_all: bool,
71 },
72 HotbarSwap {
74 hotbar: u8,
76 },
77 OffhandSwap,
79}
80
81pub use crate::world::block_entity::BlockEntityKind;
82
83#[derive(Debug, Clone)]
92pub struct ContainerOpenRequestEvent {
93 pub inventory_type: InventoryType,
95 pub backing: ContainerBacking,
97 pub title: String,
99 pub cancelled: bool,
101}
102crate::game_cancellable_event!(ContainerOpenRequestEvent);
103
104#[derive(Debug, Clone)]
108pub struct ContainerOpenedEvent {
109 pub window_id: u8,
111 pub inventory_type: InventoryType,
113 pub backing: ContainerBacking,
115 pub viewer_count: u32,
123}
124crate::game_event!(ContainerOpenedEvent);
125
126#[derive(Debug, Clone)]
130pub struct ContainerClosedEvent {
131 pub window_id: u8,
133 pub inventory_type: InventoryType,
135 pub backing: ContainerBacking,
137 pub reason: CloseReason,
139 pub viewer_count: u32,
146 pub crafting_grid_state: Option<[Slot; 9]>,
154}
155crate::game_event!(ContainerClosedEvent);
156
157#[derive(Debug, Clone)]
169pub struct ContainerClickEvent {
170 pub window_id: u8,
172 pub backing: ContainerBacking,
174 pub slot_index: i16,
176 pub window_slot_kind: WindowSlotKind,
178 pub click_type: ContainerClickType,
180 pub cursor_before: Slot,
182 pub cancelled: bool,
184}
185crate::game_cancellable_event!(ContainerClickEvent);
186
187#[derive(Debug, Clone)]
192pub struct ContainerDragEvent {
193 pub window_id: u8,
195 pub backing: ContainerBacking,
197 pub affected_slots: Vec<(i16, Slot)>,
199 pub drag_type: DragType,
201 pub cursor: Slot,
203 pub cancelled: bool,
205}
206crate::game_cancellable_event!(ContainerDragEvent);
207
208#[derive(Debug, Clone)]
214pub struct ContainerSlotChangedEvent {
215 pub window_id: u8,
217 pub backing: ContainerBacking,
219 pub slot_index: i16,
221 pub old: Slot,
223 pub new: Slot,
225}
226crate::game_event!(ContainerSlotChangedEvent);
227
228#[derive(Debug, Clone)]
236pub struct BlockEntityCreatedEvent {
237 pub position: BlockPosition,
239 pub kind: BlockEntityKind,
241}
242crate::game_event!(BlockEntityCreatedEvent);
243
244#[derive(Debug, Clone)]
249pub struct BlockEntityModifiedEvent {
250 pub position: BlockPosition,
252 pub kind: BlockEntityKind,
254}
255crate::game_event!(BlockEntityModifiedEvent);
256
257#[derive(Debug, Clone)]
262pub struct BlockEntityDestroyedEvent {
263 pub position: BlockPosition,
265 pub kind: BlockEntityKind,
267 pub last_state: BlockEntity,
269}
270crate::game_event!(BlockEntityDestroyedEvent);
271
272#[cfg(test)]
273mod tests {
274 use crate::events::{BusKind, Event, EventRouting};
275
276 use super::*;
277
278 #[test]
281 fn close_reason_variants() {
282 assert_eq!(CloseReason::Manual, CloseReason::Manual);
283 assert_eq!(CloseReason::Disconnect, CloseReason::Disconnect);
284 assert_eq!(CloseReason::ForceClose, CloseReason::ForceClose);
285 assert_ne!(CloseReason::Manual, CloseReason::Disconnect);
286 }
287
288 #[test]
289 fn window_slot_kind_variants() {
290 let variants = [
291 WindowSlotKind::CraftOutput,
292 WindowSlotKind::CraftGrid,
293 WindowSlotKind::Armor,
294 WindowSlotKind::MainInventory,
295 WindowSlotKind::Hotbar,
296 WindowSlotKind::Offhand,
297 WindowSlotKind::Container,
298 ];
299 for (i, a) in variants.iter().enumerate() {
300 assert_eq!(a, a, "variant should equal itself");
301 for b in variants.iter().skip(i + 1) {
302 assert_ne!(a, b, "distinct variants should differ");
303 }
304 }
305 }
306
307 #[test]
308 fn drag_type_variants() {
309 assert_eq!(DragType::LeftDrag, DragType::LeftDrag);
310 assert_eq!(DragType::RightDrag, DragType::RightDrag);
311 assert_eq!(DragType::MiddleDrag, DragType::MiddleDrag);
312 assert_ne!(DragType::LeftDrag, DragType::RightDrag);
313 }
314
315 #[test]
316 fn container_click_type_variants() {
317 assert_eq!(ContainerClickType::LeftClick, ContainerClickType::LeftClick);
318 assert_eq!(
319 ContainerClickType::RightClick,
320 ContainerClickType::RightClick
321 );
322 assert_eq!(
323 ContainerClickType::ShiftClick,
324 ContainerClickType::ShiftClick
325 );
326 assert_eq!(
327 ContainerClickType::DoubleClick,
328 ContainerClickType::DoubleClick
329 );
330 assert_eq!(
331 ContainerClickType::DropSlot { drop_all: true },
332 ContainerClickType::DropSlot { drop_all: true }
333 );
334 assert_ne!(
335 ContainerClickType::DropSlot { drop_all: false },
336 ContainerClickType::DropSlot { drop_all: true }
337 );
338 assert_eq!(
339 ContainerClickType::HotbarSwap { hotbar: 3 },
340 ContainerClickType::HotbarSwap { hotbar: 3 }
341 );
342 assert_ne!(
343 ContainerClickType::HotbarSwap { hotbar: 0 },
344 ContainerClickType::HotbarSwap { hotbar: 1 }
345 );
346 assert_eq!(
347 ContainerClickType::OffhandSwap,
348 ContainerClickType::OffhandSwap
349 );
350 }
351
352 #[test]
353 fn block_entity_kind_variants() {
354 assert_eq!(BlockEntityKind::Chest, BlockEntityKind::Chest);
355 }
356
357 #[test]
360 fn container_open_request_cancellation() {
361 let mut event = ContainerOpenRequestEvent {
362 inventory_type: InventoryType::Generic9x3,
363 backing: ContainerBacking::Virtual,
364 title: "Test".to_string(),
365 cancelled: false,
366 };
367 assert!(!event.is_cancelled());
368 event.cancel();
369 assert!(event.is_cancelled());
370 }
371
372 #[test]
373 fn container_opened_construction() {
374 let event = ContainerOpenedEvent {
375 window_id: 1,
376 inventory_type: InventoryType::Generic9x3,
377 backing: ContainerBacking::Block {
378 position: BlockPosition { x: 5, y: 64, z: 3 },
379 },
380 viewer_count: 1,
381 };
382 assert_eq!(event.window_id, 1);
383 assert_eq!(event.inventory_type, InventoryType::Generic9x3);
384 assert_eq!(event.viewer_count, 1);
385 }
386
387 #[test]
388 fn container_opened_not_cancellable() {
389 let mut event = ContainerOpenedEvent {
390 window_id: 1,
391 inventory_type: InventoryType::Generic9x3,
392 backing: ContainerBacking::Virtual,
393 viewer_count: 0,
394 };
395 event.cancel(); assert!(!event.is_cancelled());
397 }
398
399 #[test]
400 fn container_closed_construction() {
401 let event = ContainerClosedEvent {
402 window_id: 2,
403 inventory_type: InventoryType::Hopper,
404 backing: ContainerBacking::Block {
405 position: BlockPosition {
406 x: 10,
407 y: 32,
408 z: -5,
409 },
410 },
411 reason: CloseReason::Manual,
412 viewer_count: 0,
413 crafting_grid_state: None,
414 };
415 assert_eq!(event.window_id, 2);
416 assert_eq!(event.reason, CloseReason::Manual);
417 }
418
419 #[test]
420 fn container_closed_not_cancellable() {
421 let mut event = ContainerClosedEvent {
422 window_id: 1,
423 inventory_type: InventoryType::Generic9x3,
424 backing: ContainerBacking::Virtual,
425 reason: CloseReason::Disconnect,
426 viewer_count: 0,
427 crafting_grid_state: None,
428 };
429 event.cancel();
430 assert!(!event.is_cancelled());
431 }
432
433 #[test]
436 fn container_click_cancellation() {
437 let mut event = ContainerClickEvent {
438 window_id: 1,
439 backing: ContainerBacking::Virtual,
440 slot_index: 5,
441 window_slot_kind: WindowSlotKind::Container,
442 click_type: ContainerClickType::LeftClick,
443 cursor_before: Slot::empty(),
444 cancelled: false,
445 };
446 assert!(!event.is_cancelled());
447 event.cancel();
448 assert!(event.is_cancelled());
449 }
450
451 #[test]
452 fn container_click_field_access() {
453 let event = ContainerClickEvent {
454 window_id: 3,
455 backing: ContainerBacking::Block {
456 position: BlockPosition { x: 0, y: 64, z: 0 },
457 },
458 slot_index: 12,
459 window_slot_kind: WindowSlotKind::Hotbar,
460 click_type: ContainerClickType::HotbarSwap { hotbar: 2 },
461 cursor_before: Slot::new(1, 32),
462 cancelled: false,
463 };
464 assert_eq!(event.slot_index, 12);
465 assert_eq!(event.window_slot_kind, WindowSlotKind::Hotbar);
466 assert_eq!(
467 event.click_type,
468 ContainerClickType::HotbarSwap { hotbar: 2 }
469 );
470 }
471
472 #[test]
473 fn container_drag_cancellation() {
474 let mut event = ContainerDragEvent {
475 window_id: 1,
476 backing: ContainerBacking::Virtual,
477 affected_slots: vec![(0, Slot::new(1, 16)), (1, Slot::new(1, 16))],
478 drag_type: DragType::LeftDrag,
479 cursor: Slot::new(1, 32),
480 cancelled: false,
481 };
482 assert!(!event.is_cancelled());
483 event.cancel();
484 assert!(event.is_cancelled());
485 }
486
487 #[test]
488 fn container_drag_field_access() {
489 let event = ContainerDragEvent {
490 window_id: 2,
491 backing: ContainerBacking::Virtual,
492 affected_slots: vec![(5, Slot::new(3, 1))],
493 drag_type: DragType::RightDrag,
494 cursor: Slot::new(3, 5),
495 cancelled: false,
496 };
497 assert_eq!(event.affected_slots.len(), 1);
498 assert_eq!(event.drag_type, DragType::RightDrag);
499 }
500
501 #[test]
502 fn container_slot_changed_construction() {
503 let event = ContainerSlotChangedEvent {
504 window_id: 1,
505 backing: ContainerBacking::Block {
506 position: BlockPosition { x: 5, y: 64, z: 3 },
507 },
508 slot_index: 7,
509 old: Slot::empty(),
510 new: Slot::new(1, 1),
511 };
512 assert_eq!(event.slot_index, 7);
513 }
514
515 #[test]
516 fn container_slot_changed_not_cancellable() {
517 let mut event = ContainerSlotChangedEvent {
518 window_id: 1,
519 backing: ContainerBacking::Virtual,
520 slot_index: 0,
521 old: Slot::empty(),
522 new: Slot::empty(),
523 };
524 event.cancel();
525 assert!(!event.is_cancelled());
526 }
527
528 #[test]
531 fn block_entity_created_construction() {
532 let event = BlockEntityCreatedEvent {
533 position: BlockPosition { x: 5, y: 64, z: 3 },
534 kind: BlockEntityKind::Chest,
535 };
536 assert_eq!(event.position, BlockPosition { x: 5, y: 64, z: 3 });
537 assert_eq!(event.kind, BlockEntityKind::Chest);
538 }
539
540 #[test]
541 fn block_entity_created_not_cancellable() {
542 let mut event = BlockEntityCreatedEvent {
543 position: BlockPosition { x: 0, y: 0, z: 0 },
544 kind: BlockEntityKind::Chest,
545 };
546 event.cancel();
547 assert!(!event.is_cancelled());
548 }
549
550 #[test]
551 fn block_entity_modified_construction() {
552 let event = BlockEntityModifiedEvent {
553 position: BlockPosition {
554 x: -10,
555 y: 32,
556 z: 100,
557 },
558 kind: BlockEntityKind::Chest,
559 };
560 assert_eq!(event.kind, BlockEntityKind::Chest);
561 }
562
563 #[test]
564 fn block_entity_modified_not_cancellable() {
565 let mut event = BlockEntityModifiedEvent {
566 position: BlockPosition { x: 0, y: 0, z: 0 },
567 kind: BlockEntityKind::Chest,
568 };
569 event.cancel();
570 assert!(!event.is_cancelled());
571 }
572
573 #[test]
574 fn block_entity_destroyed_carries_last_state() {
575 let be = BlockEntity::empty_chest();
576 let event = BlockEntityDestroyedEvent {
577 position: BlockPosition {
578 x: 100,
579 y: 64,
580 z: -50,
581 },
582 kind: BlockEntityKind::Chest,
583 last_state: be,
584 };
585 match &event.last_state {
586 BlockEntity::Chest { slots } => {
587 assert_eq!(slots.len(), 27);
588 }
589 }
590 }
591
592 #[test]
593 fn block_entity_destroyed_not_cancellable() {
594 let mut event = BlockEntityDestroyedEvent {
595 position: BlockPosition { x: 0, y: 0, z: 0 },
596 kind: BlockEntityKind::Chest,
597 last_state: BlockEntity::empty_chest(),
598 };
599 event.cancel();
600 assert!(!event.is_cancelled());
601 }
602
603 #[test]
606 fn all_events_route_to_game_bus() {
607 assert_eq!(ContainerOpenRequestEvent::BUS, BusKind::Game);
608 assert_eq!(ContainerOpenedEvent::BUS, BusKind::Game);
609 assert_eq!(ContainerClosedEvent::BUS, BusKind::Game);
610 assert_eq!(ContainerClickEvent::BUS, BusKind::Game);
611 assert_eq!(ContainerDragEvent::BUS, BusKind::Game);
612 assert_eq!(ContainerSlotChangedEvent::BUS, BusKind::Game);
613 assert_eq!(BlockEntityCreatedEvent::BUS, BusKind::Game);
614 assert_eq!(BlockEntityModifiedEvent::BUS, BusKind::Game);
615 assert_eq!(BlockEntityDestroyedEvent::BUS, BusKind::Game);
616 }
617
618 #[test]
619 fn bus_kind_method_matches_const() {
620 let events_game: Vec<Box<dyn Event>> = vec![
621 Box::new(ContainerOpenRequestEvent {
622 inventory_type: InventoryType::Generic9x3,
623 backing: ContainerBacking::Virtual,
624 title: String::new(),
625 cancelled: false,
626 }),
627 Box::new(ContainerOpenedEvent {
628 window_id: 1,
629 inventory_type: InventoryType::Generic9x3,
630 backing: ContainerBacking::Virtual,
631 viewer_count: 0,
632 }),
633 Box::new(ContainerClosedEvent {
634 window_id: 1,
635 inventory_type: InventoryType::Generic9x3,
636 backing: ContainerBacking::Virtual,
637 reason: CloseReason::Manual,
638 viewer_count: 0,
639 crafting_grid_state: None,
640 }),
641 Box::new(ContainerClickEvent {
642 window_id: 1,
643 backing: ContainerBacking::Virtual,
644 slot_index: 0,
645 window_slot_kind: WindowSlotKind::Container,
646 click_type: ContainerClickType::LeftClick,
647 cursor_before: Slot::empty(),
648 cancelled: false,
649 }),
650 Box::new(ContainerDragEvent {
651 window_id: 1,
652 backing: ContainerBacking::Virtual,
653 affected_slots: vec![],
654 drag_type: DragType::LeftDrag,
655 cursor: Slot::empty(),
656 cancelled: false,
657 }),
658 Box::new(ContainerSlotChangedEvent {
659 window_id: 1,
660 backing: ContainerBacking::Virtual,
661 slot_index: 0,
662 old: Slot::empty(),
663 new: Slot::empty(),
664 }),
665 Box::new(BlockEntityCreatedEvent {
666 position: BlockPosition { x: 0, y: 0, z: 0 },
667 kind: BlockEntityKind::Chest,
668 }),
669 Box::new(BlockEntityModifiedEvent {
670 position: BlockPosition { x: 0, y: 0, z: 0 },
671 kind: BlockEntityKind::Chest,
672 }),
673 Box::new(BlockEntityDestroyedEvent {
674 position: BlockPosition { x: 0, y: 0, z: 0 },
675 kind: BlockEntityKind::Chest,
676 last_state: BlockEntity::empty_chest(),
677 }),
678 ];
679 for event in &events_game {
680 assert_eq!(event.bus_kind(), BusKind::Game);
681 }
682 }
683}