Skip to main content

basalt_api/events/
block.rs

1//! Block interaction events: breaking, placing, right-click.
2
3use crate::components::BlockPosition;
4
5/// A player broke a block.
6///
7/// Fired when the server receives a `BlockDig` packet with status 0.
8/// The breaking player is available via `ctx.player()`.
9#[derive(Debug, Clone)]
10pub struct BlockBrokenEvent {
11    /// Position of the broken block.
12    pub position: BlockPosition,
13    /// Block state that was at this position before breaking.
14    pub block_state: u16,
15    /// Sequence number for client acknowledgement.
16    pub sequence: i32,
17    /// Whether this event has been cancelled by a Validate handler.
18    pub cancelled: bool,
19}
20crate::game_cancellable_event!(BlockBrokenEvent);
21
22/// A player placed a block.
23///
24/// Fired when the server receives a `BlockPlace` packet with a valid
25/// held item. The placement position has already been computed from
26/// the target block + face offset. The placing player is available
27/// via `ctx.player()`.
28#[derive(Debug, Clone)]
29pub struct BlockPlacedEvent {
30    /// Position where the block was placed.
31    pub position: BlockPosition,
32    /// The block state ID that was placed.
33    pub block_state: u16,
34    /// Sequence number for client acknowledgement.
35    pub sequence: i32,
36    /// Whether this event has been cancelled by a Validate handler.
37    pub cancelled: bool,
38}
39crate::game_cancellable_event!(BlockPlacedEvent);
40
41/// A player right-clicked on a block.
42///
43/// Fired before any container interaction or block placement.
44/// If cancelled (e.g., ContainerPlugin opens a chest), the game
45/// loop skips block placement. The interacting player is available
46/// via `ctx.player()`.
47#[derive(Debug, Clone)]
48pub struct PlayerInteractEvent {
49    /// Position of the clicked block.
50    pub position: BlockPosition,
51    /// Block state at the clicked position.
52    pub block_state: u16,
53    /// Face direction clicked (0-5).
54    pub direction: i32,
55    /// Sequence number for acknowledgement.
56    pub sequence: i32,
57    /// Whether this event has been cancelled.
58    pub cancelled: bool,
59}
60crate::game_cancellable_event!(PlayerInteractEvent);
61
62#[cfg(test)]
63mod tests {
64    use crate::events::Event;
65
66    use super::*;
67
68    #[test]
69    fn block_broken_cancellation() {
70        let mut event = BlockBrokenEvent {
71            position: BlockPosition { x: 0, y: 64, z: 0 },
72            block_state: 1,
73            sequence: 1,
74            cancelled: false,
75        };
76        assert!(!event.is_cancelled());
77        event.cancel();
78        assert!(event.is_cancelled());
79    }
80
81    #[test]
82    fn block_placed_downcast_roundtrip() {
83        let mut event = BlockPlacedEvent {
84            position: BlockPosition { x: 5, y: 64, z: 3 },
85            block_state: 1,
86            sequence: 42,
87            cancelled: false,
88        };
89        let any = event.as_any_mut();
90        let concrete = any.downcast_mut::<BlockPlacedEvent>().unwrap();
91        assert_eq!(concrete.block_state, 1);
92    }
93
94    #[test]
95    fn event_routing() {
96        use crate::events::{BusKind, EventRouting};
97        assert_eq!(BlockBrokenEvent::BUS, BusKind::Game);
98        assert_eq!(BlockPlacedEvent::BUS, BusKind::Game);
99        assert_eq!(PlayerInteractEvent::BUS, BusKind::Game);
100    }
101}